@dubeyvishal/orbital-cli 1.6.12 → 1.6.14

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.
@@ -1,85 +1,98 @@
1
1
  import chalk from "chalk";
2
2
  import boxen from "boxen";
3
- import {text , isCancel , cancel , intro , outro }from "@clack/prompts";
3
+ import { text, isCancel, intro, outro } from "@clack/prompts";
4
4
  import yoctoSpinner from "yocto-spinner";
5
- import {marked} from "marked";
6
- import {markedTerminal} from "marked-terminal";
7
- import {getStoredToken} from "../../lib/token.js";
5
+ import { marked } from "marked";
6
+ import { markedTerminal } from "marked-terminal";
7
+ import { getStoredToken } from "../../lib/token.js";
8
8
  import { apiRequestSafe } from "../utils/apiClient.js";
9
9
  import { AIService } from "../ai/googleService.js";
10
- import { requireGeminiApiKey } from "../../lib/orbitalConfig.js";
10
+ import { requireApiKey, hydrateApiKeyEnv } from "../../lib/orbitalConfig.js";
11
+ import { parseModelChoice } from "../../config/aiConfig.js";
12
+
11
13
 
12
14
  marked.use(
13
- markedTerminal({
14
- code : chalk.cyan ,
15
- blockquote : chalk.gray.italic ,
16
- heading : chalk.green.bold ,
17
- firstHeading: chalk.magenta.underline.bold ,
18
- hr : chalk.reset ,
19
- listitem : chalk.reset ,
20
- list : chalk.reset ,
21
- paragraph : chalk.reset ,
22
- strong : chalk.bold ,
23
- em : chalk.italic ,
24
- codespan : chalk.yellow.bgBlack ,
25
- del : chalk.dim.gray.strikethrough ,
26
- link : chalk.blue.underline ,
27
- href : chalk.blue.underline
28
- })
29
- )
15
+ markedTerminal({
16
+ code: chalk.cyan,
17
+ blockquote: chalk.gray.italic,
18
+ heading: chalk.green.bold,
19
+ firstHeading: chalk.magenta.underline.bold,
20
+ hr: chalk.reset,
21
+ listitem: chalk.reset,
22
+ list: chalk.reset,
23
+ paragraph: chalk.reset,
24
+ strong: chalk.bold,
25
+ em: chalk.italic,
26
+ codespan: chalk.yellow.bgBlack,
27
+ del: chalk.dim.gray.strikethrough,
28
+ link: chalk.blue.underline,
29
+ href: chalk.blue.underline,
30
+ })
31
+ );
30
32
 
31
- const getUserFromToken = async()=>{
32
- const token = await getStoredToken()
33
- if(!token?.access_token){
34
- throw new Error("Not authenticated. Please run 'orbital login' first.");
35
- }
33
+ const getUserFromToken = async () => {
34
+ const token = await getStoredToken();
35
+ if (!token?.access_token) {
36
+ throw new Error("Not authenticated. Please run 'orbital login' first.");
37
+ }
36
38
 
37
- const spinner = yoctoSpinner({text: "Authenticating..."}).start();
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();
39
+ const spinner = yoctoSpinner({ text: "Authenticating..." }).start();
40
+ try {
41
+ const result = await apiRequestSafe("/api/cli/me");
42
+ const user = result?.user;
43
+ if (!user) {
44
+ spinner.error("User not found");
45
+ throw new Error("User not found. Please login again");
49
46
  }
50
- }
47
+ spinner.success(`Welcome back, ${user.name}!`);
48
+ return user;
49
+ } finally {
50
+ spinner.stop();
51
+ }
52
+ };
51
53
 
