@dubeyvishal/orbital-cli 1.0.3 → 1.0.5
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 +13 -0
- package/package.json +2 -1
- package/server/src/cli/ai/googleService.js +3 -4
- package/server/src/cli/chat/chat-with-ai-agent.js +45 -53
- package/server/src/cli/chat/chat-with-ai-tools.js +72 -84
- package/server/src/cli/chat/chat-with-ai.js +58 -67
- package/server/src/cli/commands/General/openApp.js +3 -20
- package/server/src/cli/commands/ai/wakeUp.js +11 -22
- package/server/src/cli/commands/auth/aboutMe.js +2 -1
- package/server/src/cli/commands/auth/login.js +34 -11
- package/server/src/cli/commands/config/setkey.js +12 -5
- package/server/src/cli/main.js +1 -1
- package/server/src/cli/utils/apiClient.js +66 -0
- package/server/src/config/agentConfig.js +22 -17
- package/server/src/config/api.js +22 -0
- package/server/src/config/env.js +2 -16
- package/server/src/config/toolConfig.js +7 -0
- package/server/src/controllers/aiController.js +58 -0
- package/server/src/controllers/cliController.js +77 -0
- package/server/src/db/prisma.js +3 -0
- package/server/src/index.js +12 -6
- package/server/src/lib/auth.js +4 -4
- package/server/src/lib/credentialStore.js +47 -0
- package/server/src/lib/orbitalConfig.js +165 -17
- package/server/src/lib/token.js +8 -14
- package/server/src/middleware/auth.js +39 -0
- package/server/src/middleware/errorHandler.js +11 -0
- package/server/src/routes/authRoutes.js +14 -0
- package/server/src/routes/cliRoutes.js +18 -0
- package/server/src/services/aiService.js +10 -0
- package/server/src/services/chatService.js +1 -0
- package/server/src/types/express.d.ts +19 -0
package/README.md
CHANGED
|
@@ -6,6 +6,19 @@ Built with **Next.js**, **Node.js**, **Express**, and **Commander.js**, with sec
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## 📸 Project Screenshots
|
|
10
|
+
|
|
11
|
+
### 🔐 Orbital Login Screen
|
|
12
|
+

|
|
13
|
+
|
|
14
|
+
### 🛠️ Orbital Wakeup – Tools Loaded
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
### 🤖 Agentic Mode
|
|
18
|
+

