@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 +95 -0
- package/package.json +65 -0
- package/server/prisma/schema.prisma +117 -0
- package/server/src/cli/ai/googleService.js +107 -0
- package/server/src/cli/chat/chat-with-ai-agent.js +263 -0
- package/server/src/cli/chat/chat-with-ai-tools.js +432 -0
- package/server/src/cli/chat/chat-with-ai.js +269 -0
- package/server/src/cli/commands/ai/wakeUp.js +95 -0
- package/server/src/cli/commands/auth/aboutMe.js +76 -0
- package/server/src/cli/commands/auth/login.js +242 -0
- package/server/src/cli/commands/auth/logout.js +38 -0
- package/server/src/cli/commands/config/setkey.js +19 -0
- package/server/src/cli/main.js +45 -0
- package/server/src/config/agentConfig.js +176 -0
- package/server/src/config/env.js +100 -0
- package/server/src/config/googleConfig.js +6 -0
- package/server/src/config/toolConfig.js +113 -0
- package/server/src/lib/auth.js +37 -0
- package/server/src/lib/db.js +12 -0
- package/server/src/lib/dbHealth.js +106 -0
- package/server/src/lib/orbitalConfig.js +44 -0
- package/server/src/lib/token.js +86 -0
- package/server/src/service/chatService.js +156 -0
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
|
+
};
|