52
- const initConversation = async(userId , conversationId = null , mode = "chat")=>{
53
- const spinner = yoctoSpinner({text : "Loading conservation ..."}).start();
54
+ const initConversation = async (
55
+ userId,
56
+ conversationId = null,
57
+ mode = "chat",
58
+ modelDisplayName = null
59
+ ) => {
60
+ const spinner = yoctoSpinner({ text: "Loading conversation..." }).start();
54
61
 
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 || [];
61
- spinner.success("Conversation Loaded");
62
+ const result = await apiRequestSafe("/api/cli/conversations/init", {
63
+ method: "POST",
64
+ body: { conversationId, mode },
65
+ });
66
+ const conversation = result?.conversation;
67
+ const messages = result?.messages || [];
68
+ spinner.success("Conversation Loaded");
62
69
 
63
- const conversationInfo = boxen(
64
- `${chalk.bold("Conversation")} : ${conversation.title}\n ${chalk.gray("ID: " + conversation.id)} \n ${chalk.gray("Mode: " + conversation.mode)}`,
65
- {
66
- padding : 1 ,
67
- margin : {top: 1 , bottom : 1} ,
68
- borderStyle : "round" ,
69
- borderColor : "cyan" ,
70
- title : "Chat Settion",
71
- titleAlignment : "center"
72
- }
73
- );
74
- console.log(conversationInfo);
70
+ const modelLine = modelDisplayName
71
+ ? `\n ${chalk.cyan("Model: " + modelDisplayName)}`
72
+ : "";
75
73
 
76
- if(messages.length > 0){
77
- console.log(chalk.yellow("Previous Messsages: \n"));
78
- displayMessages(messages);
74
+ const conversationInfo = boxen(
75
+ `${chalk.bold("Conversation")}: ${conversation.title}\n ${chalk.gray(
76
+ "ID: " + conversation.id
77
+ )}\n ${chalk.gray("Mode: " + conversation.mode)}${modelLine}`,
78
+ {
79
+ padding: 1,
80
+ margin: { top: 1, bottom: 1 },
81
+ borderStyle: "round",
82
+ borderColor: "cyan",
83
+ title: "Chat Session",
84
+ titleAlignment: "center",
79
85
  }
86
+ );
87
+ console.log(conversationInfo);
80
88
 
81
- return conversation ;
82
- }
89
+ if (messages.length > 0) {
90
+ console.log(chalk.yellow("Previous Messages:\n"));
91
+ displayMessages(messages);
92
+ }
93
+
94
+ return conversation;
95
+ };
83
96
 
84
97
  const displayMessages = (messages) => {
85
98
  messages.forEach((msg) => {
@@ -108,153 +121,175 @@ const displayMessages = (messages) => {
108
121
  });
109
122
  };
110
123
 
111
- const saveMessage = async(conversationId , role , content) =>{
112
- return await apiRequestSafe("/api/cli/messages", {
113
- method: "POST",
114
- body: { conversationId, role, content },
115
- });
116
- }
124
+ const saveMessage = async (conversationId, role, content) => {
125
+ return await apiRequestSafe("/api/cli/messages", {
126
+ method: "POST",
127
+ body: { conversationId, role, content },
128
+ });
129
+ };
117
130
 
118
- const getAIResponse = async(conversationId)=>{
119
- const spinner = yoctoSpinner({
120
- text : "AI is thinking...",
121
- color: "cyan"
122
- }).start();
131
+ const getAIResponse = async (conversationId, modelConfig = null) => {
132
+ const aiService = new AIService(modelConfig);
133
+ await requireApiKey(aiService.provider);
123
134
 
124
- let fullResponse = "";
125
- try{
126
- const messageResult = await apiRequestSafe(
127
- `/api/cli/messages?conversationId=${encodeURIComponent(conversationId)}`,
128
- { method: "GET" }
129
- );
135
+ const spinner = yoctoSpinner({
136
+ text: `${aiService.getDisplayName()} is thinking...`,
137
+ color: "cyan",
138
+ }).start();
130
139
 
131
- const messages = Array.isArray(messageResult?.messages) ? messageResult.messages : [];
132
- const aiMessages = messages.map((m) => ({ role: m.role, content: m.content }));
140
+ let fullResponse = "";
141
+ try {
142
+ const messageResult = await apiRequestSafe(
143
+ `/api/cli/messages?conversationId=${encodeURIComponent(conversationId)}`,
144
+ { method: "GET" }
145
+ );
133
146
 
134
- await requireGeminiApiKey();
135
- const aiService = new AIService();
136
- const result = await aiService.sendMessage(aiMessages);
147
+ const messages = Array.isArray(messageResult?.messages)
148
+ ? messageResult.messages
149
+ : [];
150
+ const aiMessages = messages.map((m) => ({
151
+ role: m.role,
152
+ content: m.content,
153
+ }));
137
154
 
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)));
155
+ const result = await aiService.sendMessage(aiMessages);
143
156
 
144
- fullResponse = result?.content || "";
145
- console.log("\n");
146
- const renderMarkdown = marked.parse(fullResponse);
147
- console.log(renderMarkdown);
148
- console.log(chalk.gray("-".repeat(60)));
149
- console.log("\n");
157
+ spinner.stop();
158
+ console.log("\n");
159
+ const header = chalk.green.bold(`Assistant (${aiService.getDisplayName()}):`);
160
+ console.log(header);
161
+ console.log(chalk.gray("-".repeat(60)));
150
162
 
151
- return fullResponse ;
152
- }
153
- catch(error){
154
- spinner.error("Failed to get AI response");
155
- throw error ;
156
- }
157
- }
163
+ fullResponse = result?.content || "";
164
+ console.log("\n");
165
+ const renderMarkdown = marked.parse(fullResponse);
166
+ console.log(renderMarkdown);
167
+ console.log(chalk.gray("-".repeat(60)));
168
+ console.log("\n");
158
169
 
159
- const updateConversationTitle = async(conversationId , userInput , messageCount)=>{
160
- if(messageCount ===1){
161
- const title = userInput.slice(0,50) + (userInput.length > 50 ? "..." : "");
162
- await apiRequestSafe(`/api/cli/conversations/${conversationId}/title`, {
163
- method: "PATCH",
164
- body: { title },
165
- });
166
- }
167
- }
170
+ return fullResponse;
171
+ } catch (error) {
172
+ spinner.error("Failed to get AI response");
173
+ throw error;
174
+ }
175
+ };
168
176
 
