@dubeyvishal/orbital-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.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # Orbital CLI
2
+
3
+ **Orbital CLI** is an AI-powered developer CLI that lets you **chat with AI**, run **smart tool-assisted searches**, and even enter **Agent Mode** where it can generate **mini-projects directly inside your directory** — in minutes — from just a single text prompt.
4
+
5
+ Built with **Next.js**, **Node.js**, **Express**, and **Commander.js**, with secure auth powered by **Better Auth (OAuth + Device Authorization)**.
6
+
7
+ ---
8
+
9
+ ## ✨ Features
10
+
11
+ ### AI Chat Mode
12
+ - Chat with AI directly in your terminal
13
+ - Streaming responses (real-time)
14
+ - Supports markdown rendering in terminal output
15
+
16
+ ### 🧠 Autonomous Tool Selection
17
+ Orbital intelligently selects tools when needed:
18
+ - 🔍 Web Search
19
+ - 🔗 URL Search / Page Summarization
20
+ - 🧰 Context-based tool routing (AI decides when to use tools)
21
+
22
+ ### 🤖 Agent Mode (Autonomous App Generator)
23
+ The most powerful mode:
24
+ - Generates **mini-projects** from a single prompt
25
+ - Creates **folders + files automatically**
26
+ - Writes production-ready code
27
+ - Generates setup commands
28
+ - Output saved neatly into your working directory
29
+ - Designed to build a runnable project in **a few minutes**
30
+
31
+ ### 🔐 Secure Authentication
32
+ - OAuth using **Better Auth**
33
+ - **Device Authorization flow**
34
+ - Session + token handling
35
+
36
+ ---
37
+
38
+ ## 🧱 Tech Stack
39
+ - **Next.js** (Dashboard / Web UI)
40
+ - **Node.js**
41
+ - **Express.js**
42
+ - **Commander.js** (CLI framework)
43
+ - **Better Auth** (OAuth + Device Flow)
44
+ - **Prisma** (Database ORM)
45
+ - **Neon PostgreSQL** (recommended)
46
+ - **AI SDK (Google Gemini)**
47
+
48
+ ---
49
+
50
+ ## 📦 Installation
51
+
52
+ ## Deployed URLs
53
+
54
+ - Frontend (Vercel): https://smart-cli-based-agent-t7x4.vercel.app/sign-in
55
+ - Backend (Render): https://smart-cli-based-agent.onrender.com
56
+
57
+ Clone the repo:
58
+
59
+ ```bash
60
+ git clone https://github.com/<your-username>/orbital-cli.git
61
+ cd orbital-cli
62
+
63
+ ```
64
+
65
+ Environment Setup
66
+
67
+ DATABASE_URL="postgresql://USER:PASSWORD@HOST:5432/DATABASE?sslmode=require"
68
+ GOOGLE_GEMINI_API_KEY="your_api_key"
69
+ ORBITAL_MODEL="gemini-2.5-flash"
70
+
71
+ # Deployed URLs
72
+ BETTER_AUTH_BASE_URL="https://smart-cli-based-agent.onrender.com"
73
+ FRONTEND_URL="https://smart-cli-based-agent-t7x4.vercel.app"
74
+
75
+ Then generate Prisma client + sync schema:
76
+
77
+ cd server
78
+ npx prisma generate
79
+ npx prisma db push
80
+
81
+ Run Orbital CLI:
82
+ orbital
83
+
84
+ Orbital uses secure OAuth + Device Authorization:
85
+ orbital login
86
+
87
+ Chat with AI
88
+ orbital wakeup
89
+ Output
90
+ When agent generation succeeds, Orbital shows:
91
+
92
+ App folder name
93
+ Total files created
94
+ Location on disk
95
+ Setup commands (install, run)
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@dubeyvishal/orbital-cli",
3
+ "version": "1.0.1",
4
+ "description": "A fullstack CLI-based AI platform with chat mode, multi-tool agents, and agentic AI workflows. Includes GitHub login with device authorization, secure authentication, and modular client–server architecture for building intelligent automation tools.",
5
+ "author": "Vishal Dubey",
6
+ "license": "MIT",
7
+
8
+ "private": false,
9
+
10
+ "type": "module",
11
+ "bin": {
12
+ "orbital": "server/src/cli/main.js"
13
+ },
14
+ "files": [
15
+ "server/src/cli/",
16
+ "server/src/config/",
17
+ "server/src/lib/",
18
+ "server/src/service/",
19
+ "server/prisma/schema.prisma",
20
+ "README.md"
21
+ ],
22
+
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+
27
+ "engines": {
28
+ "node": ">=18"
29
+ },
30
+
31
+ "dependencies": {
32
+ "@ai-sdk/google": "^3.0.6",
33
+ "@clack/prompts": "^0.11.0",
34
+ "@prisma/client": "^5.22.0",
35
+ "ai": "^6.0.29",
36
+ "better-auth": "^1.4.10",
37
+ "boxen": "^8.0.1",
38
+ "chalk": "^5.6.2",
39
+ "commander": "^14.0.2",
40
+ "dotenv": "^17.2.3",
41
+ "figlet": "^1.9.4",
42
+ "marked": "^15.0.12",
43
+ "marked-terminal": "^7.3.0",
44
+ "open": "^11.0.0",
45
+ "prisma": "^5.22.0",
46
+ "yocto-spinner": "^1.0.0",
47
+ "zod": "^4.3.5"
48
+ },
49
+
50
+ "scripts": {
51
+ "install-all": "npm install --prefix client && npm install --prefix server",
52
+ "start-client": "npm start --prefix client",
53
+ "start-server": "npm start --prefix server",
54
+ "dev": "npm run start-server && npm run start-client"
55
+ },
56
+
57
+ "keywords": [
58
+ "orbital",
59
+ "cli",
60
+ "nodejs",
61
+ "fullstack",
62
+ "client-server",
63
+ "javascript"
64
+ ]
65
+ }
@@ -0,0 +1,117 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "postgresql"
7
+ url = env("DATABASE_URL")
8
+ directUrl = env("DIRECT_DATABASE_URL")
9
+ }
10
+
11
+ model Test {
12
+ id String @id @default(cuid())
13
+ name String
14
+ }
15
+
16
+ model User {
17
+ id String @id @default(cuid())
18
+ name String
19
+ email String @unique
20
+ emailVerified Boolean @default(false)
21
+ image String?
22
+ createdAt DateTime @default(now())
23
+ updatedAt DateTime @updatedAt
24
+ accounts Account[]
25
+ conversations Conversation[]
26
+ sessions Session[]
27
+
28
+ @@map("user")
29
+ }
30
+
31
+ model Session {
32
+ id String @id @default(cuid())
33
+ expiresAt DateTime
34
+ token String @unique
35
+ createdAt DateTime @default(now())
36
+ updatedAt DateTime @updatedAt
37
+ ipAddress String?
38
+ userAgent String?
39
+ userId String
40
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
41
+
42
+ @@index([userId])
43
+ @@map("session")
44
+ }
45
+
46
+ model Account {
47
+ id String @id @default(cuid())
48
+ accountId String
49
+ providerId String
50
+ userId String
51
+ accessToken String?
52
+ refreshToken String?
53
+ idToken String?
54
+ accessTokenExpiresAt DateTime?
55
+ refreshTokenExpiresAt DateTime?
56
+ scope String?
57
+ password String?
58
+ createdAt DateTime @default(now())
59
+ updatedAt DateTime @updatedAt
60
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
61
+
62
+ @@index([userId])
63
+ @@map("account")
64
+ }
65
+
66
+ model Verification {
67
+ id String @id @default(cuid())
68
+ identifier String
69
+ value String
70
+ expiresAt DateTime
71
+ createdAt DateTime @default(now())
72
+ updatedAt DateTime @updatedAt
73
+
74
+ @@index([identifier])
75
+ @@map("verification")
76
+ }
77
+
78
+ model DeviceCode {
79
+ id String @id @default(cuid())
80
+ deviceCode String
81
+ userCode String
82
+ userId String?
83
+ expiresAt DateTime
84
+ status String
85
+ lastPolledAt DateTime?
86
+ pollingInterval Int?
87
+ clientId String?
88
+ scope String?
89
+
90
+ @@map("deviceCode")
91
+ }
92
+
93
+ model Conversation {
94
+ id String @id @default(cuid())
95
+ userId String
96
+ title String
97
+ mode String @default("chat")
98
+ createdAt DateTime @default(now())
99
+ updatedAt DateTime @updatedAt
100
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
101
+ messages Message[]
102
+
103
+ @@index([userId])
104
+ @@map("conversation")
105
+ }
106
+
107
+ model Message {
108
+ id String @id @default(cuid())
109
+ conversationId String
110
+ role String
111
+ content String
112
+ createdAt DateTime @default(now())
113
+ conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
114
+
115
+ @@index([conversationId])
116
+ @@map("message")
117
+ }
@@ -0,0 +1,107 @@
1
+ import { google } from "@ai-sdk/google";
2
+ import { streamText, generateObject } from "ai";
3
+ import { config } from "../../config/googleConfig.js";
4
+ import chalk from "chalk";
5
+
6
+ export class AIService {
7
+ constructor() {
8
+ if (!config.googleApiKey) {
9
+ throw new Error("GOOGLE_GENERATIVE_AI_API_KEY is not set in env");
10
+ }
11
+
12
+ this.model = google(config.model, {
13
+ apiKey: config.googleApiKey,
14
+ });
15
+ }
16
+
17
+ async sendMessage(messages, onChunk, tools = undefined, onToolCall = null) {
18
+ try {
19
+ const streamConfig = {
20
+ model: this.model,
21
+ messages,
22
+ temperature: config.temperature,
23
+ };
24
+
25
+ if (tools && Object.keys(tools).length > 0) {
26
+ streamConfig.tools = tools;
27
+ streamConfig.maxSteps = 5;
28
+ console.log(
29
+ chalk.gray(`[DEBUG] Tools enabled: ${Object.keys(tools).join(", ")}`)
30
+ );
31
+ }
32
+
33
+ const result = await streamText(streamConfig);
34
+
35
+ let fullResponse = "";
36
+
37
+ for await (const chunk of result.textStream) {
38
+ fullResponse += chunk;
39
+ if (onChunk) onChunk(chunk);
40
+ }
41
+
42
+ const toolCalls = [];
43
+ const toolResults = [];
44
+
45
+ const steps = await Promise.resolve(result.steps);
46
+
47
+ if (Array.isArray(steps)) {
48
+ for (const step of steps) {
49
+ if (
50
+ step?.toolCalls &&
51
+ Array.isArray(step.toolCalls) &&
52
+ step.toolCalls.length > 0
53
+ ) {
54
+ for (const toolCall of step.toolCalls) {
55
+ toolCalls.push(toolCall);
56
+ if (onToolCall) onToolCall(toolCall);
57
+ }
58
+ }
59
+
60
+ if (
61
+ step?.toolResults &&
62
+ Array.isArray(step.toolResults) &&
63
+ step.toolResults.length > 0
64
+ ) {
65
+ toolResults.push(...step.toolResults);
66
+ }
67
+ }
68
+ }
69
+
70
+ return {
71
+ content: fullResponse,
72
+ finishReason: result.finishReason,
73
+ usage: result.usage,
74
+ toolCalls,
75
+ toolResults,
76
+ steps,
77
+ };
78
+ } catch (error) {
79
+ console.error(chalk.red("AI Service Error:"), error?.message || error);
80
+ throw error;
81
+ }
82
+ }
83
+
84
+ async getMessage(messages, tools = undefined) {
85
+ const result = await this.sendMessage(messages, null, tools);
86
+ return result.content;
87
+ }
88
+
89
+ async generateStructured(schema, prompt) {
90
+ try {
91
+ const result = await generateObject({
92
+ model: this.model,
93
+ schema,
94
+ prompt,
95
+ });
96
+
97
+ return result.object;
98
+ } catch (error) {
99
+ console.log(
100
+ chalk.red("AI Structured Generation Error:"),
101
+ error?.message || error
102
+ );
103
+ throw error;
104
+ }
105
+ }
106
+ }
107
+
@@ -0,0 +1,263 @@
1
+ import chalk from "chalk";
2
+ import boxen from "boxen";
3
+ import { text, isCancel, cancel, intro, outro, confirm } from "@clack/prompts";
4
+ import yoctoSpinner from "yocto-spinner";
5
+ import { marked } from "marked";
6
+ import { markedTerminal } from "marked-terminal";
7
+ import { AIService } from "../ai/googleService.js";
8
+ import { ChatService } from "../../service/chatService.js";
9
+ import { getStoredToken } from "../../lib/token.js";
10
+ import prisma from "../../lib/db.js";
11
+ import { ensureDbConnection } from "../../lib/dbHealth.js";
12
+ import { generateApplication } from "../../config/agentConfig.js";
13
+
14
+ marked.use(markedTerminal());
15
+
16
+ let aiService;
17
+ const chatService = new ChatService();
18
+
19
+ const getEnabledToolNames = () => {
20
+ return [];
21
+ };
22
+
23
+ const getUserFromToken = async () => {
24
+ const token = await getStoredToken();
25
+
26
+ if (!token?.access_token) {
27
+ throw new Error("Not authenticated. Please run 'orbital login' first.");
28
+ }
29
+
30
+ const dbOk = await ensureDbConnection();
31
+ if (!dbOk) {
32
+ throw new Error(
33
+ "Database unavailable. Fix DATABASE_URL/connectivity and try again."
34
+ );
35
+ }
36
+
37
+ const spinner = yoctoSpinner({ text: "Authenticating..." }).start();
38
+
39
+ const user = await prisma.user.findFirst({
40
+ where: {
41
+ sessions: {
42
+ some: { token: token.access_token },
43
+ },
44
+ },
45
+ });
46
+
47
+ if (!user) {
48
+ spinner.error("User not found");
49
+ throw new Error("User not found. Please login again");
50
+ }
51
+
52
+ spinner.success(`Welcome back, ${user.name}!`);
53
+ return user;
54
+ };
55
+
56
+ const initConversation = async (userId, conversationId = null, mode = "tool") => {
57
+ const spinner = yoctoSpinner({ text: "Loading conversation..." }).start();
58
+
59
+ const conversation = await chatService.getOrCreateConversation(
60
+ userId,
61
+ conversationId,
62
+ mode
63
+ );
64
+
65
+ spinner.success("Conversation Loaded");
66
+
67
+ const enabledToolNames = getEnabledToolNames();
68
+ const toolsDisplay =
69
+ enabledToolNames.length > 0
70
+ ? `\n${chalk.gray("Active Tools:")} ${enabledToolNames.join(", ")}`
71
+ : `\n${chalk.gray("No tools enabled")}`;
72
+
73
+ const conversationInfo = boxen(
74
+ `${chalk.bold("Conversation")}: ${conversation.title}\n${chalk.gray(
75
+ "ID: " + conversation.id
76
+ )}\n${chalk.gray("Mode: " + conversation.mode)}${toolsDisplay}\n${chalk.cyan(
77
+ "Working Directory: "
78
+ )}${process.cwd()}`,
79
+ {
80
+ padding: 1,
81
+ margin: { top: 1, bottom: 1 },
82
+ borderStyle: "round",
83
+ borderColor: "magenta",
84
+ title: "Agent Mode",
85
+ titleAlignment: "center",
86
+ }
87
+ );
88
+
89
+ console.log(conversationInfo);
90
+ return conversation;
91
+ };
92
+
93
+ const saveMessage = async (conversationId, role, content) => {
94
+ return await chatService.addMessage(conversationId, role, content);
95
+ };
96
+
97
+ const agentLoop = async (conversation) => {
98
+ const helpBox = boxen(
99
+ `${chalk.cyan.bold("What can the agent do?")}\n\n` +
100
+ `${chalk.gray("• Generate complete applications from descriptions")}\n` +
101
+ `${chalk.gray("• Create all necessary files and folders")}\n` +
102
+ `${chalk.gray("• Include setup instructions and commands")}\n` +
103
+ `${chalk.gray("• Generate production-ready code")}\n\n` +
104
+ `${chalk.yellow.bold("Examples:")}\n` +
105
+ `${chalk.white('• "Build a todo app with React and Tailwind"')}\n` +
106
+ `${chalk.white('• "Create a REST API with Express and MongoDB"')}\n` +
107
+ `${chalk.white('• "Make a weather app using OpenWeatherMap API"')}\n\n` +
108
+ `${chalk.gray("Type 'exit' to end the session")}`,
109
+ {
110
+ padding: 1,
111
+ margin: { bottom: 1 },
112
+ borderStyle: "round",
113
+ borderColor: "cyan",
114
+ title: "Agent Instructions",
115
+ }
116
+ );
117
+
118
+ console.log(helpBox);
119
+
120
+ while (true) {
121
+ const userInput = await text({
122
+ message: chalk.magenta("What would you like to build?"),
123
+ placeholder: "Describe your application...",
124
+ validate(value) {
125
+ if (!value || value.trim().length === 0) {
126
+ return "Description cannot be empty";
127
+ }
128
+ if (value.trim().length < 10) {
129
+ return "Please provide more details (at least 10 characters)";
130
+ }
131
+ },
132
+ });
133
+
134
+ if (isCancel(userInput)) {
135
+ console.log(chalk.yellow("\nAgent session cancelled\n"));
136
+ process.exit(0);
137
+ }
138
+
139
+ if (userInput.toLowerCase() === "exit") {
140
+ console.log(chalk.yellow("\nAgent session ended\n"));
141
+ break;
142
+ }
143
+
144
+ console.log(
145
+ boxen(chalk.white(userInput), {
146
+ padding: 1,
147
+ margin: { left: 2, top: 1, bottom: 1 },
148
+ borderStyle: "round",
149
+ borderColor: "blue",
150
+ title: "Your Request",
151
+ titleAlignment: "left",
152
+ })
153
+ );
154
+
155
+ await saveMessage(conversation.id, "user", userInput);
156
+
157
+ try {
158
+ const result = await generateApplication(
159
+ userInput,
160
+ aiService,
161
+ process.cwd()
162
+ );
163
+
164
+ if (!result || !result.success) {
165
+ throw new Error("Generation returned no result.");
166
+ }
167
+
168
+ const responseMessage =
169
+ `Generated application: ${result.folderName}\n` +
170
+ `Files created: ${result.files.length}\n` +
171
+ `Location: ${result.appDir}\n\n` +
172
+ `Setup commands:\n${result.commands.join("\n")}`;
173
+
174
+ console.log(
175
+ boxen(chalk.green(responseMessage), {
176
+ padding: 1,
177
+ margin: { top: 1, bottom: 1 },
178
+ borderStyle: "round",
179
+ borderColor: "green",
180
+ title: "Generation Complete",
181
+ })
182
+ );
183
+
184
+ await saveMessage(conversation.id, "assistant", responseMessage);
185
+
186
+ const continuePrompt = await confirm({
187
+ message: chalk.cyan("Would you like to generate another?"),
188
+ initialValue: false,
189
+ });
190
+
191
+ if (isCancel(continuePrompt) || !continuePrompt) {
192
+ console.log(chalk.yellow("\nGreat! Check your new application.\n"));
193
+ break;
194
+ }
195
+ } catch (error) {
196
+ console.log(chalk.red(`\n❌ Error: ${error.message}\n`));
197
+
198
+ await saveMessage(conversation.id, "assistant", `Error: ${error.message}`);
199
+
200
+ const retry = await confirm({
201
+ message: chalk.cyan("Would you like to try again?"),
202
+ initialValue: true,
203
+ });
204
+
205
+ if (isCancel(retry) || !retry) {
206
+ break;
207
+ }
208
+ }
209
+ }
210
+ };
211
+
212
+ export const startAgentChat = async (conversationId = null) => {
213
+ try {
214
+ if (!process.env.GOOGLE_GENERATIVE_AI_API_KEY) {
215
+ throw new Error(
216
+ "Gemini API key is not set. Run: orbital setkey <your-gemini-api-key>"
217
+ );
218
+ }
219
+
220
+ aiService = new AIService();
221
+
222
+ intro(
223
+ boxen(
224
+ chalk.bold.magenta("Orbital AI - Agent Mode\n\n") +
225
+ chalk.gray("Autonomous Application Generator"),
226
+ {
227
+ padding: 1,
228
+ borderStyle: "double",
229
+ borderColor: "magenta",
230
+ }
231
+ )
232
+ );
233
+
234
+ const user = await getUserFromToken();
235
+
236
+ const shouldContinue = await confirm({
237
+ message: chalk.yellow(
238
+ "The agent will create files and folders in the current directory. Continue?"
239
+ ),
240
+ initialValue: true,
241
+ });
242
+
243
+ if (isCancel(shouldContinue) || !shouldContinue) {
244
+ cancel(chalk.yellow("Agent mode cancelled"));
245
+ process.exit(0);
246
+ }
247
+
248
+ const conversation = await initConversation(user.id, conversationId);
249
+ await agentLoop(conversation);
250
+
251
+ outro(chalk.green.bold("\nThanks for using Agent Mode!"));
252
+ } catch (error) {
253
+ console.log(
254
+ boxen(chalk.red(`Error: ${error.message}`), {
255
+ padding: 1,
256
+ margin: 1,
257
+ borderStyle: "round",
258
+ borderColor: "red",
259
+ })
260
+ );
261
+ process.exit(1);
262
+ }
263
+ };