|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
9
22
|
## ✨ Features
|
|
10
23
|
|
|
11
24
|
### AI Chat Mode
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dubeyvishal/orbital-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
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
5
|
"author": "Vishal Dubey",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"commander": "^14.0.2",
|
|
32
32
|
"dotenv": "^17.2.3",
|
|
33
33
|
"figlet": "^1.9.4",
|
|
34
|
+
"keytar": "^7.9.0",
|
|
34
35
|
"marked": "^15.0.12",
|
|
35
36
|
"marked-terminal": "^7.3.0",
|
|
36
37
|
"open": "^11.0.0",
|
|
@@ -2,15 +2,14 @@ import { google } from "@ai-sdk/google";
|
|
|
2
2
|
import { streamText, generateObject } from "ai";
|
|
3
3
|
import { config } from "../../config/googleConfig.js";
|
|
4
4
|
import chalk from "chalk";
|
|
5
|
+
import { requireGeminiApiKeySync } from "../../lib/orbitalConfig.js";
|
|
5
6
|
|
|
6
7
|
export class AIService {
|
|
7
8
|
constructor() {
|
|
8
|
-
|
|
9
|
-
throw new Error("GOOGLE_GENERATIVE_AI_API_KEY is not set in env");
|
|
10
|
-
}
|
|
9
|
+
const apiKey = requireGeminiApiKeySync();
|
|
11
10
|
|
|
12
11
|
this.model = google(config.model, {
|
|
13
|
-
apiKey
|
|
12
|
+
apiKey,
|
|
14
13
|
});
|
|
15
14
|
}
|
|
16
15
|
|
|
@@ -4,18 +4,18 @@ import { text, isCancel, cancel, intro, outro, confirm } from "@clack/prompts";
|
|
|
4
4
|
import yoctoSpinner from "yocto-spinner";
|
|
5
5
|
import { marked } from "marked";
|
|
6
6
|
import { markedTerminal } from "marked-terminal";
|
|
7
|
-
import { AIService } from "../ai/googleService.js";
|
|
8
|
-
import { ChatService } from "../../service/chatService.js";
|
|
9
7
|
import { getStoredToken } from "../../lib/token.js";
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
import {
|
|
9
|
+
createApplicationFiles,
|
|
10
|
+
displayFileTree,
|
|
11
|
+
generateApplicationPlan,
|
|
12
|
+
} from "../../config/agentConfig.js";
|
|
13
|
+
import { apiRequestSafe } from "../utils/apiClient.js";
|
|
14
|
+
import { AIService } from "../ai/googleService.js";
|
|
15
|
+
import { requireGeminiApiKey } from "../../lib/orbitalConfig.js";
|
|
13
16
|
|
|
14
17
|
marked.use(markedTerminal());
|
|
15
18
|
|
|
16
|
-
let aiService;
|
|
17
|
-
const chatService = new ChatService();
|
|
18
|
-
|
|
19
19
|
const getEnabledToolNames = () => {
|
|
20
20
|
return [];
|
|
21
21
|
};
|
|
@@ -27,40 +27,31 @@ const getUserFromToken = async () => {
|
|
|
27
27
|
throw new Error("Not authenticated. Please run 'orbital login' first.");
|
|
28
28
|
}
|
|
29
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
30
|
const spinner = yoctoSpinner({ text: "Authenticating..." }).start();
|
|
31
|
+
try {
|
|
32
|
+
const result = await apiRequestSafe("/api/cli/me");
|
|
33
|
+
const user = result?.user;
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
});
|
|
35
|
+
if (!user) {
|
|
36
|
+
spinner.error("User not found");
|
|
37
|
+
throw new Error("User not found. Please login again");
|
|
38
|
+
}
|
|
46
39
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
spinner.success(`Welcome back, ${user.name}!`);
|
|
41
|
+
return user;
|
|
42
|
+
} finally {
|
|
43
|
+
spinner.stop();
|
|
50
44
|
}
|
|
51
|
-
|
|
52
|
-
spinner.success(`Welcome back, ${user.name}!`);
|
|
53
|
-
return user;
|
|
54
45
|
};
|
|
55
46
|
|
|
56
47
|
const initConversation = async (userId, conversationId = null, mode = "tool") => {
|
|
57
48
|
const spinner = yoctoSpinner({ text: "Loading conversation..." }).start();
|
|
58
49
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
conversationId,
|
|
62
|
-
|
|
63
|
-
|
|
50
|
+
const result = await apiRequestSafe("/api/cli/conversations/init", {
|
|
51
|
+
method: "POST",
|
|
52
|
+
body: { conversationId, mode },
|
|
53
|
+
});
|
|
54
|
+
const conversation = result?.conversation;
|
|
64
55
|
|
|
65
56
|
spinner.success("Conversation Loaded");
|
|
66
57
|
|
|
@@ -91,7 +82,10 @@ const initConversation = async (userId, conversationId = null, mode = "tool") =>
|
|
|
91
82
|
};
|
|
92
83
|
|
|
93
84
|
const saveMessage = async (conversationId, role, content) => {
|
|
94
|
-
return await
|
|
85
|
+
return await apiRequestSafe("/api/cli/messages", {
|
|
86
|
+
method: "POST",
|
|
87
|
+
body: { conversationId, role, content },
|
|
88
|
+
});
|
|
95
89
|
};
|
|
96
90
|
|
|
97
91
|
const agentLoop = async (conversation) => {
|
|
@@ -155,21 +149,27 @@ const agentLoop = async (conversation) => {
|
|
|
155
149
|
await saveMessage(conversation.id, "user", userInput);
|
|
156
150
|
|
|
157
151
|
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
process.cwd()
|
|
162
|
-
);
|
|
152
|
+
await requireGeminiApiKey();
|
|
153
|
+
const aiService = new AIService();
|
|
154
|
+
const application = await generateApplicationPlan(userInput, aiService);
|
|
163
155
|
|
|
164
|
-
if (!
|
|
165
|
-
throw new Error("Generation returned no
|
|
156
|
+
if (!application || !Array.isArray(application.files) || application.files.length === 0) {
|
|
157
|
+
throw new Error("Generation returned no files.");
|
|
166
158
|
}
|
|
167
159
|
|
|
160
|
+
displayFileTree(application.files, application.folderName);
|
|
161
|
+
|
|
162
|
+
const appDir = await createApplicationFiles(
|
|
163
|
+
process.cwd(),
|
|
164
|
+
application.folderName,
|
|
165
|
+
application.files
|
|
166
|
+
);
|
|
167
|
+
|
|
168
168
|
const responseMessage =
|
|
169
|
-
`Generated application: ${
|
|
170
|
-
`Files created: ${
|
|
171
|
-
`Location: ${
|
|
172
|
-
`Setup commands:\n${
|
|
169
|
+
`Generated application: ${application.folderName}\n` +
|
|
170
|
+
`Files created: ${application.files.length}\n` +
|
|
171
|
+
`Location: ${appDir}\n\n` +
|
|
172
|
+
`Setup commands:\n${(application.setupCommands || []).join("\n")}`;
|
|
173
173
|
|
|
174
174
|
console.log(
|
|
175
175
|
boxen(chalk.green(responseMessage), {
|
|
@@ -211,14 +211,6 @@ const agentLoop = async (conversation) => {
|
|
|
211
211
|
|
|
212
212
|
export const startAgentChat = async (conversationId = null) => {
|
|
213
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
214
|
intro(
|
|
223
215
|
boxen(
|
|
224
216
|
chalk.bold.magenta("Orbital AI - Agent Mode\n\n") +
|
|
@@ -11,11 +11,7 @@ import {
|
|
|
11
11
|
import yoctoSpinner from "yocto-spinner";
|
|
12
12
|
import { marked } from "marked";
|
|
13
13
|
import { markedTerminal } from "marked-terminal";
|
|
14
|
-
import { AIService } from "../ai/googleService.js";
|
|
15
|
-
import { ChatService } from "../../service/chatService.js";
|
|
16
14
|
import { getStoredToken } from "../../lib/token.js";
|
|
17
|
-
import prisma from "../../lib/db.js";
|
|
18
|
-
import { ensureDbConnection } from "../../lib/dbHealth.js";
|
|
19
15
|
import {
|
|
20
16
|
availableTools,
|
|
21
17
|
enableTools,
|
|
@@ -23,6 +19,9 @@ import {
|
|
|
23
19
|
getEnabledToolNames,
|
|
24
20
|
resetTools,
|
|
25
21
|
} from "../../config/toolConfig.js";
|
|
22
|
+
import { apiRequestSafe } from "../utils/apiClient.js";
|
|
23
|
+
import { AIService } from "../ai/googleService.js";
|
|
24
|
+
import { requireGeminiApiKey } from "../../lib/orbitalConfig.js";
|
|
26
25
|
|
|
27
26
|
marked.use(
|
|
28
27
|
markedTerminal({
|
|
@@ -43,9 +42,6 @@ marked.use(
|
|
|
43
42
|
})
|
|
44
43
|
);
|
|
45
44
|
|
|
46
|
-
let aiService;
|
|
47
|
-
const chatService = new ChatService();
|
|
48
|
-
|
|
49
45
|
const getUserFromToken = async () => {
|
|
50
46
|
const token = await getStoredToken();
|
|
51
47
|
|
|
@@ -53,28 +49,22 @@ const getUserFromToken = async () => {
|
|
|
53
49
|
throw new Error("Not authenticated. Please run 'orbital login' first.");
|
|
54
50
|
}
|
|
55
51
|
|
|
56
|
-
const dbOk = await ensureDbConnection();
|
|
57
|
-
if (!dbOk) {
|
|
58
|
-
throw new Error("Database unavailable. Fix DATABASE_URL/connectivity and try again.");
|
|
59
|
-
}
|
|
60
|
-
|
|
61
52
|
const spinner = yoctoSpinner({ text: "Authenticating..." }).start();
|
|
62
53
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
some: { token: token.access_token },
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
});
|
|
54
|
+
try {
|
|
55
|
+
const result = await apiRequestSafe("/api/cli/me");
|
|
56
|
+
const user = result?.user;
|
|
70
57
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
58
|
+
if (!user) {
|
|
59
|
+
spinner.error("User not found");
|
|
60
|
+
throw new Error("User not found. Please login again");
|
|
61
|
+
}
|
|
75
62
|
|
|
76
|
-
|
|
77
|
-
|
|
63
|
+
spinner.success(`Welcome back, ${user.name}!`);
|
|
64
|
+
return user;
|
|
65
|
+
} finally {
|
|
66
|
+
spinner.stop();
|
|
67
|
+
}
|
|
78
68
|
};
|
|
79
69
|
|
|
80
70
|
const selectTools = async () => {
|
|
@@ -88,7 +78,7 @@ const selectTools = async () => {
|
|
|
88
78
|
const toolOptions = availableTools.map((tool) => ({
|
|
89
79
|
value: tool.id,
|
|
90
80
|
label: tool.name,
|
|
91
|
-
|
|
81
|
+
|
|
92
82
|
hint: truncateHint(tool.description),
|
|
93
83
|
}));
|
|
94
84
|
|
|
@@ -131,17 +121,18 @@ const selectTools = async () => {
|
|
|
131
121
|
console.log(toolBox);
|
|
132
122
|
}
|
|
133
123
|
|
|
134
|
-
return selectedTools
|
|
124
|
+
return selectedTools;
|
|
135
125
|
};
|
|
136
126
|
|
|
137
127
|
const initConversation = async (userId, conversationId = null, mode = "tool") => {
|
|
138
128
|
const spinner = yoctoSpinner({ text: "Loading conversation..." }).start();
|
|
129
|
+
const result = await apiRequestSafe("/api/cli/conversations/init", {
|
|
130
|
+
method: "POST",
|
|
131
|
+
body: { conversationId, mode },
|
|
132
|
+
});
|
|
139
133
|
|
|
140
|
-
const conversation =
|
|
141
|
-
|
|
142
|
-
conversationId,
|
|
143
|
-
mode
|
|
144
|
-
);
|
|
134
|
+
const conversation = result?.conversation;
|
|
135
|
+
const messages = result?.messages || [];
|
|
145
136
|
|
|
146
137
|
spinner.success("Conversation Loaded");
|
|
147
138
|
|
|
@@ -167,9 +158,9 @@ const initConversation = async (userId, conversationId = null, mode = "tool") =>
|
|
|
167
158
|
|
|
168
159
|
console.log(conversationInfo);
|
|
169
160
|
|
|
170
|
-
if (
|
|
161
|
+
if (messages.length > 0) {
|
|
171
162
|
console.log(chalk.yellow("Previous Messages:\n"));
|
|
172
|
-
displayMessages(
|
|
163
|
+
displayMessages(messages);
|
|
173
164
|
}
|
|
174
165
|
|
|
175
166
|
return conversation;
|
|
@@ -205,54 +196,52 @@ const displayMessages = (messages) => {
|
|
|
205
196
|
const updateConversationTitle = async (conversationId, userInput, messageCount) => {
|
|
206
197
|
if (messageCount === 1) {
|
|
207
198
|
const title = userInput.slice(0, 50) + (userInput.length > 50 ? "..." : "");
|
|
208
|
-
await
|
|
199
|
+
await apiRequestSafe(`/api/cli/conversations/${conversationId}/title`, {
|
|
200
|
+
method: "PATCH",
|
|
201
|
+
body: { title },
|
|
202
|
+
});
|
|
209
203
|
}
|
|
210
204
|
};
|
|
211
205
|
|
|
212
206
|
const saveMessage = async (conversationId, role, content) => {
|
|
213
|
-
return await
|
|
207
|
+
return await apiRequestSafe("/api/cli/messages", {
|
|
208
|
+
method: "POST",
|
|
209
|
+
body: { conversationId, role, content },
|
|
210
|
+
});
|
|
214
211
|
};
|
|
215
212
|
|
|
216
|
-
const getAIResponse = async (conversationId) => {
|
|
213
|
+
const getAIResponse = async (conversationId, toolIds = []) => {
|
|
217
214
|
const spinner = yoctoSpinner({ text: "AI is thinking..." }).start();
|
|
218
|
-
|
|
219
|
-
const dbMessages = await chatService.getMessages(conversationId);
|
|
220
|
-
const aiMessages = chatService.formatMessageForAI(dbMessages);
|
|
221
|
-
|
|
222
|
-
const tools = getEnabledTools();
|
|
223
|
-
|
|
224
215
|
let fullResponse = "";
|
|
225
|
-
let isFirstChunk = true;
|
|
226
216
|
const toolCallsDetected = [];
|
|
227
217
|
|
|
228
218
|
try {
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (isFirstChunk) {
|
|
233
|
-
spinner.stop();
|
|
234
|
-
console.log("\n");
|
|
235
|
-
console.log(chalk.green.bold("Assistant: "));
|
|
236
|
-
console.log(chalk.gray("-".repeat(60)));
|
|
237
|
-
isFirstChunk = false;
|
|
238
|
-
}
|
|
239
|
-
fullResponse += chunk;
|
|
240
|
-
},
|
|
241
|
-
tools,
|
|
242
|
-
(toolCall) => {
|
|
243
|
-
toolCallsDetected.push(toolCall);
|
|
244
|
-
}
|
|
219
|
+
const messageResult = await apiRequestSafe(
|
|
220
|
+
`/api/cli/messages?conversationId=${encodeURIComponent(conversationId)}`,
|
|
221
|
+
{ method: "GET" }
|
|
245
222
|
);
|
|
246
223
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
224
|
+
const messages = Array.isArray(messageResult?.messages) ? messageResult.messages : [];
|
|
225
|
+
const aiMessages = messages.map((m) => ({ role: m.role, content: m.content }));
|
|
226
|
+
|
|
227
|
+
// Ensure the API key is present in env before any tools are initialized.
|
|
228
|
+
await requireGeminiApiKey();
|
|
229
|
+
|
|
230
|
+
// Ensure tool state matches what the user selected.
|
|
231
|
+
resetTools();
|
|
232
|
+
if (Array.isArray(toolIds)) enableTools(toolIds);
|
|
233
|
+
|
|
234
|
+
const tools = getEnabledTools();
|
|
235
|
+
const aiService = new AIService();
|
|
236
|
+
const result = await aiService.sendMessage(aiMessages, null, tools);
|
|
237
|
+
|
|
238
|
+
spinner.stop();
|
|
239
|
+
console.log("\n");
|
|
240
|
+
console.log(chalk.green.bold("Assistant: "));
|
|
241
|
+
console.log(chalk.gray("-".repeat(60)));
|
|
242
|
+
|
|
243
|
+
fullResponse = result?.content || "";
|
|
244
|
+
if (Array.isArray(result?.toolCalls)) toolCallsDetected.push(...result.toolCalls);
|
|
256
245
|
|
|
257
246
|
if (toolCallsDetected.length > 0) {
|
|
258
247
|
console.log("\n");
|
|
@@ -304,14 +293,14 @@ const getAIResponse = async (conversationId) => {
|
|
|
304
293
|
console.log(chalk.gray("-".repeat(60)));
|
|
305
294
|
console.log("\n");
|
|
306
295
|
|
|
307
|
-
return
|
|
296
|
+
return fullResponse.trim();
|
|
308
297
|
} catch (error) {
|
|
309
298
|
spinner.error("Failed to get AI response");
|
|
310
299
|
throw error;
|
|
311
300
|
}
|
|
312
301
|
};
|
|
313
302
|
|
|
314
|
-
const chatLoop = async (conversation) => {
|
|
303
|
+
const chatLoop = async (conversation, selectedToolIds = []) => {
|
|
315
304
|
const enabledToolNames = getEnabledToolNames();
|
|
316
305
|
|
|
317
306
|
const helpText = [
|
|
@@ -380,25 +369,24 @@ const chatLoop = async (conversation) => {
|
|
|
380
369
|
);
|
|
381
370
|
|
|
382
371
|
await saveMessage(conversation.id, "user", userInput);
|
|
383
|
-
const
|
|
372
|
+
const messageResult = await apiRequestSafe(
|
|
373
|
+
`/api/cli/messages?conversationId=${encodeURIComponent(conversation.id)}`,
|
|
374
|
+
{ method: "GET" }
|
|
375
|
+
);
|
|
384
376
|
|
|
385
|
-
const aiResponse = await getAIResponse(conversation.id);
|
|
377
|
+
const aiResponse = await getAIResponse(conversation.id, selectedToolIds);
|
|
386
378
|
await saveMessage(conversation.id, "assistant", aiResponse);
|
|
387
379
|
|
|
388
|
-
await updateConversationTitle(
|
|
380
|
+
await updateConversationTitle(
|
|
381
|
+
conversation.id,
|
|
382
|
+
userInput,
|
|
383
|
+
messageResult?.messages?.length || 0
|
|
384
|
+
);
|
|
389
385
|
}
|
|
390
386
|
};
|
|
391
387
|
|
|
392
388
|
export const startToolChat = async (conversationId) => {
|
|
393
389
|
try {
|
|
394
|
-
if (!process.env.GOOGLE_GENERATIVE_AI_API_KEY) {
|
|
395
|
-
throw new Error(
|
|
396
|
-
"Gemini API key is not set. Run: orbital setkey <your-gemini-api-key>"
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
aiService = new AIService();
|
|
401
|
-
|
|
402
390
|
intro(
|
|
403
391
|
boxen(chalk.bold.cyan("Orbital AI - Tool Calling Mode"), {
|
|
404
392
|
padding: 1,
|
|
@@ -409,11 +397,11 @@ export const startToolChat = async (conversationId) => {
|
|
|
409
397
|
|
|
410
398
|
const user = await getUserFromToken();
|
|
411
399
|
|
|
412
|
-
await selectTools();
|
|
400
|
+
const selectedToolIds = await selectTools();
|
|
413
401
|
|
|
414
402
|
const conversation = await initConversation(user.id, conversationId, "tool");
|
|
415
403
|
|
|
416
|
-
await chatLoop(conversation);
|
|
404
|
+
await chatLoop(conversation, selectedToolIds);
|
|
417
405
|
|
|
418
406
|
resetTools();
|
|
419
407
|
outro(chalk.green("Thanks for using tools"));
|
|
@@ -4,11 +4,10 @@ import {text , isCancel , cancel , intro , outro }from "@clack/prompts";
|
|
|
4
4
|
import yoctoSpinner from "yocto-spinner";
|
|
5
5
|
import {marked} from "marked";
|
|
6
6
|
import {markedTerminal} from "marked-terminal";
|
|
7
|
-
import {AIService} from "../ai/googleService.js";
|
|
8
|
-
import {ChatService} from "../../service/chatService.js"
|
|
9
7
|
import {getStoredToken} from "../../lib/token.js";
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
8
|
+
import { apiRequestSafe } from "../utils/apiClient.js";
|
|
9
|
+
import { AIService } from "../ai/googleService.js";
|
|
10
|
+
import { requireGeminiApiKey } from "../../lib/orbitalConfig.js";
|
|
12
11
|
|
|
13
12
|
marked.use(
|
|
14
13
|
markedTerminal({
|
|
@@ -29,43 +28,36 @@ marked.use(
|
|
|
29
28
|
})
|
|
30
29
|
)
|
|
31
30
|
|
|
32
|
-
let aiService;
|
|
33
|
-
const chatService = new ChatService();
|
|
34
|
-
|
|
35
31
|
const getUserFromToken = async()=>{
|
|
36
32
|
const token = await getStoredToken()
|
|
37
33
|
if(!token?.access_token){
|
|
38
34
|
throw new Error("Not authenticated. Please run 'orbital login' first.");
|
|
39
35
|
}
|
|
40
36
|
|
|
41
|
-
const dbOk = await ensureDbConnection();
|
|
42
|
-
if(!dbOk){
|
|
43
|
-
throw new Error("Database unavailable. Fix DATABASE_URL/connectivity and try again.");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
37
|
const spinner = yoctoSpinner({text: "Authenticating..."}).start();
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
spinner.
|
|
55
|
-
|
|
38
|
+
try {
|
|
39
|
+
const result = await apiRequestSafe("/api/cli/me");
|
|
40
|
+
const user = result?.user;
|
|
41
|
+
if(!user){
|
|
42
|
+
spinner.error("User not found");
|
|
43
|
+
throw new Error("User not found. Please login again");
|
|
44
|
+
}
|
|
45
|
+
spinner.success(`Welcome back , ${user.name}!`);
|
|
46
|
+
return user;
|
|
47
|
+
} finally {
|
|
48
|
+
spinner.stop();
|
|
56
49
|
}
|
|
57
|
-
spinner.success(`Welcome back , ${user.name}!`);
|
|
58
|
-
return user;
|
|
59
50
|
}
|
|
60
51
|
|
|
61
52
|
const initConversation = async(userId , conversationId = null , mode = "chat")=>{
|
|
62
53
|
const spinner = yoctoSpinner({text : "Loading conservation ..."}).start();
|
|
63
54
|
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
conversationId ,
|
|
67
|
-
|
|
68
|
-
|
|
55
|
+
const result = await apiRequestSafe("/api/cli/conversations/init", {
|
|
56
|
+
method: "POST",
|
|
57
|
+
body: { conversationId, mode },
|
|
58
|
+
});
|
|
59
|
+
const conversation = result?.conversation;
|
|
60
|
+
const messages = result?.messages || [];
|
|
69
61
|
spinner.success("Conversation Loaded");
|
|
70
62
|
|
|
71
63
|
const conversationInfo = boxen(
|
|
@@ -81,9 +73,9 @@ const initConversation = async(userId , conversationId = null , mode = "chat")=>
|
|
|
81
73
|
);
|
|
82
74
|
console.log(conversationInfo);
|
|
83
75
|
|
|
84
|
-
if(
|
|
76
|
+
if(messages.length > 0){
|
|
85
77
|
console.log(chalk.yellow("Previous Messsages: \n"));
|
|
86
|
-
displayMessages(
|
|
78
|
+
displayMessages(messages);
|
|
87
79
|
}
|
|
88
80
|
|
|
89
81
|
return conversation ;
|
|
@@ -117,7 +109,10 @@ const displayMessages = (messages) => {
|
|
|
117
109
|
};
|
|
118
110
|
|
|
119
111
|
const saveMessage = async(conversationId , role , content) =>{
|
|
120
|
-
return await
|
|
112
|
+
return await apiRequestSafe("/api/cli/messages", {
|
|
113
|
+
method: "POST",
|
|
114
|
+
body: { conversationId, role, content },
|
|
115
|
+
});
|
|
121
116
|
}
|
|
122
117
|
|
|
123
118
|
const getAIResponse = async(conversationId)=>{
|
|
@@ -126,40 +121,34 @@ const getAIResponse = async(conversationId)=>{
|
|
|
126
121
|
color: "cyan"
|
|
127
122
|
}).start();
|
|
128
123
|
|
|
129
|
-
const dbMessages = await chatService.getMessages(conversationId);
|
|
130
|
-
const aiMessages = chatService.formatMessageForAI(dbMessages);
|
|
131
|
-
|
|
132
124
|
let fullResponse = "";
|
|
133
|
-
let isFirstChunk = true ;
|
|
134
125
|
try{
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const header = chalk.green.bold("Assistent: ");
|
|
140
|
-
console.log(header);
|
|
141
|
-
console.log(chalk.gray("-".repeat(60)));
|
|
142
|
-
isFirstChunk = false ;
|
|
143
|
-
}
|
|
144
|
-
fullResponse += chunk ;
|
|
145
|
-
});
|
|
126
|
+
const messageResult = await apiRequestSafe(
|
|
127
|
+
`/api/cli/messages?conversationId=${encodeURIComponent(conversationId)}`,
|
|
128
|
+
{ method: "GET" }
|
|
129
|
+
);
|
|
146
130
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
console.log("\n");
|
|
150
|
-
const header = chalk.green.bold("Assistent: ");
|
|
151
|
-
console.log(header);
|
|
152
|
-
console.log(chalk.gray("-".repeat(60)));
|
|
153
|
-
isFirstChunk = false;
|
|
154
|
-
}
|
|
131
|
+
const messages = Array.isArray(messageResult?.messages) ? messageResult.messages : [];
|
|
132
|
+
const aiMessages = messages.map((m) => ({ role: m.role, content: m.content }));
|
|
155
133
|
|
|
134
|
+
await requireGeminiApiKey();
|
|
135
|
+
const aiService = new AIService();
|
|
136
|
+
const result = await aiService.sendMessage(aiMessages);
|
|
137
|
+
|
|
138
|
+
spinner.stop();
|
|
139
|
+
console.log("\n");
|
|
140
|
+
const header = chalk.green.bold("Assistent: ");
|
|
141
|
+
console.log(header);
|
|
142
|
+
console.log(chalk.gray("-".repeat(60)));
|
|
143
|
+
|
|
144
|
+
fullResponse = result?.content || "";
|
|
156
145
|
console.log("\n");
|
|
157
146
|
const renderMarkdown = marked.parse(fullResponse);
|
|
158
147
|
console.log(renderMarkdown);
|
|
159
148
|
console.log(chalk.gray("-".repeat(60)));
|
|
160
149
|
console.log("\n");
|
|
161
150
|
|
|
162
|
-
return
|
|
151
|
+
return fullResponse ;
|
|
163
152
|
}
|
|
164
153
|
catch(error){
|
|
165
154
|
spinner.error("Failed to get AI response");
|
|
@@ -170,7 +159,10 @@ const getAIResponse = async(conversationId)=>{
|
|
|
170
159
|
const updateConversationTitle = async(conversationId , userInput , messageCount)=>{
|
|
171
160
|
if(messageCount ===1){
|
|
172
161
|
const title = userInput.slice(0,50) + (userInput.length > 50 ? "..." : "");
|
|
173
|
-
await
|
|
162
|
+
await apiRequestSafe(`/api/cli/conversations/${conversationId}/title`, {
|
|
163
|
+
method: "PATCH",
|
|
164
|
+
body: { title },
|
|
165
|
+
});
|
|
174
166
|
}
|
|
175
167
|
}
|
|
176
168
|
|
|
@@ -220,12 +212,19 @@ const chatLoop = async(conversation)=>{
|
|
|
220
212
|
break ;
|
|
221
213
|
}
|
|
222
214
|
await saveMessage(conversation.id , "user" , userInput);
|
|
223
|
-
const
|
|
215
|
+
const messageResult = await apiRequestSafe(
|
|
216
|
+
`/api/cli/messages?conversationId=${encodeURIComponent(conversation.id)}`,
|
|
217
|
+
{ method: "GET" }
|
|
218
|
+
);
|
|
224
219
|
|
|
225
220
|
const aiResponse = await getAIResponse(conversation.id);
|
|
226
|
-
|
|
221
|
+
await saveMessage(conversation.id , "assistant" , aiResponse);
|
|
227
222
|
|
|
228
|
-
|
|
223
|
+
await updateConversationTitle(
|
|
224
|
+
conversation.id ,
|
|
225
|
+
userInput ,
|
|
226
|
+
messageResult?.messages?.length || 0
|
|
227
|
+
)
|
|
229
228
|
}
|
|
230
229
|
}
|
|
231
230
|
|
|
@@ -233,14 +232,6 @@ const chatLoop = async(conversation)=>{
|
|
|
233
232
|
|
|
234
233
|
export const startChat = async(mode="chat" , conversationId = null)=>{
|
|
235
234
|
try{
|
|
236
|
-
if (!process.env.GOOGLE_GENERATIVE_AI_API_KEY) {
|
|
237
|
-
throw new Error(
|
|
238
|
-
"Gemini API key is not set. Run: orbital setkey <your-gemini-api-key>"
|
|
239
|
-
);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
aiService = new AIService();
|
|
243
|
-
|
|
244
235
|
intro(
|
|
245
236
|
boxen(chalk.bold.cyan("Orbital AI Chat") , {
|
|
246
237
|
padding: 1 ,
|