169
- const chatLoop = async(conversation)=>{
170
- const helpBox = boxen(
171
- `${chalk.gray(`* Type your message and press Enter`)}\n ${chalk.gray(`* Markdown formatting is supported in response`)}
172
- \n${chalk.gray(`*Type "exit" to end conversation`)}\n ${chalk.gray(`*Please ctrl + C to quit anytime`)}`,
173
- {
174
- padding : 1 ,
175
- margin : {bottom : 1},
176
- borderStyle : "round" ,
177
- borderColor : "gray" ,
178
- dimBorder : true
179
- }
180
- );
181
- console.log(helpBox);
177
+ const updateConversationTitle = async (
178
+ conversationId,
179
+ userInput,
180
+ messageCount
181
+ ) => {
182
+ if (messageCount === 1) {
183
+ const title =
184
+ userInput.slice(0, 50) + (userInput.length > 50 ? "..." : "");
185
+ await apiRequestSafe(`/api/cli/conversations/${conversationId}/title`, {
186
+ method: "PATCH",
187
+ body: { title },
188
+ });
189
+ }
190
+ };
182
191
 
183
- while(true){
184
- const userInput = await text({
185
- message: chalk.blue("Your message"),
186
- placeholder : "Type your message...",
187
- validate(value){
188
- if(!value || value.trim().length ===0){
189
- return "Message cannot be empty";
190
- }
191
- },
192
- });
192
+ const chatLoop = async (conversation, modelConfig = null) => {
193
+ const helpBox = boxen(
194
+ `${chalk.gray(`* Type your message and press Enter`)}\n${chalk.gray(
195
+ `* Markdown formatting is supported in response`
196
+ )}\n${chalk.gray(`* Type "exit" to end conversation`)}\n${chalk.gray(
197
+ `* Press Ctrl + C to quit anytime`
198
+ )}`,
199
+ {
200
+ padding: 1,
201
+ margin: { bottom: 1 },
202
+ borderStyle: "round",
203
+ borderColor: "gray",
204
+ dimBorder: true,
205
+ }
206
+ );
207
+ console.log(helpBox);
193
208
 
194
- if(isCancel(userInput)){
195
- const exitBox = boxen(chalk.yellow("Chat session ended. GoodBye! "),{
196
- padding : 1 ,
197
- margin : 1,
198
- borderStyle : "round" ,
199
- borderColor : "yellow" ,
200
- });
201
- console.log(exitBox);
202
- process.exit(0);
209
+ while (true) {
210
+ const userInput = await text({
211
+ message: chalk.blue("Your message"),
212
+ placeholder: "Type your message...",
213
+ validate(value) {
214
+ if (!value || value.trim().length === 0) {
215
+ return "Message cannot be empty";
203
216
  }
204
- if(userInput.trim().toLowerCase() === "exit"){
205
- const exitBox = boxen(chalk.yellow("Chat session ended. GoodBye! "),{
206
- padding : 1 ,
207
- margin : 1,
208
- borderStyle : "round" ,
209
- borderColor : "yellow" ,
210
- });
211
- console.log(exitBox);
212
- break ;
213
- }
214
- await saveMessage(conversation.id , "user" , userInput);
215
- const messageResult = await apiRequestSafe(
216
- `/api/cli/messages?conversationId=${encodeURIComponent(conversation.id)}`,
217
- { method: "GET" }
218
- );
219
-
220
- const aiResponse = await getAIResponse(conversation.id);
221
- await saveMessage(conversation.id , "assistant" , aiResponse);
217
+ },
218
+ });
222
219
 
223
- await updateConversationTitle(
224
- conversation.id ,
225
- userInput ,
226
- messageResult?.messages?.length || 0
227
- )
220
+ if (isCancel(userInput)) {
221
+ const exitBox = boxen(chalk.yellow("Chat session ended. Goodbye!"), {
222
+ padding: 1,
223
+ margin: 1,
224
+ borderStyle: "round",
225
+ borderColor: "yellow",
226
+ });
227
+ console.log(exitBox);
228
+ process.exit(0);
229
+ }
230
+ if (userInput.trim().toLowerCase() === "exit") {
231
+ const exitBox = boxen(chalk.yellow("Chat session ended. Goodbye!"), {
232
+ padding: 1,
233
+ margin: 1,
234
+ borderStyle: "round",
235
+ borderColor: "yellow",
236
+ });
237
+ console.log(exitBox);
238
+ break;
228
239
  }
229
- }
240
+ await saveMessage(conversation.id, "user", userInput);
241
+ const messageResult = await apiRequestSafe(
242
+ `/api/cli/messages?conversationId=${encodeURIComponent(conversation.id)}`,
243
+ { method: "GET" }
244
+ );
230
245
 
246
+ const aiResponse = await getAIResponse(conversation.id, modelConfig);
247
+ await saveMessage(conversation.id, "assistant", aiResponse);
231
248
 
249
+ await updateConversationTitle(
250
+ conversation.id,
251
+ userInput,
252
+ messageResult?.messages?.length || 0
253
+ );
254
+ }
255
+ };
256
+
257
+ export const startChat = async (
258
+ mode = "chat",
259
+ conversationId = null,
260
+ modelConfig = null
261
+ ) => {
262
+ try {
263
+ intro(
264
+ boxen(chalk.bold.cyan("Orbital AI Chat"), {
265
+ padding: 1,
266
+ borderStyle: "double",
267
+ borderColor: "cyan",
268
+ })
269
+ );
232
270
 
233
- export const startChat = async(mode="chat" , conversationId = null)=>{
234
- try{
235
- intro(
236
- boxen(chalk.bold.cyan("Orbital AI Chat") , {
237
- padding: 1 ,
238
- borderStyle : "double",
239
- borderColor: "cyan"
240
- })
241
- )
271
+ const { provider } = parseModelChoice(modelConfig);
272
+ await hydrateApiKeyEnv(provider);
273
+ const aiService = new AIService(modelConfig);
242
274
 
243
- const user = await getUserFromToken()
244
- const conversation = await initConversation(user.id , conversationId , mode);
245
- await chatLoop(conversation)
275
+ const user = await getUserFromToken();
276
+ const conversation = await initConversation(
277
+ user.id,
278
+ conversationId,
279
+ mode,
280
+ aiService.getDisplayName()
281
+ );
282
+ await chatLoop(conversation, modelConfig);
246
283
 
247
- outro(chalk.green("Thanks for chatting"))
248
- }
249
- catch(error){
250
- const errorBox = boxen(chalk.red(`Error: ${error.message}`),{
251
- padding : 1 ,
252
- margin : 1 ,
253
- borderStyle : "round",
254
- borderColor : "red",
255
-
256
- });
257
- console.log(errorBox);
258
- process.exit(1);
259
- }
260
- }
284
+ outro(chalk.green("Thanks for chatting"));
285
+ } catch (error) {
286
+ const errorBox = boxen(chalk.red(`Error: ${error.message}`), {
287
+ padding: 1,
288
+ margin: 1,
289
+ borderStyle: "round",
290
+ borderColor: "red",
291
+ });
292
+ console.log(errorBox);
293
+ process.exit(1);
294
+ }
295
+ };