@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
|
@@ -2,10 +2,9 @@ import chalk from "chalk";
|
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import yoctoSpinner from "yocto-spinner";
|
|
4
4
|
import { getStoredToken } from "../../../lib/token.js";
|
|
5
|
-
import prisma from "../../../lib/db.js";
|
|
6
|
-
import { ensureDbConnection } from "../../../lib/dbHealth.js";
|
|
7
5
|
import { select } from "@clack/prompts";
|
|
8
6
|
import {openGithub , openLinkedin , openLeetcode , openGmail , openWhatsApp} from "../../../cli/generalApp/Apps.js"
|
|
7
|
+
import { apiRequestSafe } from "../../utils/apiClient.js";
|
|
9
8
|
|
|
10
9
|
const openAppAction = async () => {
|
|
11
10
|
const token = await getStoredToken();
|
|
@@ -15,29 +14,13 @@ const openAppAction = async () => {
|
|
|
15
14
|
return;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
|
-
const dbOk = await ensureDbConnection();
|
|
19
|
-
if (!dbOk) return;
|
|
20
|
-
|
|
21
17
|
const spinner = yoctoSpinner({ text: "Fetching user information..." });
|
|
22
18
|
spinner.start();
|
|
23
19
|
|
|
24
20
|
let user;
|
|
25
21
|
try {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
sessions: {
|
|
29
|
-
some: {
|
|
30
|
-
token: token.access_token,
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
select: {
|
|
35
|
-
id: true,
|
|
36
|
-
name: true,
|
|
37
|
-
email: true,
|
|
38
|
-
image: true,
|
|
39
|
-
},
|
|
40
|
-
});
|
|
22
|
+
const result = await apiRequestSafe("/api/cli/me");
|
|
23
|
+
user = result?.user;
|
|
41
24
|
} finally {
|
|
42
25
|
spinner.stop();
|
|
43
26
|
}
|
|
@@ -2,46 +2,35 @@ import chalk from "chalk";
|
|
|
2
2
|
import {Command} from "commander";
|
|
3
3
|
import yoctoSpinner from "yocto-spinner";
|
|
4
4
|
import {getStoredToken} from "../../../lib/token.js"
|
|
5
|
-
import prisma from "../../../lib/db.js"
|
|
6
|
-
import { ensureDbConnection } from "../../../lib/dbHealth.js";
|
|
7
5
|
import {select} from "@clack/prompts";
|
|
8
6
|
import {startChat} from "../../../cli/chat/chat-with-ai.js";
|
|
9
7
|
import {startToolChat} from "../../../cli/chat/chat-with-ai-tools.js";
|
|
10
8
|
import {startAgentChat} from "../../../cli/chat/chat-with-ai-agent.js";
|
|
9
|
+
import { apiRequestSafe } from "../../utils/apiClient.js";
|
|
10
|
+
import { requireGeminiApiKey } from "../../../lib/orbitalConfig.js";
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
const wakeUpAction = async()=>{
|
|
14
|
+
try {
|
|
15
|
+
await requireGeminiApiKey();
|
|
16
|
+
} catch {
|
|
17
|
+
console.log(chalk.red("Gemini API key not set. Run: orbital set-key <API_KEY>"));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
14
21
|
const token = await getStoredToken();
|
|
15
22
|
if(!token?.access_token){
|
|
16
23
|
console.log(chalk.red("Not Authenticated. please login"))
|
|
17
24
|
return ;
|
|
18
25
|
}
|
|
19
26
|
|
|
20
|
-
const dbOk = await ensureDbConnection();
|
|
21
|
-
if(!dbOk){
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
27
|
const spinner = yoctoSpinner({text: "Fetching user information ..."})
|
|
26
28
|
spinner.start()
|
|
27
29
|
|
|
28
30
|
let user;
|
|
29
31
|
try{
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
sessions : {
|
|
33
|
-
some : {
|
|
34
|
-
token : token.access_token
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
},
|
|
38
|
-
select : {
|
|
39
|
-
id : true ,
|
|
40
|
-
name : true ,
|
|
41
|
-
email : true ,
|
|
42
|
-
image : true
|
|
43
|
-
}
|
|
44
|
-
});
|
|
32
|
+
const result = await apiRequestSafe("/api/cli/me");
|
|
33
|
+
user = result?.user;
|
|
45
34
|
}
|
|
46
35
|
finally{
|
|
47
36
|
spinner.stop();
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { requireAuth } from "../../../lib/token.js";
|
|
4
|
+
import { API_BASE } from "../../../config/api.js";
|
|
4
5
|
|
|
5
|
-
const DEFAULT_SERVER_URL =
|
|
6
|
+
const DEFAULT_SERVER_URL = API_BASE;
|
|
6
7
|
|
|
7
8
|
export const whoAmIAction = async (cmdOptions = {})=>{
|
|
8
9
|
const token = await requireAuth();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "../../../config/env.js";
|
|
1
2
|
import { cancel, confirm, intro, outro, isCancel } from "@clack/prompts";
|
|
2
3
|
import chalk from "chalk";
|
|
3
4
|
import { Command } from "commander";
|
|
@@ -11,24 +12,36 @@ import { deviceAuthorizationClient } from "better-auth/client/plugins";
|
|
|
11
12
|
import { logger } from "better-auth";
|
|
12
13
|
import { fileURLToPath } from "url";
|
|
13
14
|
import { getStoredToken, isTokenExpired, storeToken ,TOKEN_FILE } from "../../../lib/token.js";
|
|
15
|
+
import { API_BASE } from "../../../config/api.js";
|
|
16
|
+
import { apiRequestSafe } from "../../utils/apiClient.js";
|
|
17
|
+
import { requireGeminiApiKey } from "../../../lib/orbitalConfig.js";
|
|
14
18
|
|
|
15
19
|
|
|
16
20
|
|
|
17
21
|
const __filename = fileURLToPath(import.meta.url);
|
|
18
22
|
const __dirname = path.dirname(__filename);
|
|
19
23
|
|
|
20
|
-
const URL =
|
|
21
|
-
|
|
24
|
+
const URL = API_BASE;
|
|
25
|
+
|
|
26
|
+
const resolveClientId = async (cliClientId) => {
|
|
27
|
+
const resolved = (cliClientId || "").trim();
|
|
28
|
+
if (resolved.length > 0) return resolved;
|
|
29
|
+
|
|
30
|
+
const response = await apiRequestSafe("/auth/github/client-id", {
|
|
31
|
+
method: "GET",
|
|
32
|
+
requireAuth: false,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const clientId = typeof response?.client_id === "string" ? response.client_id.trim() : "";
|
|
36
|
+
return clientId.length > 0 ? clientId : undefined;
|
|
37
|
+
};
|
|
22
38
|
|
|
23
39
|
|
|
24
40
|
export const loginAction = async (cmdOptions) => {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
"Run: orbital setkey <your-gemini-api-key>\nThen run: orbital login",
|
|
30
|
-
),
|
|
31
|
-
);
|
|
41
|
+
try {
|
|
42
|
+
await requireGeminiApiKey();
|
|
43
|
+
} catch {
|
|
44
|
+
console.log(chalk.red("Gemini API key not set. Run: orbital set-key <API_KEY>"));
|
|
32
45
|
process.exit(1);
|
|
33
46
|
}
|
|
34
47
|
|
|
@@ -43,7 +56,17 @@ export const loginAction = async (cmdOptions) => {
|
|
|
43
56
|
});
|
|
44
57
|
|
|
45
58
|
const serverUrl = options.serverUrl || URL;
|
|
46
|
-
const clientId = options.clientId
|
|
59
|
+
const clientId = await resolveClientId(options.clientId);
|
|
60
|
+
|
|
61
|
+
if (!clientId) {
|
|
62
|
+
console.error(chalk.red("GitHub OAuth client ID is not available from the server."));
|
|
63
|
+
console.log(
|
|
64
|
+
chalk.gray(
|
|
65
|
+
"Make sure the backend is deployed and configured with GITHUB_CLIENT_ID.",
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
47
70
|
|
|
48
71
|
intro(chalk.bold("Auth CLI Login"));
|
|
49
72
|
|
|
@@ -238,5 +261,5 @@ const pollForToken = async (
|
|
|
238
261
|
export const login = new Command("login")
|
|
239
262
|
.description("Login to Better Auth")
|
|
240
263
|
.option("--server-url <url>", "The Better Auth server URL", URL)
|
|
241
|
-
.option("--client-id <id>", "The OAuth client ID"
|
|
264
|
+
.option("--client-id <id>", "The OAuth client ID")
|
|
242
265
|
.action(loginAction);
|
|
@@ -1,19 +1,26 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
-
import { setGeminiApiKey
|
|
3
|
+
import { setGeminiApiKey } from "../../../lib/orbitalConfig.js";
|
|
4
|
+
import { getCredentialServiceName } from "../../../lib/credentialStore.js";
|
|
4
5
|
|
|
5
6
|
const setKeyAction = async (apiKey) => {
|
|
6
7
|
try {
|
|
7
8
|
await setGeminiApiKey(apiKey);
|
|
8
9
|
console.log(chalk.green("Gemini API key saved."));
|
|
9
|
-
console.log(
|
|
10
|
+
console.log(
|
|
11
|
+
chalk.gray(
|
|
12
|
+
`Stored securely in your OS credential manager (service: ${getCredentialServiceName()}).`
|
|
13
|
+
)
|
|
14
|
+
);
|
|
10
15
|
} catch (err) {
|
|
11
16
|
console.log(chalk.red("Failed to save key:"), err?.message || err);
|
|
12
17
|
process.exit(1);
|
|
13
18
|
}
|
|
14
19
|
};
|
|
15
20
|
|
|
16
|
-
export const setkey = new Command("
|
|
17
|
-
.description("
|
|
18
|
-
.argument("<
|
|
21
|
+
export const setkey = new Command("set-key")
|
|
22
|
+
.description("Store your Gemini API key securely (keytar)")
|
|
23
|
+
.argument("<API_KEY>", "Your Gemini API key")
|
|
24
|
+
.alias("set")
|
|
25
|
+
.alias("setkey")
|
|
19
26
|
.action(setKeyAction);
|
package/server/src/cli/main.js
CHANGED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { API_BASE } from "../../config/api.js";
|
|
3
|
+
import { getStoredToken, requireAuth } from "../../lib/token.js";
|
|
4
|
+
|
|
5
|
+
const buildUrl = (path) => {
|
|
6
|
+
const normalized = path.startsWith("/") ? path : `/${path}`;
|
|
7
|
+
return `${API_BASE}${normalized}`;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const getAuthHeader = async (isRequired) => {
|
|
11
|
+
const token = isRequired ? await requireAuth() : await getStoredToken();
|
|
12
|
+
if (!token?.access_token) return {};
|
|
13
|
+
|
|
14
|
+
const headerValue = token.token_type
|
|
15
|
+
? `${token.token_type} ${token.access_token}`
|
|
16
|
+
: `Bearer ${token.access_token}`;
|
|
17
|
+
|
|
18
|
+
return { Authorization: headerValue };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export const apiRequest = async (
|
|
22
|
+
path,
|
|
23
|
+
{ method = "GET", body, requireAuth: isRequired = true } = {}
|
|
24
|
+
) => {
|
|
25
|
+
const headers = {
|
|
26
|
+
"content-type": "application/json",
|
|
27
|
+
...(await getAuthHeader(isRequired)),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
let response;
|
|
31
|
+
try {
|
|
32
|
+
response = await fetch(buildUrl(path), {
|
|
33
|
+
method,
|
|
34
|
+
headers,
|
|
35
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
36
|
+
});
|
|
37
|
+
} catch (error) {
|
|
38
|
+
throw new Error(`Failed to reach server at ${API_BASE}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const text = await response.text();
|
|
42
|
+
let data = null;
|
|
43
|
+
if (text) {
|
|
44
|
+
try {
|
|
45
|
+
data = JSON.parse(text);
|
|
46
|
+
} catch {
|
|
47
|
+
data = null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
const errorMessage = data?.error || data?.message || response.statusText;
|
|
53
|
+
throw new Error(`${errorMessage} (HTTP ${response.status})`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return data;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const apiRequestSafe = async (...args) => {
|
|
60
|
+
try {
|
|
61
|
+
return await apiRequest(...args);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error(chalk.red(error?.message || "Request failed"));
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
@@ -8,7 +8,7 @@ import { z } from "zod";
|
|
|
8
8
|
const genenateObject = generateObject;
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
const applicationSchema = z.object({
|
|
11
|
+
export const applicationSchema = z.object({
|
|
12
12
|
folderName: z.string().describe("Kebab-Case folder name for the application"),
|
|
13
13
|
description: z.string().describe("Brief description of what was created"),
|
|
14
14
|
files: z.array(
|
|
@@ -35,7 +35,7 @@ const printSystem = (message) => {
|
|
|
35
35
|
console.log(message);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
const displayFileTree = (files, folderName) => {
|
|
38
|
+
export const displayFileTree = (files, folderName) => {
|
|
39
39
|
printSystem(chalk.cyan("📂 Project Structure:"));
|
|
40
40
|
printSystem(chalk.white(`${folderName}/`));
|
|
41
41
|
|
|
@@ -77,7 +77,7 @@ const displayFileTree = (files, folderName) => {
|
|
|
77
77
|
printTree(tree, " ");
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
const createApplicationFiles = async(baseDir , folderName , files)=>{
|
|
80
|
+
export const createApplicationFiles = async(baseDir , folderName , files)=>{
|
|
81
81
|
const appDir = path.join(baseDir , folderName)
|
|
82
82
|
|
|
83
83
|
await fs.mkdir(appDir , {recursive : true});
|
|
@@ -97,18 +97,11 @@ const createApplicationFiles = async(baseDir , folderName , files)=>{
|
|
|
97
97
|
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
printSystem(chalk.magenta("Agent Response: \n"));
|
|
107
|
-
|
|
108
|
-
const { object: application } = await generateObject({
|
|
109
|
-
model: aiService.model,
|
|
110
|
-
schema: applicationSchema,
|
|
111
|
-
prompt: `Create a complete, production-ready application for: ${description}
|
|
100
|
+
export const generateApplicationPlan = async (description, aiService) => {
|
|
101
|
+
const { object: application } = await generateObject({
|
|
102
|
+
model: aiService.model,
|
|
103
|
+
schema: applicationSchema,
|
|
104
|
+
prompt: `Create a complete, production-ready application for: ${description}
|
|
112
105
|
|
|
113
106
|
CRITICAL REQUIREMENTS:
|
|
114
107
|
1. Generate ALL files needed for the application to run
|
|
@@ -125,9 +118,21 @@ Provide:
|
|
|
125
118
|
- A meaningful kebab-case folder name
|
|
126
119
|
- All necessary files with complete content
|
|
127
120
|
- Setup commands (cd folder, npm install, npm run dev, etc.)
|
|
128
|
-
- All dependencies with versions
|
|
121
|
+
- All dependencies with versions`,
|
|
122
|
+
});
|
|
129
123
|
|
|
130
|
-
|
|
124
|
+
return application;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
export const generateApplication = async (description, aiService, cwd = process.cwd()) => {
|
|
129
|
+
try {
|
|
130
|
+
printSystem(chalk.cyan(`\n Agent Mode: Generating your application...\n`));
|
|
131
|
+
printSystem(chalk.gray(`Request: ${description}\n`));
|
|
132
|
+
|
|
133
|
+
printSystem(chalk.magenta("Agent Response: \n"));
|
|
134
|
+
|
|
135
|
+
const application = await generateApplicationPlan(description, aiService);
|
|
131
136
|
|
|
132
137
|
printSystem(chalk.green(`\n Generated: ${application.folderName}`));
|
|
133
138
|
printSystem(chalk.green(`\n Description: ${application.description}`));
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const DEFAULT_API_BASE = "https://smart-cli-based-agent.onrender.com";
|
|
2
|
+
const DEFAULT_FRONTEND_URL = "https://smart-cli-based-agent-t7x4.vercel.app";
|
|
3
|
+
|
|
4
|
+
const stripTrailingSlash = (value) => {
|
|
5
|
+
if (typeof value !== "string") return value;
|
|
6
|
+
return value.replace(/\/+$/, "");
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const API_BASE = stripTrailingSlash(
|
|
10
|
+
process.env.ORBITAL_SERVER_URL ||
|
|
11
|
+
process.env.BACKEND_URL ||
|
|
12
|
+
process.env.SERVER_URL ||
|
|
13
|
+
DEFAULT_API_BASE
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
export const FRONTEND_URL = stripTrailingSlash(
|
|
17
|
+
process.env.FRONTEND_URL || process.env.CLIENT_ORIGIN || DEFAULT_FRONTEND_URL
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
export const AUTH_BASE_URL = stripTrailingSlash(
|
|
21
|
+
process.env.BETTER_AUTH_BASE_URL || API_BASE
|
|
22
|
+
);
|
package/server/src/config/env.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import dotenv from "dotenv";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
-
import fs from "fs";
|
|
5
|
-
import os from "os";
|
|
6
4
|
|
|
7
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
6
|
const __dirname = path.dirname(__filename);
|
|
@@ -12,20 +10,8 @@ const serverEnvPath = path.resolve(__dirname, "../../.env");
|
|
|
12
10
|
|
|
13
11
|
dotenv.config({ path: serverEnvPath });
|
|
14
12
|
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
try {
|
|
18
|
-
const orbitalConfigPath = path.join(os.homedir(), ".orbital", "config.json");
|
|
19
|
-
if (!process.env.GOOGLE_GENERATIVE_AI_API_KEY && fs.existsSync(orbitalConfigPath)) {
|
|
20
|
-
const raw = fs.readFileSync(orbitalConfigPath, "utf-8");
|
|
21
|
-
const parsed = JSON.parse(raw);
|
|
22
|
-
if (parsed?.geminiApiKey && typeof parsed.geminiApiKey === "string") {
|
|
23
|
-
process.env.GOOGLE_GENERATIVE_AI_API_KEY = parsed.geminiApiKey.trim();
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
} catch {
|
|
27
|
-
// ignore
|
|
28
|
-
}
|
|
13
|
+
// Note: The Gemini API key is stored in the OS credential manager via keytar.
|
|
14
|
+
// CLI code will hydrate GOOGLE_GENERATIVE_AI_API_KEY from keytar when needed.
|
|
29
15
|
|
|
30
16
|
const stripWrappingQuotes = (value) => {
|
|
31
17
|
if (typeof value !== "string") return value;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { google } from "@ai-sdk/google";
|
|
2
2
|
import chalk from "chalk";
|
|
3
|
+
import { requireGeminiApiKeySync } from "../lib/orbitalConfig.js";
|
|
3
4
|
|
|
4
5
|
export const availableTools = [
|
|
5
6
|
{
|
|
@@ -32,6 +33,12 @@ export const getEnabledTools = () => {
|
|
|
32
33
|
const tools = {};
|
|
33
34
|
|
|
34
35
|
try {
|
|
36
|
+
const enabledToolCount = availableTools.filter((t) => t.enabled).length;
|
|
37
|
+
if (enabledToolCount > 0 && !process.env.GOOGLE_GENERATIVE_AI_API_KEY) {
|
|
38
|
+
|
|
39
|
+
requireGeminiApiKeySync();
|
|
40
|
+
}
|
|
41
|
+
|
|
35
42
|
for (const toolConfig of availableTools) {
|
|
36
43
|
if (toolConfig.enabled) {
|
|
37
44
|
tools[toolConfig.id] = toolConfig.getTool();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { ChatService } from "../services/chatService.js";
|
|
2
|
+
import { getAIService } from "../services/aiService.js";
|
|
3
|
+
import { enableTools, getEnabledTools, resetTools } from "../config/toolConfig.js";
|
|
4
|
+
import { generateApplicationPlan } from "../config/agentConfig.js";
|
|
5
|
+
|
|
6
|
+
const chatService = new ChatService();
|
|
7
|
+
|
|
8
|
+
export const respond = async (req, res, next) => {
|
|
9
|
+
try {
|
|
10
|
+
const { conversationId, mode, toolIds } = req.body || {};
|
|
11
|
+
|
|
12
|
+
if (!conversationId) {
|
|
13
|
+
return res.status(400).json({ error: "conversationId is required" });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const messages = await chatService.getMessages(conversationId);
|
|
17
|
+
const aiMessages = chatService.formatMessageForAI(messages);
|
|
18
|
+
|
|
19
|
+
let tools;
|
|
20
|
+
if (mode === "tool") {
|
|
21
|
+
resetTools();
|
|
22
|
+
if (Array.isArray(toolIds)) {
|
|
23
|
+
enableTools(toolIds);
|
|
24
|
+
}
|
|
25
|
+
tools = getEnabledTools();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const aiService = getAIService();
|
|
29
|
+
const result = await aiService.sendMessage(aiMessages, null, tools);
|
|
30
|
+
|
|
31
|
+
await chatService.addMessage(conversationId, "assistant", result.content);
|
|
32
|
+
|
|
33
|
+
return res.json({
|
|
34
|
+
content: result.content,
|
|
35
|
+
toolCalls: result.toolCalls || [],
|
|
36
|
+
toolResults: result.toolResults || [],
|
|
37
|
+
});
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return next(error);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const generateAgentPlan = async (req, res, next) => {
|
|
44
|
+
try {
|
|
45
|
+
const { description } = req.body || {};
|
|
46
|
+
|
|
47
|
+
if (!description || typeof description !== "string") {
|
|
48
|
+
return res.status(400).json({ error: "description is required" });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const aiService = getAIService();
|
|
52
|
+
const application = await generateApplicationPlan(description, aiService);
|
|
53
|
+
|
|
54
|
+
return res.json({ application });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
return next(error);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ChatService } from "../services/chatService.js";
|
|
2
|
+
|
|
3
|
+
const chatService = new ChatService();
|
|
4
|
+
|
|
5
|
+
export const getMe = async (req, res) => {
|
|
6
|
+
return res.json({
|
|
7
|
+
user: req.user,
|
|
8
|
+
session: {
|
|
9
|
+
id: req.session?.id,
|
|
10
|
+
expiresAt: req.session?.expiresAt,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const initConversation = async (req, res, next) => {
|
|
16
|
+
try {
|
|
17
|
+
const { conversationId, mode } = req.body || {};
|
|
18
|
+
|
|
19
|
+
const conversation = await chatService.getOrCreateConversation(
|
|
20
|
+
req.user.id,
|
|
21
|
+
conversationId || null,
|
|
22
|
+
mode || "chat"
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const messages = await chatService.getMessages(conversation.id);
|
|
26
|
+
|
|
27
|
+
return res.json({ conversation, messages });
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return next(error);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const addMessage = async (req, res, next) => {
|
|
34
|
+
try {
|
|
35
|
+
const { conversationId, role, content } = req.body || {};
|
|
36
|
+
|
|
37
|
+
if (!conversationId || !role) {
|
|
38
|
+
return res.status(400).json({ error: "conversationId and role are required" });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const message = await chatService.addMessage(conversationId, role, content);
|
|
42
|
+
return res.json({ message });
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return next(error);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export const getMessages = async (req, res, next) => {
|
|
49
|
+
try {
|
|
50
|
+
const { conversationId } = req.query || {};
|
|
51
|
+
|
|
52
|
+
if (!conversationId) {
|
|
53
|
+
return res.status(400).json({ error: "conversationId is required" });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const messages = await chatService.getMessages(conversationId);
|
|
57
|
+
return res.json({ messages });
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return next(error);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const updateTitle = async (req, res, next) => {
|
|
64
|
+
try {
|
|
65
|
+
const { id } = req.params || {};
|
|
66
|
+
const { title } = req.body || {};
|
|
67
|
+
|
|
68
|
+
if (!id || !title) {
|
|
69
|
+
return res.status(400).json({ error: "title is required" });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const conversation = await chatService.updateTitle(id, title);
|
|
73
|
+
return res.json({ conversation });
|
|
74
|
+
} catch (error) {
|
|
75
|
+
return next(error);
|
|
76
|
+
}
|
|
77
|
+
};
|
package/server/src/index.js
CHANGED
|
@@ -5,14 +5,14 @@ import cors from "cors";
|
|
|
5
5
|
import { auth } from "./lib/auth.js";
|
|
6
6
|
import prisma from "./lib/db.js";
|
|
7
7
|
import { ensureDbConnectionOrExit } from "./lib/dbHealth.js";
|
|
8
|
-
|
|
8
|
+
import { FRONTEND_URL } from "./config/api.js";
|
|
9
|
+
import cliRoutes from "./routes/cliRoutes.js";
|
|
10
|
+
import authRoutes from "./routes/authRoutes.js";
|
|
11
|
+
import { errorHandler } from "./middleware/errorHandler.js";
|
|
9
12
|
|
|
10
13
|
const app = express();
|
|
11
14
|
const PORT = process.env.PORT || 8080;
|
|
12
|
-
const CLIENT_ORIGIN =
|
|
13
|
-
process.env.CLIENT_ORIGIN ||
|
|
14
|
-
process.env.FRONTEND_URL ||
|
|
15
|
-
"https://smart-cli-based-agent-t7x4.vercel.app";
|
|
15
|
+
const CLIENT_ORIGIN = FRONTEND_URL;
|
|
16
16
|
|
|
17
17
|
app.use(express.json());
|
|
18
18
|
|
|
@@ -20,12 +20,14 @@ app.use(
|
|
|
20
20
|
cors({
|
|
21
21
|
origin: CLIENT_ORIGIN,
|
|
22
22
|
methods: ["GET", "POST", "PUT", "DELETE"],
|
|
23
|
-
credentials: true,
|
|
23
|
+
credentials: true,
|
|
24
24
|
})
|
|
25
25
|
);
|
|
26
26
|
|
|
27
27
|
app.all("/api/auth/*splat", toNodeHandler(auth));
|
|
28
28
|
|
|
29
|
+
app.use("/auth", authRoutes);
|
|
30
|
+
|
|
29
31
|
app.get("/api/me" , async (req, res)=>{
|
|
30
32
|
const session = await auth.api.getSession({
|
|
31
33
|
headers : fromNodeHeaders (req.headers),
|
|
@@ -72,6 +74,10 @@ app.get("/device" , async(req , res)=>{
|
|
|
72
74
|
res.redirect(`${CLIENT_ORIGIN}/device?user_code=${user_code}`)
|
|
73
75
|
});
|
|
74
76
|
|
|
77
|
+
app.use("/api/cli", cliRoutes);
|
|
78
|
+
|
|
79
|
+
app.use(errorHandler);
|
|
80
|
+
|
|
75
81
|
const start = async () => {
|
|
76
82
|
await ensureDbConnectionOrExit({
|
|
77
83
|
retries: Number(process.env.DB_CONNECT_RETRIES || 10),
|
package/server/src/lib/auth.js
CHANGED
|
@@ -3,6 +3,7 @@ import { betterAuth } from "better-auth";
|
|
|
3
3
|
import { prismaAdapter } from "better-auth/adapters/prisma";
|
|
4
4
|
import { deviceAuthorization } from "better-auth/plugins";
|
|
5
5
|
import prisma from "./db.js";
|
|
6
|
+
import { FRONTEND_URL } from "../config/api.js";
|
|
6
7
|
|
|
7
8
|
export const auth = betterAuth({
|
|
8
9
|
database: prismaAdapter(prisma, {
|
|
@@ -12,16 +13,15 @@ export const auth = betterAuth({
|
|
|
12
13
|
// auth cookies are set on the frontend origin. The OAuth callback must therefore
|
|
13
14
|
// also land on the frontend origin to avoid `state_mismatch`.
|
|
14
15
|
baseURL:
|
|
15
|
-
process.env.BETTER_AUTH_BASE_URL ||
|
|
16
16
|
process.env.FRONTEND_URL ||
|
|
17
17
|
process.env.CLIENT_ORIGIN ||
|
|
18
|
-
|
|
18
|
+
FRONTEND_URL,
|
|
19
19
|
basePath:"/api/auth" ,
|
|
20
20
|
trustedOrigins: [
|
|
21
21
|
process.env.CLIENT_ORIGIN ||
|
|
22
22
|
process.env.FRONTEND_URL ||
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
FRONTEND_URL,
|
|
24
|
+
FRONTEND_URL,
|
|
25
25
|
],
|
|
26
26
|
plugins: [
|
|
27
27
|
deviceAuthorization({
|