@bharatpanigrahi/onw-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/package.json +35 -0
- package/src/cli/ai/google-service.js +102 -0
- package/src/cli/chat/chat-with-ai-agent.js +209 -0
- package/src/cli/chat/chat-with-ai-tool.js +325 -0
- package/src/cli/chat/chat-with-ai.js +242 -0
- package/src/cli/commands/ai/wakeUp.js +83 -0
- package/src/cli/commands/auth/login.js +287 -0
- package/src/cli/main.js +45 -0
- package/src/config/agent.config.js +163 -0
- package/src/config/google.config.js +7 -0
- package/src/config/tool.config.js +105 -0
- package/src/index.js +36 -0
- package/src/lib/auth.js +34 -0
- package/src/lib/db.js +8 -0
- package/src/lib/token.js +85 -0
- package/src/service/chat.service.js +104 -0
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bharatpanigrahi/onw-cli",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "A CLI tool for Claude AI",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"src/"
|
|
8
|
+
],
|
|
9
|
+
"bin": {
|
|
10
|
+
"claude": "./src/cli/main.js"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"start": "node src/index.js"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"claude",
|
|
17
|
+
"ai",
|
|
18
|
+
"cli"
|
|
19
|
+
],
|
|
20
|
+
"author": "bharatpanigrahi",
|
|
21
|
+
"license": "ISC",
|
|
22
|
+
"type": "module",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@ai-sdk/google": "^3.0.53",
|
|
25
|
+
"@clack/prompts": "^1.1.0",
|
|
26
|
+
"ai": "^6.0.141",
|
|
27
|
+
"boxen": "^8.0.1",
|
|
28
|
+
"chalk": "^5.6.2",
|
|
29
|
+
"marked": "^15.0.12",
|
|
30
|
+
"marked-terminal": "^7.3.0",
|
|
31
|
+
"open": "^11.0.0",
|
|
32
|
+
"yocto-spinner": "^1.1.0",
|
|
33
|
+
"zod": "^4.3.6"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { google } from "@ai-sdk/google";
|
|
2
|
+
import { generateObject, streamText, tool } from "ai";
|
|
3
|
+
import { config } from "../../config/google.config.js";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
|
|
6
|
+
export class AIService {
|
|
7
|
+
constructor() {
|
|
8
|
+
if (!config.googleApiKey) {
|
|
9
|
+
throw new Error("GOOGLE_GENERATIVE_AI_API_KEY us not set in env");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
this.model = google(config.model, {
|
|
13
|
+
apiKey: config.googleApiKey,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async sendMessage(message, onChunk, tools = undefined, onToolCall = null) {
|
|
18
|
+
try {
|
|
19
|
+
const streamConfig = {
|
|
20
|
+
model: this.model,
|
|
21
|
+
messages: message,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
if (tools && Object.keys(tools).length > 0) {
|
|
25
|
+
streamConfig.tools = tools;
|
|
26
|
+
streamConfig.maxSteps = 5; // Allow Up to 5 tool call steps
|
|
27
|
+
|
|
28
|
+
console.log(
|
|
29
|
+
chalk.gray(`[DEBUG] Tools enabled: ${Object.keys(tools).join(", ")}`),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const result = streamText(streamConfig);
|
|
34
|
+
|
|
35
|
+
let fullResponse = "";
|
|
36
|
+
for await (const chunk of result.textStream) {
|
|
37
|
+
fullResponse += chunk;
|
|
38
|
+
if (onChunk) onChunk(chunk);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const fullResult = result;
|
|
42
|
+
|
|
43
|
+
const toolCalls = [];
|
|
44
|
+
const toolResults = [];
|
|
45
|
+
|
|
46
|
+
if (fullResult.steps && Array.isArray(fullResult.steps)) {
|
|
47
|
+
for (const step of fullResult.steps) {
|
|
48
|
+
if (step.toolCalls && step.toolCalls.length > 0) {
|
|
49
|
+
for (const toolCall of step.toolCalls) {
|
|
50
|
+
toolCalls.push(toolCall);
|
|
51
|
+
if (onToolCall) {
|
|
52
|
+
onToolCall(toolCall);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (step.toolResults && step.toolResults.length > 0) {
|
|
58
|
+
toolResults.push(...step.toolResults);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
content: fullResponse,
|
|
65
|
+
finishResponse: fullResult.finishReason,
|
|
66
|
+
usage: fullResult.usage,
|
|
67
|
+
toolCalls,
|
|
68
|
+
toolResults,
|
|
69
|
+
steps: fullResult.steps,
|
|
70
|
+
};
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(chalk.red("AI Service Error: ", error?.message));
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async getMessage(message, tools = undefined) {
|
|
78
|
+
let fullResponse = "";
|
|
79
|
+
const result = await this.sendMessage(message, (chunk) => {
|
|
80
|
+
fullResponse += chunk;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return result.content;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async generatedStructure(schema, prompt) {
|
|
87
|
+
try {
|
|
88
|
+
const result = await generateObject({
|
|
89
|
+
model: this.model,
|
|
90
|
+
schema,
|
|
91
|
+
prompt,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return result.object;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error(
|
|
97
|
+
chalk.red("AI Structured Generation Error: ", error?.message),
|
|
98
|
+
);
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import boxen from "boxen";
|
|
3
|
+
import yoctoSpinner from "yocto-spinner";
|
|
4
|
+
import { text, isCancel, cancel, intro, outro, confirm } from "@clack/prompts";
|
|
5
|
+
import { AIService } from "../ai/google-service.js";
|
|
6
|
+
import { ChatService } from "../../service/chat.service.js";
|
|
7
|
+
import { getStoredToken } from "../../lib/token.js";
|
|
8
|
+
import prisma from "../../lib/db.js";
|
|
9
|
+
import { generateApplication } from "../../config/agent.config.js";
|
|
10
|
+
import { saveMessage } from "./chat-with-ai.js";
|
|
11
|
+
|
|
12
|
+
const aiService = new AIService();
|
|
13
|
+
const chatService = new ChatService();
|
|
14
|
+
|
|
15
|
+
async function getUserFromToken() {
|
|
16
|
+
const token = await getStoredToken();
|
|
17
|
+
if (!token?.access_token) {
|
|
18
|
+
throw new Error("Not Authenticated Please run 'claude login' first.");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const spinner = yoctoSpinner({ text: "Authenticating..." }).start();
|
|
22
|
+
const user = await prisma.user.findFirst({
|
|
23
|
+
where: {
|
|
24
|
+
sessions: {
|
|
25
|
+
some: {
|
|
26
|
+
token: token?.access_token,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
if (!user) {
|
|
32
|
+
spinner.error("User not found");
|
|
33
|
+
throw new Error("User not found please login again.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
spinner.success(`Welcome back, ${user.name}!`);
|
|
37
|
+
return user;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function initConversation(userId, conversationId = null) {
|
|
41
|
+
const spinner = yoctoSpinner({ text: "Loading conversation..." }).start();
|
|
42
|
+
const conversation = await chatService.getOrCreateConversations(
|
|
43
|
+
userId,
|
|
44
|
+
conversationId,
|
|
45
|
+
"agent",
|
|
46
|
+
);
|
|
47
|
+
spinner.success("Conversation loaded");
|
|
48
|
+
const conversationInfo = boxen(
|
|
49
|
+
`${chalk.bold("Conversation ID:")} ${conversation.id}\n` +
|
|
50
|
+
`${chalk.gray("ID:")} ${conversation.id}\n` +
|
|
51
|
+
`${chalk.gray("Mode:")} ${chalk.magenta("Agent (Code Generator)")}\n` +
|
|
52
|
+
`${chalk.cyan("Working Directory:")} ${process.cwd()}`,
|
|
53
|
+
{
|
|
54
|
+
padding: 1,
|
|
55
|
+
margin: { top: 1, bottom: 1 },
|
|
56
|
+
borderStyle: "round",
|
|
57
|
+
borderColor: "magenta",
|
|
58
|
+
title: "🤖 Agent Mode",
|
|
59
|
+
titleAlignment: "center",
|
|
60
|
+
},
|
|
61
|
+
);
|
|
62
|
+
console.log(conversationInfo);
|
|
63
|
+
return conversation;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function agentLoop(conversation) {
|
|
67
|
+
const helpBox = boxen(
|
|
68
|
+
`${chalk.cyan.bold("What can the agent do?")}\n\n` +
|
|
69
|
+
`${chalk.gray("• Generate complete applications from descriptions")}\n` +
|
|
70
|
+
`${chalk.gray("• Create all necessary files and folders")}\n` +
|
|
71
|
+
`${chalk.gray("• Include setup instructions and commands")}\n` +
|
|
72
|
+
`${chalk.gray("• Generate production-ready code")}\n\n` +
|
|
73
|
+
`${chalk.yellow.bold("Examples:")}\n` +
|
|
74
|
+
`${chalk.white('• "Build a todo app with React and Tailwind"')}\n` +
|
|
75
|
+
`${chalk.white('• "Create a REST API with Express and MongoDB"')}\n` +
|
|
76
|
+
`${chalk.white('• "Make a weather app using OpenWeatherMap API"')}\n\n` +
|
|
77
|
+
`${chalk.gray('Type "exit" to end the session')}`,
|
|
78
|
+
{
|
|
79
|
+
padding: 1,
|
|
80
|
+
margin: { bottom: 1 },
|
|
81
|
+
borderStyle: "round",
|
|
82
|
+
borderColor: "cyan",
|
|
83
|
+
title: "💡 Agent Instructions",
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
console.log(helpBox);
|
|
88
|
+
|
|
89
|
+
while (true) {
|
|
90
|
+
const userInput = await text({
|
|
91
|
+
message: chalk.magenta("You: "),
|
|
92
|
+
placeholder: "Type your message here...",
|
|
93
|
+
validate: (value) => {
|
|
94
|
+
if (!value || value.trim().length === 0) {
|
|
95
|
+
return "Message cannot be empty";
|
|
96
|
+
}
|
|
97
|
+
if (value.trim().length < 10) {
|
|
98
|
+
return "Please provide a more detailed description (at least 10 characters)";
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (isCancel(userInput)) {
|
|
104
|
+
console.log(chalk.yellow("\n Agent session cancelled\n"));
|
|
105
|
+
process.exit(0);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (userInput.toLowerCase() === "exit") {
|
|
109
|
+
console.log(chalk.yellow("\n Agent session ended\n"));
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const userBox = boxen(chalk.white(userInput), {
|
|
114
|
+
padding: 1,
|
|
115
|
+
margin: { top: 1, bottom: 1 },
|
|
116
|
+
borderStyle: "round",
|
|
117
|
+
borderColor: "blue",
|
|
118
|
+
title: "👤 Your Request",
|
|
119
|
+
titleAlignment: "left",
|
|
120
|
+
});
|
|
121
|
+
console.log(userBox);
|
|
122
|
+
|
|
123
|
+
await saveMessage(conversation.id, "user", userInput);
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const result = await generateApplication(
|
|
127
|
+
userInput,
|
|
128
|
+
aiService,
|
|
129
|
+
process.cwd(),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
if (result && result.success) {
|
|
133
|
+
const responseMessage =
|
|
134
|
+
`Generated application: ${result.folderName}\n` +
|
|
135
|
+
`Files created: ${result.files.length}\n` +
|
|
136
|
+
`Location: ${result.appDir}\n\n` +
|
|
137
|
+
`Setup commands:\n${result.commands.join("\n")}`;
|
|
138
|
+
|
|
139
|
+
await saveMessage(conversation.id, "assistant", responseMessage);
|
|
140
|
+
|
|
141
|
+
// Ask if user wants to generate another app
|
|
142
|
+
const continuePrompt = await confirm({
|
|
143
|
+
message: chalk.cyan(
|
|
144
|
+
"Would you like to generate another application?",
|
|
145
|
+
),
|
|
146
|
+
initialValue: false,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
if (isCancel(continuePrompt) || !continuePrompt) {
|
|
150
|
+
console.log(chalk.yellow("\n Great! Check your new application!\n"));
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
throw new Error("Generation returned no result");
|
|
155
|
+
}
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.log(chalk.red(`\n❌ Error: ${error.message}\n`));
|
|
158
|
+
|
|
159
|
+
await saveMessage(
|
|
160
|
+
conversation.id,
|
|
161
|
+
"assistant",
|
|
162
|
+
`Error: ${error.message}`,
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const retry = await confirm({
|
|
166
|
+
message: chalk.cyan("Would you like to try again?"),
|
|
167
|
+
initialValue: true,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
if (isCancel(retry) || !retry) {
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function startAgentChat(conversationId = null) {
|
|
178
|
+
try {
|
|
179
|
+
intro(
|
|
180
|
+
boxen(
|
|
181
|
+
chalk.bold.magenta("Claude AI - Agent Mode\n\n") +
|
|
182
|
+
chalk.gray("Autonomous Application Generator"),
|
|
183
|
+
{
|
|
184
|
+
padding: 1,
|
|
185
|
+
borderStyle: "double",
|
|
186
|
+
borderColor: "magenta",
|
|
187
|
+
},
|
|
188
|
+
),
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
const user = await getUserFromToken();
|
|
192
|
+
const shouldContinue = await confirm({
|
|
193
|
+
message: chalk.yellow(
|
|
194
|
+
"The agent will create files and folders in the current directory. Do you want to continue?",
|
|
195
|
+
),
|
|
196
|
+
initialValue: true,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
if (isCancel(shouldContinue) || !shouldContinue) {
|
|
200
|
+
cancel(chalk.yellow("Agent mode cancelled"));
|
|
201
|
+
process.exit(0);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const conversation = await initConversation(user.id, conversationId);
|
|
205
|
+
await agentLoop(conversation);
|
|
206
|
+
|
|
207
|
+
outro(chalk.green.bold("\n Thanks for using Agent Mode!"));
|
|
208
|
+
} catch (error) {}
|
|
209
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import boxen from "boxen";
|
|
3
|
+
import {
|
|
4
|
+
text,
|
|
5
|
+
isCancel,
|
|
6
|
+
cancel,
|
|
7
|
+
intro,
|
|
8
|
+
outro,
|
|
9
|
+
multiselect,
|
|
10
|
+
} from "@clack/prompts";
|
|
11
|
+
import yoctoSpinner from "yocto-spinner";
|
|
12
|
+
import { marked } from "marked";
|
|
13
|
+
import { markedTerminal } from "marked-terminal";
|
|
14
|
+
import { AIService } from "../ai/google-service.js";
|
|
15
|
+
import { ChatService } from "../../service/chat.service.js";
|
|
16
|
+
import { getStoredToken } from "../../lib/token.js";
|
|
17
|
+
import prisma from "../../lib/db.js";
|
|
18
|
+
import {
|
|
19
|
+
avaiableTools,
|
|
20
|
+
enableTools,
|
|
21
|
+
getEnabledToolNames,
|
|
22
|
+
getEnabledTools,
|
|
23
|
+
resetTools,
|
|
24
|
+
} from "../../config/tool.config.js";
|
|
25
|
+
import {
|
|
26
|
+
displayMessages,
|
|
27
|
+
saveMessage,
|
|
28
|
+
updateConversationTitle,
|
|
29
|
+
} from "./chat-with-ai.js";
|
|
30
|
+
|
|
31
|
+
marked.use(
|
|
32
|
+
markedTerminal({
|
|
33
|
+
code: chalk.cyan,
|
|
34
|
+
blockquote: chalk.gray.italic,
|
|
35
|
+
heading: chalk.green.bold,
|
|
36
|
+
firstHeading: chalk.magenta.underline.bold,
|
|
37
|
+
hr: chalk.reset,
|
|
38
|
+
listitem: chalk.reset,
|
|
39
|
+
list: chalk.reset,
|
|
40
|
+
paragraph: chalk.reset,
|
|
41
|
+
strong: chalk.bold,
|
|
42
|
+
em: chalk.italic,
|
|
43
|
+
codespan: chalk.yellow.bgBlack,
|
|
44
|
+
del: chalk.dim.gray.strikethrough,
|
|
45
|
+
link: chalk.blue.underline,
|
|
46
|
+
href: chalk.blue.underline,
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const aiService = new AIService();
|
|
51
|
+
const chatService = new ChatService();
|
|
52
|
+
|
|
53
|
+
async function getUserFromToken() {
|
|
54
|
+
const token = await getStoredToken();
|
|
55
|
+
if (!token?.access_token) {
|
|
56
|
+
throw new Error("Not Authenticated Please run 'claude login' first.");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const spinner = yoctoSpinner({ text: "Authenticating..." }).start();
|
|
60
|
+
const user = await prisma.user.findFirst({
|
|
61
|
+
where: {
|
|
62
|
+
sessions: {
|
|
63
|
+
some: {
|
|
64
|
+
token: token?.access_token,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
if (!user) {
|
|
70
|
+
spinner.error("User not found");
|
|
71
|
+
throw new Error("User not found please login again.");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
spinner.success(`Welcome back, ${user.name}!`);
|
|
75
|
+
return user;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function selectTools() {
|
|
79
|
+
const toolOptions = avaiableTools.map((tool) => ({
|
|
80
|
+
value: tool.id,
|
|
81
|
+
label: tool.name,
|
|
82
|
+
hint: tool.description,
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
const selectedTools = await multiselect({
|
|
86
|
+
message: chalk.cyan(
|
|
87
|
+
"Select tools to enable (Space to select, Enter to confirm): ",
|
|
88
|
+
),
|
|
89
|
+
options: toolOptions,
|
|
90
|
+
required: false,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (isCancel(selectedTools)) {
|
|
94
|
+
cancel(chalk.yellow("Tool selection cancelled."));
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
enableTools(selectedTools);
|
|
99
|
+
|
|
100
|
+
if (selectedTools.length === 0) {
|
|
101
|
+
console.log(
|
|
102
|
+
chalk.yellow("\n⚠️ No tools selected. AI will work without tools.\n"),
|
|
103
|
+
);
|
|
104
|
+
} else {
|
|
105
|
+
const toolsBox = boxen(
|
|
106
|
+
chalk.green(
|
|
107
|
+
`Enabled tools:\n${selectedTools
|
|
108
|
+
.map((id) => {
|
|
109
|
+
const tool = avaiableTools.find((t) => t.id === id);
|
|
110
|
+
return ` - ${tool.name}`;
|
|
111
|
+
})
|
|
112
|
+
.join("\n")}`,
|
|
113
|
+
),
|
|
114
|
+
{
|
|
115
|
+
padding: 1,
|
|
116
|
+
margin: { top: 1, bottom: 1 },
|
|
117
|
+
borderStyle: "round",
|
|
118
|
+
borderColor: "green",
|
|
119
|
+
title: "Active Tools",
|
|
120
|
+
titleAlignment: "center",
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
console.log(toolsBox);
|
|
124
|
+
}
|
|
125
|
+
return selectedTools.length > 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function getAIResponse(conversationId) {
|
|
129
|
+
const spinner = yoctoSpinner({
|
|
130
|
+
text: "Claude is thinking...",
|
|
131
|
+
color: "cyan",
|
|
132
|
+
}).start();
|
|
133
|
+
const dbMessages = await chatService.getMessages(conversationId);
|
|
134
|
+
const aiMessages = await chatService.formatMessagesForAI(dbMessages);
|
|
135
|
+
|
|
136
|
+
const tools = getEnabledTools();
|
|
137
|
+
|
|
138
|
+
let fullResponse = "";
|
|
139
|
+
let isFirstChunk = true;
|
|
140
|
+
const toolCallIsDetected = [];
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const result = await aiService.sendMessage(
|
|
144
|
+
aiMessages,
|
|
145
|
+
(chunk) => {
|
|
146
|
+
if (isFirstChunk) {
|
|
147
|
+
spinner.stop();
|
|
148
|
+
console.log("\n");
|
|
149
|
+
const header = chalk.green.bold("🤖 Claude:");
|
|
150
|
+
console.log(header);
|
|
151
|
+
console.log(chalk.gray("-".repeat(60)));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
fullResponse += chunk;
|
|
155
|
+
},
|
|
156
|
+
tools,
|
|
157
|
+
(toolCall) => {
|
|
158
|
+
toolCallIsDetected.push(toolCall);
|
|
159
|
+
},
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
if (toolCallIsDetected.length > 0) {
|
|
163
|
+
console.log("\n");
|
|
164
|
+
const toolCallBox = boxen(
|
|
165
|
+
toolCallIsDetected
|
|
166
|
+
.map(
|
|
167
|
+
(tc) =>
|
|
168
|
+
`${chalk.cyan("Tool:")} ${tc.toolName}\n${chalk.gray("Args:")} ${JSON.stringify(tc.args, null, 2)}`,
|
|
169
|
+
)
|
|
170
|
+
.join("\n\n"),
|
|
171
|
+
{
|
|
172
|
+
padding: 1,
|
|
173
|
+
margin: 1,
|
|
174
|
+
borderStyle: "round",
|
|
175
|
+
borderColor: "cyan",
|
|
176
|
+
title: "Tool Calls",
|
|
177
|
+
},
|
|
178
|
+
);
|
|
179
|
+
console.log(toolCallBox);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log("\n");
|
|
183
|
+
const renderedMarkdown = marked.parse(fullResponse);
|
|
184
|
+
console.log(renderedMarkdown);
|
|
185
|
+
console.log(chalk.gray("-".repeat(60)));
|
|
186
|
+
console.log("\n");
|
|
187
|
+
return result.content;
|
|
188
|
+
} catch (error) {
|
|
189
|
+
spinner.error("Failed to get AI response");
|
|
190
|
+
throw error;
|
|
191
|
+
} finally {
|
|
192
|
+
spinner.stop();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function initCnnversation(userId, conversationId = null, mode = "tool") {
|
|
197
|
+
const spinner = yoctoSpinner({ text: "Loading conversation..." }).start();
|
|
198
|
+
|
|
199
|
+
const conversation = await chatService.getOrCreateConversations(
|
|
200
|
+
userId,
|
|
201
|
+
conversationId,
|
|
202
|
+
mode,
|
|
203
|
+
);
|
|
204
|
+
spinner.success("Conversation loaded");
|
|
205
|
+
|
|
206
|
+
const enabledToolNames = getEnabledToolNames();
|
|
207
|
+
const toolsDisplay =
|
|
208
|
+
enabledToolNames.length > 0
|
|
209
|
+
? `\n${chalk.gray("Active Tools:")} ${enabledToolNames.join(", ")}`
|
|
210
|
+
: `\n${chalk.gray("No tools enabled.")}`;
|
|
211
|
+
|
|
212
|
+
const conversationInfo = boxen(
|
|
213
|
+
`${chalk.bold("Conversation")}: ${conversation.title}\n${chalk.gray("ID: " + conversation.id)}\n${chalk.gray("Mode: " + conversation.mode)}${toolsDisplay}`,
|
|
214
|
+
{
|
|
215
|
+
padding: 1,
|
|
216
|
+
margin: { top: 1, bottom: 1 },
|
|
217
|
+
borderStyle: "round",
|
|
218
|
+
borderColor: "cyan",
|
|
219
|
+
title: "💭 Tool Calling Session",
|
|
220
|
+
titleAlignment: "center",
|
|
221
|
+
},
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
console.log(conversationInfo);
|
|
225
|
+
|
|
226
|
+
if (conversation?.messages?.length > 0) {
|
|
227
|
+
console.log(chalk.yellow("📜 Previous messages:\n"));
|
|
228
|
+
displayMessages(conversation.messages);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return conversation;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function chatLoop(conversation) {
|
|
235
|
+
const enabledToolNames = getEnabledToolNames();
|
|
236
|
+
const helpBox = boxen(
|
|
237
|
+
`${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 at any time")}`,
|
|
238
|
+
{
|
|
239
|
+
padding: 1,
|
|
240
|
+
margin: { bottom: 1 },
|
|
241
|
+
borderStyle: "round",
|
|
242
|
+
borderColor: "gray",
|
|
243
|
+
dimBorder: true,
|
|
244
|
+
},
|
|
245
|
+
);
|
|
246
|
+
console.log(helpBox);
|
|
247
|
+
|
|
248
|
+
while (true) {
|
|
249
|
+
const userInput = await text({
|
|
250
|
+
message: chalk.blue("You: "),
|
|
251
|
+
placeholder: "Type your message here...",
|
|
252
|
+
validate: (value) => {
|
|
253
|
+
if (!value || value.trim().length === 0) {
|
|
254
|
+
return "Message cannot be empty";
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
if (isCancel(userInput)) {
|
|
260
|
+
const exitBox = boxen(chalk.yellow("Chat session ended. Goodbye! 👋"), {
|
|
261
|
+
padding: 1,
|
|
262
|
+
margin: 1,
|
|
263
|
+
borderStyle: "round",
|
|
264
|
+
borderColor: "yellow",
|
|
265
|
+
});
|
|
266
|
+
console.log(exitBox);
|
|
267
|
+
process.exit(0);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (userInput.toLowerCase() === "exit") {
|
|
271
|
+
const exitBox = boxen(chalk.yellow("Chat session ended. Goodbye! 👋"), {
|
|
272
|
+
padding: 1,
|
|
273
|
+
margin: 1,
|
|
274
|
+
borderStyle: "round",
|
|
275
|
+
borderColor: "yellow",
|
|
276
|
+
});
|
|
277
|
+
console.log(exitBox);
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
await saveMessage(conversation.id, "user", userInput);
|
|
282
|
+
const messages = await chatService.getMessages(conversation.id);
|
|
283
|
+
|
|
284
|
+
const aiResponse = await getAIResponse(conversation.id);
|
|
285
|
+
await saveMessage(conversation.id, "assistant", aiResponse);
|
|
286
|
+
|
|
287
|
+
await updateConversationTitle(conversation.id, userInput, messages.length);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export async function startToolChat(conversationId = null) {
|
|
292
|
+
try {
|
|
293
|
+
intro(
|
|
294
|
+
boxen(chalk.bold.cyan("Claude AI - Tool Calling Mode"), {
|
|
295
|
+
padding: 1,
|
|
296
|
+
borderStyle: "double",
|
|
297
|
+
borderColor: "cyan",
|
|
298
|
+
}),
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
const user = await getUserFromToken();
|
|
302
|
+
await selectTools();
|
|
303
|
+
|
|
304
|
+
const conversation = await initCnnversation(
|
|
305
|
+
user.id,
|
|
306
|
+
conversationId,
|
|
307
|
+
"tool",
|
|
308
|
+
);
|
|
309
|
+
await chatLoop(conversation);
|
|
310
|
+
resetTools();
|
|
311
|
+
|
|
312
|
+
outro(chalk.green.bold("Thanks for using tools. Goodbye!"));
|
|
313
|
+
} catch (error) {
|
|
314
|
+
const errorBox = boxen(chalk.red(`❌ Error: ${error?.message}`), {
|
|
315
|
+
padding: 1,
|
|
316
|
+
margin: 1,
|
|
317
|
+
borderStyle: "round",
|
|
318
|
+
borderColor: "red",
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
console.log(errorBox);
|
|
322
|
+
resetTools();
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
}
|