orbital-cli-agent 1.0.0
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/package.json +29 -0
- package/src/cli/ai/google-service.js +128 -0
- package/src/cli/api/api-client.js +115 -0
- package/src/cli/chat/chat-with-ai-agent.js +237 -0
- package/src/cli/chat/chat-with-ai-tool.js +371 -0
- package/src/cli/chat/chat-with-ai.js +267 -0
- package/src/cli/commands/ai/wakeup.js +61 -0
- package/src/cli/commands/auth/login.js +423 -0
- package/src/cli/main.js +52 -0
- package/src/config/agent.config.js +94 -0
- package/src/config/google.config.js +8 -0
- package/src/config/tool.config.js +107 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import boxen from "boxen";
|
|
3
|
+
import { text, isCancel, cancel, intro, outro, multiselect } 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/google-service.js";
|
|
8
|
+
import { getStoredToken } from "../commands/auth/login.js";
|
|
9
|
+
import * as apiClient from "../api/api-client.js";
|
|
10
|
+
import {
|
|
11
|
+
availableTools,
|
|
12
|
+
getEnabledTools,
|
|
13
|
+
enableTools,
|
|
14
|
+
getEnabledToolNames,
|
|
15
|
+
resetTools
|
|
16
|
+
} from "../../config/tool.config.js";
|
|
17
|
+
|
|
18
|
+
// Configure marked for terminal
|
|
19
|
+
marked.use(
|
|
20
|
+
markedTerminal({
|
|
21
|
+
code: chalk.cyan,
|
|
22
|
+
blockquote: chalk.gray.italic,
|
|
23
|
+
heading: chalk.green.bold,
|
|
24
|
+
firstHeading: chalk.magenta.underline.bold,
|
|
25
|
+
hr: chalk.reset,
|
|
26
|
+
listitem: chalk.reset,
|
|
27
|
+
list: chalk.reset,
|
|
28
|
+
paragraph: chalk.reset,
|
|
29
|
+
strong: chalk.bold,
|
|
30
|
+
em: chalk.italic,
|
|
31
|
+
codespan: chalk.yellow.bgBlack,
|
|
32
|
+
del: chalk.dim.gray.strikethrough,
|
|
33
|
+
link: chalk.blue.underline,
|
|
34
|
+
href: chalk.blue.underline,
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const aiService = new AIService();
|
|
39
|
+
|
|
40
|
+
async function getUserFromToken() {
|
|
41
|
+
const token = await getStoredToken();
|
|
42
|
+
|
|
43
|
+
if (!token?.access_token) {
|
|
44
|
+
throw new Error("Not authenticated. Please run 'orbit login' first.");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const spinner = yoctoSpinner({ text: "Authenticating..." }).start();
|
|
48
|
+
|
|
49
|
+
const user = await apiClient.getUserFromApi(token.access_token);
|
|
50
|
+
|
|
51
|
+
if (!user) {
|
|
52
|
+
spinner.error("User not found");
|
|
53
|
+
throw new Error("User not found. Please login again.");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
spinner.success(`Welcome back, ${user.name}!`);
|
|
57
|
+
return { user, token };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function selectTools() {
|
|
61
|
+
const toolOptions = availableTools.map(tool => ({
|
|
62
|
+
value: tool.id,
|
|
63
|
+
label: tool.name,
|
|
64
|
+
hint: tool.description,
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
const selectedTools = await multiselect({
|
|
68
|
+
message: chalk.cyan("Select tools to enable (Space to select, Enter to confirm):"),
|
|
69
|
+
options: toolOptions,
|
|
70
|
+
required: false,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (isCancel(selectedTools)) {
|
|
74
|
+
cancel(chalk.yellow("Tool selection cancelled"));
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Enable selected tools
|
|
79
|
+
enableTools(selectedTools);
|
|
80
|
+
|
|
81
|
+
if (selectedTools.length === 0) {
|
|
82
|
+
console.log(chalk.yellow("\nā ļø No tools selected. AI will work without tools.\n"));
|
|
83
|
+
} else {
|
|
84
|
+
const toolsBox = boxen(
|
|
85
|
+
chalk.green(`ā
Enabled tools:\n${selectedTools.map(id => {
|
|
86
|
+
const tool = availableTools.find(t => t.id === id);
|
|
87
|
+
return ` ⢠${tool.name}`;
|
|
88
|
+
}).join('\n')}`),
|
|
89
|
+
{
|
|
90
|
+
padding: 1,
|
|
91
|
+
margin: { top: 1, bottom: 1 },
|
|
92
|
+
borderStyle: "round",
|
|
93
|
+
borderColor: "green",
|
|
94
|
+
title: "š ļø Active Tools",
|
|
95
|
+
titleAlignment: "center",
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
console.log(toolsBox);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return selectedTools.length > 0;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function initConversation(token, userId, conversationId = null, mode = "tool") {
|
|
105
|
+
const spinner = yoctoSpinner({ text: "Loading conversation..." }).start();
|
|
106
|
+
|
|
107
|
+
const conversation = await apiClient.getOrCreateConversation(
|
|
108
|
+
token,
|
|
109
|
+
conversationId,
|
|
110
|
+
mode
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
spinner.success("Conversation loaded");
|
|
114
|
+
|
|
115
|
+
// Get enabled tool names for display
|
|
116
|
+
const enabledToolNames = getEnabledToolNames();
|
|
117
|
+
const toolsDisplay = enabledToolNames.length > 0
|
|
118
|
+
? `\n${chalk.gray("Active Tools:")} ${enabledToolNames.join(", ")}`
|
|
119
|
+
: `\n${chalk.gray("No tools enabled")}`;
|
|
120
|
+
|
|
121
|
+
// Display conversation info in a box
|
|
122
|
+
const conversationInfo = boxen(
|
|
123
|
+
`${chalk.bold("Conversation")}: ${conversation.title}\n${chalk.gray("ID: " + conversation.id)}\n${chalk.gray("Mode: " + conversation.mode)}${toolsDisplay}`,
|
|
124
|
+
{
|
|
125
|
+
padding: 1,
|
|
126
|
+
margin: { top: 1, bottom: 1 },
|
|
127
|
+
borderStyle: "round",
|
|
128
|
+
borderColor: "cyan",
|
|
129
|
+
title: "š¬ Tool Calling Session",
|
|
130
|
+
titleAlignment: "center",
|
|
131
|
+
}
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
console.log(conversationInfo);
|
|
135
|
+
|
|
136
|
+
// Display existing messages if any
|
|
137
|
+
if (conversation.messages?.length > 0) {
|
|
138
|
+
console.log(chalk.yellow("š Previous messages:\n"));
|
|
139
|
+
displayMessages(conversation.messages);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return conversation;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function displayMessages(messages) {
|
|
146
|
+
messages.forEach((msg) => {
|
|
147
|
+
if (msg.role === "user") {
|
|
148
|
+
const userBox = boxen(chalk.white(msg.content), {
|
|
149
|
+
padding: 1,
|
|
150
|
+
margin: { left: 2, bottom: 1 },
|
|
151
|
+
borderStyle: "round",
|
|
152
|
+
borderColor: "blue",
|
|
153
|
+
title: "š¤ You",
|
|
154
|
+
titleAlignment: "left",
|
|
155
|
+
});
|
|
156
|
+
console.log(userBox);
|
|
157
|
+
} else if (msg.role === "assistant") {
|
|
158
|
+
const renderedContent = marked.parse(msg.content);
|
|
159
|
+
const assistantBox = boxen(renderedContent.trim(), {
|
|
160
|
+
padding: 1,
|
|
161
|
+
margin: { left: 2, bottom: 1 },
|
|
162
|
+
borderStyle: "round",
|
|
163
|
+
borderColor: "green",
|
|
164
|
+
title: "š¤ Assistant (with tools)",
|
|
165
|
+
titleAlignment: "left",
|
|
166
|
+
});
|
|
167
|
+
console.log(assistantBox);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async function saveMessage(token, conversationId, role, content) {
|
|
173
|
+
return await apiClient.addMessage(token, conversationId, role, content);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function getAIResponse(token, conversationId) {
|
|
177
|
+
const spinner = yoctoSpinner({
|
|
178
|
+
text: "AI is thinking...",
|
|
179
|
+
color: "cyan"
|
|
180
|
+
}).start();
|
|
181
|
+
|
|
182
|
+
const dbMessages = await apiClient.getMessages(token, conversationId);
|
|
183
|
+
const aiMessages = dbMessages.map((msg) => ({
|
|
184
|
+
role: msg.role,
|
|
185
|
+
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
|
|
186
|
+
}));
|
|
187
|
+
|
|
188
|
+
const tools = getEnabledTools();
|
|
189
|
+
|
|
190
|
+
let fullResponse = "";
|
|
191
|
+
let isFirstChunk = true;
|
|
192
|
+
const toolCallsDetected = [];
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
// IMPORTANT: Pass tools in the streamText config
|
|
196
|
+
const result = await aiService.sendMessage(
|
|
197
|
+
aiMessages,
|
|
198
|
+
(chunk) => {
|
|
199
|
+
if (isFirstChunk) {
|
|
200
|
+
spinner.stop();
|
|
201
|
+
console.log("\n");
|
|
202
|
+
const header = chalk.green.bold("š¤ Assistant:");
|
|
203
|
+
console.log(header);
|
|
204
|
+
console.log(chalk.gray("ā".repeat(60)));
|
|
205
|
+
isFirstChunk = false;
|
|
206
|
+
}
|
|
207
|
+
fullResponse += chunk;
|
|
208
|
+
},
|
|
209
|
+
tools,
|
|
210
|
+
(toolCall) => {
|
|
211
|
+
toolCallsDetected.push(toolCall);
|
|
212
|
+
}
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// Display tool calls if any
|
|
216
|
+
if (toolCallsDetected.length > 0) {
|
|
217
|
+
console.log("\n");
|
|
218
|
+
const toolCallBox = boxen(
|
|
219
|
+
toolCallsDetected.map(tc =>
|
|
220
|
+
`${chalk.cyan("š§ Tool:")} ${tc.toolName}\n${chalk.gray("Args:")} ${JSON.stringify(tc.args, null, 2)}`
|
|
221
|
+
).join("\n\n"),
|
|
222
|
+
{
|
|
223
|
+
padding: 1,
|
|
224
|
+
margin: 1,
|
|
225
|
+
borderStyle: "round",
|
|
226
|
+
borderColor: "cyan",
|
|
227
|
+
title: "š ļø Tool Calls",
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
console.log(toolCallBox);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Display tool results if any
|
|
234
|
+
if (result.toolResults && result.toolResults.length > 0) {
|
|
235
|
+
const toolResultBox = boxen(
|
|
236
|
+
result.toolResults.map(tr =>
|
|
237
|
+
`${chalk.green("ā
Tool:")} ${tr.toolName}\n${chalk.gray("Result:")} ${JSON.stringify(tr.result, null, 2).slice(0, 200)}...`
|
|
238
|
+
).join("\n\n"),
|
|
239
|
+
{
|
|
240
|
+
padding: 1,
|
|
241
|
+
margin: 1,
|
|
242
|
+
borderStyle: "round",
|
|
243
|
+
borderColor: "green",
|
|
244
|
+
title: "š Tool Results",
|
|
245
|
+
}
|
|
246
|
+
);
|
|
247
|
+
console.log(toolResultBox);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Render markdown response
|
|
251
|
+
console.log("\n");
|
|
252
|
+
const renderedMarkdown = marked.parse(fullResponse);
|
|
253
|
+
console.log(renderedMarkdown);
|
|
254
|
+
console.log(chalk.gray("ā".repeat(60)));
|
|
255
|
+
console.log("\n");
|
|
256
|
+
|
|
257
|
+
return result.content;
|
|
258
|
+
} catch (error) {
|
|
259
|
+
spinner.error("Failed to get AI response");
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
async function updateConversationTitle(token, conversationId, userId, userInput, messageCount) {
|
|
266
|
+
if (messageCount === 1) {
|
|
267
|
+
const title = userInput.slice(0, 50) + (userInput.length > 50 ? "..." : "");
|
|
268
|
+
await apiClient.updateTitle(token, conversationId, title);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function chatLoop(token, conversation) {
|
|
273
|
+
const enabledToolNames = getEnabledToolNames();
|
|
274
|
+
const helpBox = boxen(
|
|
275
|
+
`${chalk.gray('⢠Type your message and press Enter')}\n${chalk.gray('⢠AI has access to:')} ${enabledToolNames.length > 0 ? enabledToolNames.join(", ") : "No tools"}\n${chalk.gray('⢠Type "exit" to end conversation')}\n${chalk.gray('⢠Press Ctrl+C to quit anytime')}`,
|
|
276
|
+
{
|
|
277
|
+
padding: 1,
|
|
278
|
+
margin: { bottom: 1 },
|
|
279
|
+
borderStyle: "round",
|
|
280
|
+
borderColor: "gray",
|
|
281
|
+
dimBorder: true,
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
console.log(helpBox);
|
|
286
|
+
|
|
287
|
+
while (true) {
|
|
288
|
+
const userInput = await text({
|
|
289
|
+
message: chalk.blue("š¬ Your message"),
|
|
290
|
+
placeholder: "Type your message...",
|
|
291
|
+
validate(value) {
|
|
292
|
+
if (!value || value.trim().length === 0) {
|
|
293
|
+
return "Message cannot be empty";
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
if (isCancel(userInput)) {
|
|
299
|
+
const exitBox = boxen(chalk.yellow("Chat session ended. Goodbye! š"), {
|
|
300
|
+
padding: 1,
|
|
301
|
+
margin: 1,
|
|
302
|
+
borderStyle: "round",
|
|
303
|
+
borderColor: "yellow",
|
|
304
|
+
});
|
|
305
|
+
console.log(exitBox);
|
|
306
|
+
process.exit(0);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (userInput.toLowerCase() === "exit") {
|
|
310
|
+
const exitBox = boxen(chalk.yellow("Chat session ended. Goodbye! š"), {
|
|
311
|
+
padding: 1,
|
|
312
|
+
margin: 1,
|
|
313
|
+
borderStyle: "round",
|
|
314
|
+
borderColor: "yellow",
|
|
315
|
+
});
|
|
316
|
+
console.log(exitBox);
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const userBox = boxen(chalk.white(userInput), {
|
|
321
|
+
padding: 1,
|
|
322
|
+
margin: { left: 2, top: 1, bottom: 1 },
|
|
323
|
+
borderStyle: "round",
|
|
324
|
+
borderColor: "blue",
|
|
325
|
+
title: "š¤ You",
|
|
326
|
+
titleAlignment: "left",
|
|
327
|
+
});
|
|
328
|
+
console.log(userBox);
|
|
329
|
+
|
|
330
|
+
await saveMessage(token, conversation.id, "user", userInput);
|
|
331
|
+
const messages = await apiClient.getMessages(token, conversation.id);
|
|
332
|
+
const aiResponse = await getAIResponse(token, conversation.id);
|
|
333
|
+
await saveMessage(token, conversation.id, "assistant", aiResponse);
|
|
334
|
+
await updateConversationTitle(token, conversation.id, conversation.userId, userInput, messages.length);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export async function startToolChat(conversationId = null) {
|
|
339
|
+
try {
|
|
340
|
+
intro(
|
|
341
|
+
boxen(chalk.bold.cyan("š ļø Orbit AI - Tool Calling Mode"), {
|
|
342
|
+
padding: 1,
|
|
343
|
+
borderStyle: "double",
|
|
344
|
+
borderColor: "cyan",
|
|
345
|
+
})
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
const { user, token } = await getUserFromToken();
|
|
349
|
+
|
|
350
|
+
// Select tools
|
|
351
|
+
await selectTools();
|
|
352
|
+
|
|
353
|
+
const conversation = await initConversation(token.access_token, user.id, conversationId, "tool");
|
|
354
|
+
await chatLoop(token.access_token, conversation);
|
|
355
|
+
|
|
356
|
+
// Reset tools on exit
|
|
357
|
+
resetTools();
|
|
358
|
+
|
|
359
|
+
outro(chalk.green("⨠Thanks for using tools!"));
|
|
360
|
+
} catch (error) {
|
|
361
|
+
const errorBox = boxen(chalk.red(`ā Error: ${error.message}`), {
|
|
362
|
+
padding: 1,
|
|
363
|
+
margin: 1,
|
|
364
|
+
borderStyle: "round",
|
|
365
|
+
borderColor: "red",
|
|
366
|
+
});
|
|
367
|
+
console.log(errorBox);
|
|
368
|
+
resetTools();
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import boxen from "boxen";
|
|
3
|
+
import { text, isCancel, cancel, intro, outro } 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/google-service.js";
|
|
8
|
+
import { getStoredToken } from "../commands/auth/login.js";
|
|
9
|
+
import * as apiClient from "../api/api-client.js";
|
|
10
|
+
|
|
11
|
+
// Configure marked to use terminal renderer
|
|
12
|
+
marked.use(
|
|
13
|
+
markedTerminal({
|
|
14
|
+
// Styling options for terminal output
|
|
15
|
+
code: chalk.cyan,
|
|
16
|
+
blockquote: chalk.gray.italic,
|
|
17
|
+
heading: chalk.green.bold,
|
|
18
|
+
firstHeading: chalk.magenta.underline.bold,
|
|
19
|
+
hr: chalk.reset,
|
|
20
|
+
listitem: chalk.reset,
|
|
21
|
+
list: chalk.reset,
|
|
22
|
+
paragraph: chalk.reset,
|
|
23
|
+
strong: chalk.bold,
|
|
24
|
+
em: chalk.italic,
|
|
25
|
+
codespan: chalk.yellow.bgBlack,
|
|
26
|
+
del: chalk.dim.gray.strikethrough,
|
|
27
|
+
link: chalk.blue.underline,
|
|
28
|
+
href: chalk.blue.underline,
|
|
29
|
+
})
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Initialize service
|
|
33
|
+
const aiService = new AIService();
|
|
34
|
+
|
|
35
|
+
async function getUserFromToken() {
|
|
36
|
+
const token = await getStoredToken();
|
|
37
|
+
|
|
38
|
+
if (!token?.access_token) {
|
|
39
|
+
throw new Error("Not authenticated. Please run 'orbit login' first.");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const spinner = yoctoSpinner({ text: "Authenticating..." }).start();
|
|
43
|
+
|
|
44
|
+
const user = await apiClient.getUserFromApi(token.access_token);
|
|
45
|
+
|
|
46
|
+
if (!user) {
|
|
47
|
+
spinner.error("User not found");
|
|
48
|
+
throw new Error("User not found. Please login again.");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
spinner.success(`Welcome back, ${user.name}!`);
|
|
52
|
+
return { user, token };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function initConversation(token, userId, conversationId = null, mode = "chat") {
|
|
56
|
+
const spinner = yoctoSpinner({ text: "Loading conversation..." }).start();
|
|
57
|
+
|
|
58
|
+
const conversation = await apiClient.getOrCreateConversation(
|
|
59
|
+
token,
|
|
60
|
+
conversationId,
|
|
61
|
+
mode
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
spinner.success("Conversation loaded");
|
|
65
|
+
|
|
66
|
+
// Display conversation info in a box
|
|
67
|
+
const conversationInfo = boxen(
|
|
68
|
+
`${chalk.bold("Conversation")}: ${conversation.title}\n${chalk.gray("ID: " + conversation.id)}\n${chalk.gray("Mode: " + conversation.mode)}`,
|
|
69
|
+
{
|
|
70
|
+
padding: 1,
|
|
71
|
+
margin: { top: 1, bottom: 1 },
|
|
72
|
+
borderStyle: "round",
|
|
73
|
+
borderColor: "cyan",
|
|
74
|
+
title: "š¬ Chat Session",
|
|
75
|
+
titleAlignment: "center",
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
console.log(conversationInfo);
|
|
80
|
+
|
|
81
|
+
// Display existing messages if any
|
|
82
|
+
if (conversation.messages?.length > 0) {
|
|
83
|
+
console.log(chalk.yellow("š Previous messages:\n"));
|
|
84
|
+
displayMessages(conversation.messages);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return conversation;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function displayMessages(messages) {
|
|
91
|
+
messages.forEach((msg) => {
|
|
92
|
+
if (msg.role === "user") {
|
|
93
|
+
const userBox = boxen(chalk.white(msg.content), {
|
|
94
|
+
padding: 1,
|
|
95
|
+
margin: { left: 2, bottom: 1 },
|
|
96
|
+
borderStyle: "round",
|
|
97
|
+
borderColor: "blue",
|
|
98
|
+
title: "š¤ You",
|
|
99
|
+
titleAlignment: "left",
|
|
100
|
+
});
|
|
101
|
+
console.log(userBox);
|
|
102
|
+
} else {
|
|
103
|
+
// Render markdown for assistant messages
|
|
104
|
+
const renderedContent = marked.parse(msg.content);
|
|
105
|
+
const assistantBox = boxen(renderedContent.trim(), {
|
|
106
|
+
padding: 1,
|
|
107
|
+
margin: { left: 2, bottom: 1 },
|
|
108
|
+
borderStyle: "round",
|
|
109
|
+
borderColor: "green",
|
|
110
|
+
title: "š¤ Assistant",
|
|
111
|
+
titleAlignment: "left",
|
|
112
|
+
});
|
|
113
|
+
console.log(assistantBox);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function saveMessage(token, conversationId, role, content) {
|
|
119
|
+
return await apiClient.addMessage(token, conversationId, role, content);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function getAIResponse(token, conversationId) {
|
|
123
|
+
const spinner = yoctoSpinner({
|
|
124
|
+
text: "AI is thinking...",
|
|
125
|
+
color: "cyan"
|
|
126
|
+
}).start();
|
|
127
|
+
|
|
128
|
+
const dbMessages = await apiClient.getMessages(token, conversationId);
|
|
129
|
+
const aiMessages = dbMessages.map((msg) => ({
|
|
130
|
+
role: msg.role,
|
|
131
|
+
content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
|
|
132
|
+
}));
|
|
133
|
+
|
|
134
|
+
let fullResponse = "";
|
|
135
|
+
let isFirstChunk = true;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const result = await aiService.sendMessage(aiMessages, (chunk) => {
|
|
139
|
+
// Stop spinner on first chunk and show header
|
|
140
|
+
if (isFirstChunk) {
|
|
141
|
+
spinner.stop();
|
|
142
|
+
console.log("\n");
|
|
143
|
+
const header = chalk.green.bold("š¤ Assistant:");
|
|
144
|
+
console.log(header);
|
|
145
|
+
console.log(chalk.gray("ā".repeat(60)));
|
|
146
|
+
isFirstChunk = false;
|
|
147
|
+
}
|
|
148
|
+
process.stdout.write(chunk);
|
|
149
|
+
fullResponse += chunk;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Now render the complete markdown response
|
|
153
|
+
console.log("\n");
|
|
154
|
+
const renderedMarkdown = marked.parse(fullResponse);
|
|
155
|
+
console.log(renderedMarkdown);
|
|
156
|
+
console.log(chalk.gray("ā".repeat(60)));
|
|
157
|
+
console.log("\n");
|
|
158
|
+
|
|
159
|
+
return result.content;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
spinner.error("Failed to get AI response");
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function updateConversationTitle(token, conversationId, userId, userInput, messageCount) {
|
|
167
|
+
if (messageCount === 1) {
|
|
168
|
+
const title = userInput.slice(0, 50) + (userInput.length > 50 ? "..." : "");
|
|
169
|
+
await apiClient.updateTitle(token, conversationId, title);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function chatLoop(token, conversation) {
|
|
174
|
+
const helpBox = boxen(
|
|
175
|
+
`${chalk.gray('⢠Type your message and press Enter')}\n${chalk.gray('⢠Markdown formatting is supported in responses')}\n${chalk.gray('⢠Type "exit" to end conversation')}\n${chalk.gray('⢠Press Ctrl+C to quit anytime')}`,
|
|
176
|
+
{
|
|
177
|
+
padding: 1,
|
|
178
|
+
margin: { bottom: 1 },
|
|
179
|
+
borderStyle: "round",
|
|
180
|
+
borderColor: "gray",
|
|
181
|
+
dimBorder: true,
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
console.log(helpBox);
|
|
186
|
+
|
|
187
|
+
while (true) {
|
|
188
|
+
const userInput = await text({
|
|
189
|
+
message: chalk.blue("š¬ Your message"),
|
|
190
|
+
placeholder: "Type your message...",
|
|
191
|
+
validate(value) {
|
|
192
|
+
if (!value || value.trim().length === 0) {
|
|
193
|
+
return "Message cannot be empty";
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Handle cancellation (Ctrl+C)
|
|
199
|
+
if (isCancel(userInput)) {
|
|
200
|
+
const exitBox = boxen(chalk.yellow("Chat session ended. Goodbye! š"), {
|
|
201
|
+
padding: 1,
|
|
202
|
+
margin: 1,
|
|
203
|
+
borderStyle: "round",
|
|
204
|
+
borderColor: "yellow",
|
|
205
|
+
});
|
|
206
|
+
console.log(exitBox);
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Handle exit command
|
|
211
|
+
if (userInput.toLowerCase() === "exit") {
|
|
212
|
+
const exitBox = boxen(chalk.yellow("Chat session ended. Goodbye! š"), {
|
|
213
|
+
padding: 1,
|
|
214
|
+
margin: 1,
|
|
215
|
+
borderStyle: "round",
|
|
216
|
+
borderColor: "yellow",
|
|
217
|
+
});
|
|
218
|
+
console.log(exitBox);
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Save user message
|
|
223
|
+
await saveMessage(token, conversation.id, "user", userInput);
|
|
224
|
+
|
|
225
|
+
// Get messages count before AI response
|
|
226
|
+
const messages = await apiClient.getMessages(token, conversation.id);
|
|
227
|
+
|
|
228
|
+
// Get AI response with streaming and markdown rendering
|
|
229
|
+
const aiResponse = await getAIResponse(token, conversation.id);
|
|
230
|
+
|
|
231
|
+
// Save AI response
|
|
232
|
+
await saveMessage(token, conversation.id, "assistant", aiResponse);
|
|
233
|
+
|
|
234
|
+
// Update title if first exchange
|
|
235
|
+
await updateConversationTitle(token, conversation.id, conversation.userId, userInput, messages.length);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Main entry point
|
|
240
|
+
export async function startChat(mode = "chat", conversationId = null) {
|
|
241
|
+
try {
|
|
242
|
+
// Display intro banner
|
|
243
|
+
intro(
|
|
244
|
+
boxen(chalk.bold.cyan("š Orbit AI Chat"), {
|
|
245
|
+
padding: 1,
|
|
246
|
+
borderStyle: "double",
|
|
247
|
+
borderColor: "cyan",
|
|
248
|
+
})
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const { user, token } = await getUserFromToken();
|
|
252
|
+
const conversation = await initConversation(token.access_token, user.id, conversationId, mode);
|
|
253
|
+
await chatLoop(token.access_token, conversation);
|
|
254
|
+
|
|
255
|
+
// Display outro
|
|
256
|
+
outro(chalk.green("⨠Thanks for chatting!"));
|
|
257
|
+
} catch (error) {
|
|
258
|
+
const errorBox = boxen(chalk.red(`ā Error: ${error.message}`), {
|
|
259
|
+
padding: 1,
|
|
260
|
+
margin: 1,
|
|
261
|
+
borderStyle: "round",
|
|
262
|
+
borderColor: "red",
|
|
263
|
+
});
|
|
264
|
+
console.log(errorBox);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { getStoredToken } from "../auth/login.js";
|
|
4
|
+
import { getUserFromApi } from "../../api/api-client.js";
|
|
5
|
+
import { select, isCancel } from "@clack/prompts";
|
|
6
|
+
import yoctoSpinner from "yocto-spinner";
|
|
7
|
+
import { startChat } from "../../chat/chat-with-ai.js";
|
|
8
|
+
import { startToolChat } from "../../chat/chat-with-ai-tool.js";
|
|
9
|
+
import { startAgentChat } from "../../chat/chat-with-ai-agent.js";
|
|
10
|
+
|
|
11
|
+
const wakeUpAction = async()=>{
|
|
12
|
+
const token=await getStoredToken();
|
|
13
|
+
if(!token?.access_token){
|
|
14
|
+
console.log(chalk.red("not authenticated please login"));
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const spinner=yoctoSpinner({text:"Fetching user Information....."}).start();
|
|
19
|
+
const user = await getUserFromApi(token.access_token);
|
|
20
|
+
spinner.stop();
|
|
21
|
+
|
|
22
|
+
if(!user){
|
|
23
|
+
console.log("User not found");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
while (true) {
|
|
28
|
+
const choice = await select({
|
|
29
|
+
message: chalk.cyan(`Welcome Back ${user.name}! How can I help you today?`),
|
|
30
|
+
options: [
|
|
31
|
+
{ value: "chat", label: "AI Chat", hint: "Start a conversations with AI" },
|
|
32
|
+
{ value: "tool", label: "Tool calling", hint: "Chat with tools enabled" },
|
|
33
|
+
{ value: "agentic", label: "Agentic Mode", hint: "Build complete apps with AI" },
|
|
34
|
+
{ value: "exit", label: "Exit", hint: "Exit the application" }
|
|
35
|
+
]
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (isCancel(choice) || choice === "exit") {
|
|
39
|
+
console.log(chalk.green("\nGoodbye! See you next time. š\n"));
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
switch (choice) {
|
|
44
|
+
case "chat":
|
|
45
|
+
await startChat("chat");
|
|
46
|
+
break;
|
|
47
|
+
case "tool":
|
|
48
|
+
await startToolChat();
|
|
49
|
+
break;
|
|
50
|
+
case "agentic":
|
|
51
|
+
await startAgentChat();
|
|
52
|
+
break;
|
|
53
|
+
default:
|
|
54
|
+
console.log(chalk.red("Invalid option. Please try again."));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const wakeup = new Command("wakeup")
|
|
60
|
+
.description("Wake up the AI agent")
|
|
61
|
+
.action(wakeUpAction);
|