@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
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import {Command} from "commander";
|
|
3
|
+
import yoctoSpinner from "yocto-spinner";
|
|
4
|
+
import {getStoredToken} from "../../../lib/token.js"
|
|
5
|
+
import prisma from "../../../lib/db.js"
|
|
6
|
+
import { ensureDbConnection } from "../../../lib/dbHealth.js";
|
|
7
|
+
import {select} from "@clack/prompts";
|
|
8
|
+
import {startChat} from "../../../cli/chat/chat-with-ai.js";
|
|
9
|
+
import {startToolChat} from "../../../cli/chat/chat-with-ai-tools.js";
|
|
10
|
+
import {startAgentChat} from "../../../cli/chat/chat-with-ai-agent.js";
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
const wakeUpAction = async()=>{
|
|
14
|
+
const token = await getStoredToken();
|
|
15
|
+
if(!token?.access_token){
|
|
16
|
+
console.log(chalk.red("Not Authenticated. please login"))
|
|
17
|
+
return ;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const dbOk = await ensureDbConnection();
|
|
21
|
+
if(!dbOk){
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const spinner = yoctoSpinner({text: "Fetching user information ..."})
|
|
26
|
+
spinner.start()
|
|
27
|
+
|
|
28
|
+
let user;
|
|
29
|
+
try{
|
|
30
|
+
user = await prisma.user.findFirst({
|
|
31
|
+
where : {
|
|
32
|
+
sessions : {
|
|
33
|
+
some : {
|
|
34
|
+
token : token.access_token
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
select : {
|
|
39
|
+
id : true ,
|
|
40
|
+
name : true ,
|
|
41
|
+
email : true ,
|
|
42
|
+
image : true
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
finally{
|
|
47
|
+
spinner.stop();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if(!user){
|
|
51
|
+
console.log(chalk.red("User not found."))
|
|
52
|
+
return ;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(chalk.green(`Welcome back, ${user.name}! \n`));
|
|
56
|
+
|
|
57
|
+
const choice = await select({
|
|
58
|
+
message : "Select an Option",
|
|
59
|
+
options : [
|
|
60
|
+
{
|
|
61
|
+
value : "chat",
|
|
62
|
+
label: "Chat",
|
|
63
|
+
hint : "Simple chat with AI"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
value : "tool",
|
|
67
|
+
label: "Tool Calling",
|
|
68
|
+
hint : "Chat with tools (Google Search , Code Execution"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
value : "agent",
|
|
72
|
+
label: "Agentic Mode",
|
|
73
|
+
hint : "Advanced AI Agent"
|
|
74
|
+
},
|
|
75
|
+
]
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
switch(choice){
|
|
79
|
+
case "chat":
|
|
80
|
+
await startChat("chat");
|
|
81
|
+
break ;
|
|
82
|
+
case "tool":
|
|
83
|
+
await startToolChat();
|
|
84
|
+
break ;
|
|
85
|
+
case "agent":
|
|
86
|
+
await startAgentChat();
|
|
87
|
+
break ;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const wakeUp = new Command("wakeup").
|
|
92
|
+
description("Wake up the AI").
|
|
93
|
+
alias("wake-up").
|
|
94
|
+
alias("wakup").
|
|
95
|
+
action(wakeUpAction)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { requireAuth } from "../../../lib/token.js";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_SERVER_URL = "https://smart-cli-based-agent.onrender.com";
|
|
6
|
+
|
|
7
|
+
export const whoAmIAction = async (cmdOptions = {})=>{
|
|
8
|
+
const token = await requireAuth();
|
|
9
|
+
if(!token?.access_token){
|
|
10
|
+
console.log("No access token found . Please login.");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const baseUrl =
|
|
15
|
+
cmdOptions.serverUrl ||
|
|
16
|
+
process.env.ORBITAL_SERVER_URL ||
|
|
17
|
+
process.env.BACKEND_URL ||
|
|
18
|
+
process.env.SERVER_URL ||
|
|
19
|
+
DEFAULT_SERVER_URL;
|
|
20
|
+
const authHeaderValue = token.token_type
|
|
21
|
+
? `${token.token_type} ${token.access_token}`
|
|
22
|
+
: `Bearer ${token.access_token}`;
|
|
23
|
+
|
|
24
|
+
let res;
|
|
25
|
+
try {
|
|
26
|
+
res = await fetch(`${baseUrl}/api/me`, {
|
|
27
|
+
method: "GET",
|
|
28
|
+
headers: {
|
|
29
|
+
Authorization: authHeaderValue,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error(chalk.red(`Failed to reach server: ${baseUrl}`));
|
|
34
|
+
console.error(
|
|
35
|
+
chalk.gray(
|
|
36
|
+
"Set `ORBITAL_SERVER_URL` (or pass `--server-url`) to your deployed backend, or start your local server.",
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!res.ok) {
|
|
43
|
+
const bodyText = await res.text().catch(() => "");
|
|
44
|
+
console.error(
|
|
45
|
+
chalk.red(
|
|
46
|
+
`Failed to fetch session (${res.status} ${res.statusText}). ${bodyText}`.trim(),
|
|
47
|
+
),
|
|
48
|
+
);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const session = await res.json();
|
|
53
|
+
const user = session?.user;
|
|
54
|
+
|
|
55
|
+
if (!user) {
|
|
56
|
+
console.log(chalk.yellow("No active session found on server."));
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(chalk.green("Logged in as:"));
|
|
61
|
+
console.log(chalk.gray(`id: ${user.id ?? "(unknown)"}`));
|
|
62
|
+
if (user.name) console.log(chalk.gray(`name: ${user.name}`));
|
|
63
|
+
if (user.email) console.log(chalk.gray(`email: ${user.email}`));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const whoAmI = new Command("whoami")
|
|
67
|
+
.description("Show the currently logged-in user")
|
|
68
|
+
.option(
|
|
69
|
+
"--server-url <url>",
|
|
70
|
+
"Orbital server base URL (e.g. https://smart-cli-based-agent.onrender.com)",
|
|
71
|
+
process.env.ORBITAL_SERVER_URL ||
|
|
72
|
+
process.env.BACKEND_URL ||
|
|
73
|
+
process.env.SERVER_URL ||
|
|
74
|
+
DEFAULT_SERVER_URL,
|
|
75
|
+
)
|
|
76
|
+
.action(whoAmIAction);
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { cancel, confirm, intro, outro, isCancel } from "@clack/prompts";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import open from "open";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import yoctoSpinner from "yocto-spinner";
|
|
8
|
+
import * as z from "zod";
|
|
9
|
+
import { createAuthClient } from "better-auth/client";
|
|
10
|
+
import { deviceAuthorizationClient } from "better-auth/client/plugins";
|
|
11
|
+
import { logger } from "better-auth";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
import { getStoredToken, isTokenExpired, storeToken ,TOKEN_FILE } from "../../../lib/token.js";
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = path.dirname(__filename);
|
|
19
|
+
|
|
20
|
+
const URL = "https://smart-cli-based-agent.onrender.com";
|
|
21
|
+
const CLIENT_ID = process.env.GITHUB_CLIENT_ID;
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
export const loginAction = async (cmdOptions) => {
|
|
25
|
+
if (!process.env.GOOGLE_GENERATIVE_AI_API_KEY) {
|
|
26
|
+
console.log(chalk.red("Gemini API key is not set."));
|
|
27
|
+
console.log(
|
|
28
|
+
chalk.gray(
|
|
29
|
+
"Run: orbital setkey <your-gemini-api-key>\nThen run: orbital login",
|
|
30
|
+
),
|
|
31
|
+
);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const schema = z.object({
|
|
36
|
+
serverUrl: z.string().optional(),
|
|
37
|
+
clientId: z.string().optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const options = schema.parse({
|
|
41
|
+
serverUrl: cmdOptions.serverUrl,
|
|
42
|
+
clientId: cmdOptions.clientId,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const serverUrl = options.serverUrl || URL;
|
|
46
|
+
const clientId = options.clientId || CLIENT_ID;
|
|
47
|
+
|
|
48
|
+
intro(chalk.bold("Auth CLI Login"));
|
|
49
|
+
|
|
50
|
+
const existingToken = await getStoredToken();
|
|
51
|
+
const expired = await isTokenExpired();
|
|
52
|
+
|
|
53
|
+
if (existingToken && !expired) {
|
|
54
|
+
const shouldReAuth = await confirm({
|
|
55
|
+
message: "Your are already logged-In. Do You want to login Again",
|
|
56
|
+
initialValue: false,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (isCancel(shouldReAuth) || !shouldReAuth) {
|
|
60
|
+
cancel("Login Cancelled");
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const authClient = createAuthClient({
|
|
66
|
+
baseURL: serverUrl,
|
|
67
|
+
plugins: [deviceAuthorizationClient()],
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const spinner = yoctoSpinner({
|
|
71
|
+
text: "Requesting device authorization...",
|
|
72
|
+
}).start();
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
const { data, error } = await authClient.device.code({
|
|
76
|
+
client_id: clientId,
|
|
77
|
+
scope: "openid profile email",
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
spinner.stop();
|
|
81
|
+
|
|
82
|
+
if (error || !data) {
|
|
83
|
+
const errorMessage =
|
|
84
|
+
error?.error_description ||
|
|
85
|
+
error?.message ||
|
|
86
|
+
JSON.stringify(error) ||
|
|
87
|
+
"Unknown error";
|
|
88
|
+
console.error(
|
|
89
|
+
chalk.red(`Failed to request device authorization: ${errorMessage}`),
|
|
90
|
+
);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const {
|
|
95
|
+
user_code,
|
|
96
|
+
verification_uri,
|
|
97
|
+
verification_uri_complete,
|
|
98
|
+
expires_in,
|
|
99
|
+
device_code,
|
|
100
|
+
interval,
|
|
101
|
+
} = data;
|
|
102
|
+
|
|
103
|
+
console.log(chalk.cyan("\nDevice Authorization Required"));
|
|
104
|
+
console.log(
|
|
105
|
+
`Visit: ${chalk.underline.blue(
|
|
106
|
+
verification_uri_complete || verification_uri,
|
|
107
|
+
)}`,
|
|
108
|
+
);
|
|
109
|
+
console.log(`Enter Code: ${chalk.bold.green(user_code)}\n`);
|
|
110
|
+
|
|
111
|
+
const shouldOpen = await confirm({
|
|
112
|
+
message: "Open browser automatically?",
|
|
113
|
+
initialValue: true,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
if (!isCancel(shouldOpen) && shouldOpen) {
|
|
117
|
+
const uriToOpen = verification_uri_complete || verification_uri;
|
|
118
|
+
await open(uriToOpen);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log(
|
|
122
|
+
chalk.gray(
|
|
123
|
+
`Waiting for authorization (expires in ${Math.floor(
|
|
124
|
+
expires_in / 60,
|
|
125
|
+
)} minutes)...`,
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const token = await pollForToken(
|
|
130
|
+
authClient,
|
|
131
|
+
device_code,
|
|
132
|
+
clientId,
|
|
133
|
+
interval,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (token) {
|
|
137
|
+
const saved = await storeToken(token);
|
|
138
|
+
|
|
139
|
+
if (!saved) {
|
|
140
|
+
console.log(chalk.yellow("\n Warming: Could not save authentication token."));
|
|
141
|
+
console.log(chalk.yellow("You may need to login again on next use."));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
outro(chalk.green("Login successfull !"));
|
|
145
|
+
|
|
146
|
+
//console.log(chalk.gray(`\n Token saved to: ${TOKEN_FILE}`));
|
|
147
|
+
|
|
148
|
+
console.log(
|
|
149
|
+
chalk.gray("You can now use AI commands without logging in again. \n "),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
} catch (err) {
|
|
153
|
+
spinner.stop();
|
|
154
|
+
console.error(chalk.red("Login failed:"), err);
|
|
155
|
+
process.exit(1);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const pollForToken = async (
|
|
160
|
+
authClient,
|
|
161
|
+
deviceCode,
|
|
162
|
+
clientId,
|
|
163
|
+
initialInterval,
|
|
164
|
+
) => {
|
|
165
|
+
let pollingInterval = initialInterval;
|
|
166
|
+
let dots = 0;
|
|
167
|
+
|
|
168
|
+
const spinner = yoctoSpinner({ text: "", color: "cyan" });
|
|
169
|
+
|
|
170
|
+
return new Promise((resolve, reject) => {
|
|
171
|
+
const poll = async () => {
|
|
172
|
+
dots = (dots + 1) % 4;
|
|
173
|
+
spinner.text = chalk.gray(
|
|
174
|
+
`Polling for authorization ${".".repeat(dots)}${" ".repeat(3 - dots)}`,
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
if (!spinner.isSpinning) spinner.start();
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const { data, error } = await authClient.device.token({
|
|
181
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
182
|
+
device_code: deviceCode,
|
|
183
|
+
client_id: clientId,
|
|
184
|
+
fetchOptions: {
|
|
185
|
+
headers: {
|
|
186
|
+
"user-agent": `My CLI`,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
if (data?.access_token) {
|
|
192
|
+
console.log(
|
|
193
|
+
chalk.bold.yellow(`Your access token: ${data.access_token}`),
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
spinner.stop();
|
|
197
|
+
resolve(data);
|
|
198
|
+
return;
|
|
199
|
+
} else if (error) {
|
|
200
|
+
switch (error.error) {
|
|
201
|
+
case "authorization_pending":
|
|
202
|
+
break;
|
|
203
|
+
case "slow_down":
|
|
204
|
+
pollingInterval += 5;
|
|
205
|
+
break;
|
|
206
|
+
case "access_denied":
|
|
207
|
+
spinner.stop();
|
|
208
|
+
console.error("Access was denied by the user");
|
|
209
|
+
reject(new Error("access_denied"));
|
|
210
|
+
return;
|
|
211
|
+
case "expired_token":
|
|
212
|
+
spinner.stop();
|
|
213
|
+
console.error("The device code has expired. Please try again.");
|
|
214
|
+
reject(new Error("expired_token"));
|
|
215
|
+
return;
|
|
216
|
+
default:
|
|
217
|
+
spinner.stop();
|
|
218
|
+
logger.error(`Error: ${error.error_description}`);
|
|
219
|
+
reject(
|
|
220
|
+
new Error(error.error_description || "Unknown device error"),
|
|
221
|
+
);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
} catch (err) {
|
|
226
|
+
spinner.stop();
|
|
227
|
+
logger.error(`Error: ${err?.message || err}`);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
setTimeout(poll, pollingInterval * 1000);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
setTimeout(poll, pollingInterval * 1000);
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
export const login = new Command("login")
|
|
239
|
+
.description("Login to Better Auth")
|
|
240
|
+
.option("--server-url <url>", "The Better Auth server URL", URL)
|
|
241
|
+
.option("--client-id <id>", "The OAuth client ID", CLIENT_ID)
|
|
242
|
+
.action(loginAction);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { cancel, confirm, intro, outro, isCancel } from "@clack/prompts";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { clearStoredToken, getStoredToken } from "../../../lib/token.js";
|
|
5
|
+
|
|
6
|
+
export const logoutAction = async()=>{
|
|
7
|
+
intro(chalk.bold("Logout"));
|
|
8
|
+
|
|
9
|
+
const token = await getStoredToken();
|
|
10
|
+
|
|
11
|
+
if(!token){
|
|
12
|
+
console.log(chalk.yellow("You are not logged in."));
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const shouldLogout = await confirm({
|
|
17
|
+
message : "Are you want to logout?" ,
|
|
18
|
+
initialValue : false
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if(isCancel(shouldLogout) || !shouldLogout){
|
|
22
|
+
cancel("Logout cancelled");
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const cleared = await clearStoredToken();
|
|
27
|
+
|
|
28
|
+
if(cleared){
|
|
29
|
+
outro(chalk.green("Successfully logged out!"));
|
|
30
|
+
}
|
|
31
|
+
else{
|
|
32
|
+
console.log(chalk.yellow("Could not clear token file. "))
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const logout = new Command("logout")
|
|
37
|
+
.description("Logout from Better Auth")
|
|
38
|
+
.action(logoutAction);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { setGeminiApiKey, ORBITAL_CONFIG_FILE } from "../../../lib/orbitalConfig.js";
|
|
4
|
+
|
|
5
|
+
const setKeyAction = async (apiKey) => {
|
|
6
|
+
try {
|
|
7
|
+
await setGeminiApiKey(apiKey);
|
|
8
|
+
console.log(chalk.green("Gemini API key saved."));
|
|
9
|
+
console.log(chalk.gray(`Stored at: ${ORBITAL_CONFIG_FILE}`));
|
|
10
|
+
} catch (err) {
|
|
11
|
+
console.log(chalk.red("Failed to save key:"), err?.message || err);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const setkey = new Command("setkey")
|
|
17
|
+
.description("Set Gemini API key for Orbital")
|
|
18
|
+
.argument("<apiKey>", "Your Gemini API key")
|
|
19
|
+
.action(setKeyAction);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import "../config/env.js";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import figlet from "figlet";
|
|
6
|
+
import {Command} from "commander";
|
|
7
|
+
import {login} from "./commands/auth/login.js"
|
|
8
|
+
import {logout} from "./commands/auth/logout.js"
|
|
9
|
+
import {whoAmI} from "./commands/auth/aboutMe.js"
|
|
10
|
+
import {wakeUp} from "./commands/ai/wakeUp.js"
|
|
11
|
+
import {setkey} from "./commands/config/setkey.js"
|
|
12
|
+
|
|
13
|
+
const main = async()=>{
|
|
14
|
+
|
|
15
|
+
console.log(
|
|
16
|
+
chalk.cyan(
|
|
17
|
+
figlet.textSync("Orbital CLI", {
|
|
18
|
+
font: "Standard",
|
|
19
|
+
horizontallyLayout : "default"})
|
|
20
|
+
)
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
console.log(chalk.yellow(" A CLI Based AI Tool \n"));
|
|
24
|
+
|
|
25
|
+
const program = new Command("orbital");
|
|
26
|
+
|
|
27
|
+
program.version("0.0.1").
|
|
28
|
+
description("Orbital CLI - A CLI Based AI Tool").
|
|
29
|
+
addCommand(login).
|
|
30
|
+
addCommand(logout).
|
|
31
|
+
addCommand(whoAmI).
|
|
32
|
+
addCommand(wakeUp).
|
|
33
|
+
addCommand(setkey)
|
|
34
|
+
|
|
35
|
+
program.action(()=>{
|
|
36
|
+
program.help();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
program.parse();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
main().catch((err)=>{
|
|
43
|
+
console.log(chalk.red("Error running orbital CLI : " , err));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
})
|
|
@@ -0,0 +1,176 @@
|
|
|
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
|
+
// Backwards-compat alias (typo) to avoid runtime crashes if referenced.
|
|
8
|
+
const genenateObject = generateObject;
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const applicationSchema = z.object({
|
|
12
|
+
folderName: z.string().describe("Kebab-Case folder name for the application"),
|
|
13
|
+
description: z.string().describe("Brief description of what was created"),
|
|
14
|
+
files: z.array(
|
|
15
|
+
z.object({
|
|
16
|
+
path: z.string().describe("Relative file path (e.g src/App.jsx)"),
|
|
17
|
+
content: z.string().describe("Complete File Content")
|
|
18
|
+
}).describe("All files needed for the application")
|
|
19
|
+
),
|
|
20
|
+
setupCommands: z.array(
|
|
21
|
+
z.string().describe("Bash commands to setup and run (e.g: npm install , npm run dev")
|
|
22
|
+
),
|
|
23
|
+
dependencies: z
|
|
24
|
+
.array(
|
|
25
|
+
z.object({
|
|
26
|
+
name: z.string().describe("NPM package name"),
|
|
27
|
+
version: z.string().describe("Semver range/version")
|
|
28
|
+
})
|
|
29
|
+
)
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("NPM dependencies with versions")
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const printSystem = (message) => {
|
|
35
|
+
console.log(message);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const displayFileTree = (files, folderName) => {
|
|
39
|
+
printSystem(chalk.cyan("📂 Project Structure:"));
|
|
40
|
+
printSystem(chalk.white(`${folderName}/`));
|
|
41
|
+
|
|
42
|
+
// Build a tree structure
|
|
43
|
+
const tree = {};
|
|
44
|
+
|
|
45
|
+
files.forEach((file) => {
|
|
46
|
+
const parts = file.path.split("/");
|
|
47
|
+
let current = tree;
|
|
48
|
+
|
|
49
|
+
parts.forEach((part, index) => {
|
|
50
|
+
if (!current[part]) {
|
|
51
|
+
current[part] = index === parts.length - 1 ? null : {};
|
|
52
|
+
}
|
|
53
|
+
current = current[part];
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Recursive print function
|
|
58
|
+
function printTree(node, prefix = "") {
|
|
59
|
+
const entries = Object.keys(node || {}).sort();
|
|
60
|
+
|
|
61
|
+
entries.forEach((key, index) => {
|
|
62
|
+
const isLast = index === entries.length - 1;
|
|
63
|
+
const connector = isLast ? "└── " : "├── ";
|
|
64
|
+
|
|
65
|
+
if (node[key] === null) {
|
|
66
|
+
// file
|
|
67
|
+
printSystem(chalk.white(`${prefix}${connector}${key}`));
|
|
68
|
+
} else {
|
|
69
|
+
// folder
|
|
70
|
+
printSystem(chalk.gray(`${prefix}${connector}${key}/`));
|
|
71
|
+
const newPrefix = prefix + (isLast ? " " : "│ ");
|
|
72
|
+
printTree(node[key], newPrefix);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
printTree(tree, " ");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const createApplicationFiles = async(baseDir , folderName , files)=>{
|
|
81
|
+
const appDir = path.join(baseDir , folderName)
|
|
82
|
+
|
|
83
|
+
await fs.mkdir(appDir , {recursive : true});
|
|
84
|
+
|
|
85
|
+
printSystem(chalk.cyan(`\n Created directory: ${folderName}/`));
|
|
86
|
+
|
|
87
|
+
for(const file of files){
|
|
88
|
+
const filePath = path.join(appDir , file.path);
|
|
89
|
+
const fileDir = path.dirname(filePath);
|
|
90
|
+
|
|
91
|
+
await fs.mkdir(fileDir , {recursive : true });
|
|
92
|
+
await fs.writeFile(filePath , file.content , "utf8");
|
|
93
|
+
printSystem(chalk.green(` ${file.path}`));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return appDir ;
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
export const generateApplication = async (description, aiService, cwd = process.cwd()) => {
|
|
102
|
+
try {
|
|
103
|
+
printSystem(chalk.cyan(`\n Agent Mode: Generating your application...\n`));
|
|
104
|
+
printSystem(chalk.gray(`Request: ${description}\n`));
|
|
105
|
+
|
|
106
|
+
printSystem(chalk.magenta("Agent Response: \n"));
|
|
107
|
+
|
|
108
|
+
const { object: application } = await generateObject({
|
|
109
|
+
model: aiService.model,
|
|
110
|
+
schema: applicationSchema,
|
|
111
|
+
prompt: `Create a complete, production-ready application for: ${description}
|
|
112
|
+
|
|
113
|
+
CRITICAL REQUIREMENTS:
|
|
114
|
+
1. Generate ALL files needed for the application to run
|
|
115
|
+
2. Include package.json with ALL dependencies and correct versions
|
|
116
|
+
3. Include README.md with setup instructions
|
|
117
|
+
4. Include configuration files (.gitignore, etc.)
|
|
118
|
+
5. Write clean, well-commented, production-ready code
|
|
119
|
+
6. Include error handling and input validation
|
|
120
|
+
7. Use modern JavaScript/TypeScript best practices
|
|
121
|
+
8. Make sure all imports and paths are correct
|
|
122
|
+
9. NO PLACEHOLDERS - everything must be complete and working
|
|
123
|
+
|
|
124
|
+
Provide:
|
|
125
|
+
- A meaningful kebab-case folder name
|
|
126
|
+
- All necessary files with complete content
|
|
127
|
+
- Setup commands (cd folder, npm install, npm run dev, etc.)
|
|
128
|
+
- All dependencies with versions`
|
|
129
|
+
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
printSystem(chalk.green(`\n Generated: ${application.folderName}`));
|
|
133
|
+
printSystem(chalk.green(`\n Description: ${application.description}`));
|
|
134
|
+
|
|
135
|
+
if (application.files.length === 0) {
|
|
136
|
+
throw new Error("No files were generated");
|
|
137
|
+
}
|
|
138
|
+
displayFileTree(application.files, application.folderName);
|
|
139
|
+
|
|
140
|
+
printSystem(chalk.cyan("\n Creating files...\n"));
|
|
141
|
+
|
|
142
|
+
const appDir = await createApplicationFiles(
|
|
143
|
+
cwd ,
|
|
144
|
+
application.folderName ,
|
|
145
|
+
application.files
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
printSystem(chalk.green.bold(`\n Application created successfully!`));
|
|
149
|
+
printSystem(chalk.cyan(`Location: ${chalk.bold(appDir)}\n`));
|
|
150
|
+
|
|
151
|
+
if(application.setupCommands.length > 0){
|
|
152
|
+
printSystem(chalk.cyan(`Next Steps: \n`));
|
|
153
|
+
printSystem(chalk.white("```bash"));
|
|
154
|
+
application.setupCommands.forEach(cmd =>{
|
|
155
|
+
printSystem(chalk.white(cmd))
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
printSystem(chalk.white("```\n"));
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
folderName: application.folderName ,
|
|
163
|
+
appDir ,
|
|
164
|
+
files: application.files.map(f=> f.path),
|
|
165
|
+
commands : application.setupCommands,
|
|
166
|
+
success: true
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
printSystem(chalk.red(`\n Error generating application: ${error.message}\n`));
|
|
171
|
+
if(error.stack){
|
|
172
|
+
printSystem(chalk.dim(error.stack + "\n"));
|
|
173
|
+
}
|
|
174
|
+
throw error ;
|
|
175
|
+
}
|
|
176
|
+
}
|