quantrexa-ai 0.0.0-stage → 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/cli.js +349 -0
- package/package.json +15 -5
- package/README.md +0 -3
package/cli.js
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
const { exec } = require('child_process');
|
|
7
|
+
|
|
8
|
+
const BACKEND_URL = "https://quantrexa-prime-backend.onrender.com";
|
|
9
|
+
const CONFIG_DIR = path.join(os.homedir(), '.quantrexa');
|
|
10
|
+
const AUTH_FILE = path.join(CONFIG_DIR, 'auth.json');
|
|
11
|
+
|
|
12
|
+
const C = {
|
|
13
|
+
cyan: "\x1b[36m",
|
|
14
|
+
green: "\x1b[32m",
|
|
15
|
+
yellow: "\x1b[33m",
|
|
16
|
+
magenta: "\x1b[35m",
|
|
17
|
+
red: "\x1b[31m",
|
|
18
|
+
bold: "\x1b[1m",
|
|
19
|
+
dim: "\x1b[2m",
|
|
20
|
+
reset: "\x1b[0m"
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function getSavedSession() {
|
|
24
|
+
try {
|
|
25
|
+
if (fs.existsSync(AUTH_FILE)) {
|
|
26
|
+
const data = JSON.parse(fs.readFileSync(AUTH_FILE, 'utf8'));
|
|
27
|
+
if (data && data.token) return data;
|
|
28
|
+
}
|
|
29
|
+
} catch (e) {}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function saveSession(token, user) {
|
|
34
|
+
if (!fs.existsSync(CONFIG_DIR)) fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
35
|
+
fs.writeFileSync(AUTH_FILE, JSON.stringify({ token, user, savedAt: Date.now() }, null, 2), 'utf8');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function clearSession() {
|
|
39
|
+
if (fs.existsSync(AUTH_FILE)) fs.unlinkSync(AUTH_FILE);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 🛠️ Powerful Agent Tools (Local PC Execution)
|
|
43
|
+
const AGENT_TOOLS = [
|
|
44
|
+
{
|
|
45
|
+
type: "function",
|
|
46
|
+
function: {
|
|
47
|
+
name: "read_file",
|
|
48
|
+
description: "Read complete content of a file in the user's workspace.",
|
|
49
|
+
parameters: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: { filepath: { type: "string" } },
|
|
52
|
+
required: ["filepath"]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: "function",
|
|
58
|
+
function: {
|
|
59
|
+
name: "write_file",
|
|
60
|
+
description: "Write or create a complete source code or document file on the user's disk.",
|
|
61
|
+
parameters: {
|
|
62
|
+
type: "object",
|
|
63
|
+
properties: {
|
|
64
|
+
filepath: { type: "string" },
|
|
65
|
+
content: { type: "string" }
|
|
66
|
+
},
|
|
67
|
+
required: ["filepath", "content"]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: "function",
|
|
73
|
+
function: {
|
|
74
|
+
name: "list_directory",
|
|
75
|
+
description: "List files and subdirectories in the project.",
|
|
76
|
+
parameters: {
|
|
77
|
+
type: "object",
|
|
78
|
+
properties: { dirpath: { type: "string" } }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: "function",
|
|
84
|
+
function: {
|
|
85
|
+
name: "run_command",
|
|
86
|
+
description: "Run a safe terminal shell command (e.g., git status, npm run build, dir, python test.py).",
|
|
87
|
+
parameters: {
|
|
88
|
+
type: "object",
|
|
89
|
+
properties: { command: { type: "string" } },
|
|
90
|
+
required: ["command"]
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: "function",
|
|
96
|
+
function: {
|
|
97
|
+
name: "web_search",
|
|
98
|
+
description: "Search Google in real-time via Quantrexa cloud servers for documentation, error fixes, or live data.",
|
|
99
|
+
parameters: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: { query: { type: "string" } },
|
|
102
|
+
required: ["query"]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
async function executeLocalTool(name, args) {
|
|
109
|
+
try {
|
|
110
|
+
if (name === "read_file") {
|
|
111
|
+
const fullPath = path.resolve(process.cwd(), args.filepath);
|
|
112
|
+
if (!fs.existsSync(fullPath)) return `File '${args.filepath}' not found.`;
|
|
113
|
+
const data = fs.readFileSync(fullPath, 'utf8');
|
|
114
|
+
console.log(`${C.yellow} ⚡ [Tool: Read File] ${args.filepath} (${data.length} chars)${C.reset}`);
|
|
115
|
+
return data.slice(0, 16000);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (name === "write_file") {
|
|
119
|
+
const fullPath = path.resolve(process.cwd(), args.filepath);
|
|
120
|
+
const parent = path.dirname(fullPath);
|
|
121
|
+
if (!fs.existsSync(parent)) fs.mkdirSync(parent, { recursive: true });
|
|
122
|
+
fs.writeFileSync(fullPath, args.content, 'utf8');
|
|
123
|
+
console.log(`${C.green} ⚡ [Tool: File Written] ${args.filepath} (${args.content.length} bytes)${C.reset}`);
|
|
124
|
+
return `File ${args.filepath} written successfully.`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (name === "list_directory") {
|
|
128
|
+
const target = path.resolve(process.cwd(), args.dirpath || '.');
|
|
129
|
+
const items = fs.readdirSync(target);
|
|
130
|
+
console.log(`${C.yellow} ⚡ [Tool: Scan Directory] Found ${items.length} files${C.reset}`);
|
|
131
|
+
return JSON.stringify(items);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (name === "run_command") {
|
|
135
|
+
console.log(`${C.magenta} ⚡ [Tool: Running Shell] $ ${args.command}${C.reset}`);
|
|
136
|
+
return new Promise(resolve => {
|
|
137
|
+
exec(args.command, { timeout: 20000, cwd: process.cwd() }, (err, stdout, stderr) => {
|
|
138
|
+
if (err) return resolve(`Command Error: ${stderr || err.message}`);
|
|
139
|
+
resolve(stdout.slice(0, 8000) || "Command completed with no output.");
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (name === "web_search") {
|
|
145
|
+
console.log(`${C.cyan} ⚡ [Tool: Web Search] "${args.query}"...${C.reset}`);
|
|
146
|
+
const r = await fetch(`${BACKEND_URL}/api/search?q=${encodeURIComponent(args.query)}`);
|
|
147
|
+
const d = await r.json();
|
|
148
|
+
if (d.organic_results?.length) {
|
|
149
|
+
return d.organic_results.slice(0, 4).map(res => `${res.title} (${res.link}): ${res.snippet}`).join('\n\n');
|
|
150
|
+
}
|
|
151
|
+
return "No results found.";
|
|
152
|
+
}
|
|
153
|
+
} catch (err) {
|
|
154
|
+
return `Tool error: ${err.message}`;
|
|
155
|
+
}
|
|
156
|
+
return "Unknown tool";
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 🔐 Mandatory Login Gate (Terminal OTP)
|
|
160
|
+
function promptLogin(rl) {
|
|
161
|
+
return new Promise(resolve => {
|
|
162
|
+
console.log(`\n${C.bold}${C.yellow}====================================================${C.reset}`);
|
|
163
|
+
console.log(`${C.bold} 🔒 QUANTREXA PRIME AI — CLI LOGIN REQUIRED ${C.reset}`);
|
|
164
|
+
console.log(`${C.yellow}====================================================${C.reset}`);
|
|
165
|
+
console.log(`${C.dim}Account verification is required to use this AI Agent.${C.reset}\n`);
|
|
166
|
+
|
|
167
|
+
rl.question(`${C.cyan}Enter your Quantrexa Email: ${C.reset}`, async (emailInput) => {
|
|
168
|
+
const email = emailInput.trim().toLowerCase();
|
|
169
|
+
if (!email || !email.includes('@')) {
|
|
170
|
+
console.log(`${C.red}Invalid email. Try again.${C.reset}\n`);
|
|
171
|
+
return resolve(promptLogin(rl));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
console.log(`${C.dim}Sending 6-digit OTP code to ${email}...${C.reset}`);
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const sendRes = await fetch(`${BACKEND_URL}/api/send-otp`, {
|
|
178
|
+
method: 'POST',
|
|
179
|
+
headers: { 'Content-Type': 'application/json' },
|
|
180
|
+
body: JSON.stringify({ email })
|
|
181
|
+
});
|
|
182
|
+
const sendData = await sendRes.json();
|
|
183
|
+
|
|
184
|
+
if (!sendRes.ok || !sendData.success) {
|
|
185
|
+
console.log(`${C.red}Failed: ${sendData.error || 'Server error'}${C.reset}\n`);
|
|
186
|
+
return resolve(false);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log(`${C.green}✓ OTP code sent to your email inbox!${C.reset}`);
|
|
190
|
+
|
|
191
|
+
rl.question(`${C.cyan}Enter 6-Digit OTP: ${C.reset}`, async (otpInput) => {
|
|
192
|
+
const otp = otpInput.trim();
|
|
193
|
+
console.log(`${C.dim}Authenticating with Quantrexa Cloud...${C.reset}`);
|
|
194
|
+
|
|
195
|
+
const verifyRes = await fetch(`${BACKEND_URL}/api/verify-otp`, {
|
|
196
|
+
method: 'POST',
|
|
197
|
+
headers: { 'Content-Type': 'application/json' },
|
|
198
|
+
body: JSON.stringify({ email, otp })
|
|
199
|
+
});
|
|
200
|
+
const verifyData = await verifyRes.json();
|
|
201
|
+
|
|
202
|
+
if (verifyRes.ok && verifyData.success && verifyData.token) {
|
|
203
|
+
saveSession(verifyData.token, verifyData.user);
|
|
204
|
+
console.log(`\n${C.green}${C.bold}✓ Logged in as: ${verifyData.user.email}!${C.reset}\n`);
|
|
205
|
+
resolve(verifyData);
|
|
206
|
+
} else {
|
|
207
|
+
console.log(`${C.red}❌ Invalid OTP code.${C.reset}\n`);
|
|
208
|
+
resolve(false);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
} catch (err) {
|
|
212
|
+
console.log(`${C.red}Connection error: ${err.message}${C.reset}\n`);
|
|
213
|
+
resolve(false);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function startAgent() {
|
|
220
|
+
const rl = readline.createInterface({
|
|
221
|
+
input: process.stdin,
|
|
222
|
+
output: process.stdout
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
let session = getSavedSession();
|
|
226
|
+
|
|
227
|
+
// If not logged in, enforce login first
|
|
228
|
+
if (!session) {
|
|
229
|
+
const loggedIn = await promptLogin(rl);
|
|
230
|
+
if (!loggedIn) {
|
|
231
|
+
console.log(`${C.red}Authentication required. Quantrexa CLI exiting.${C.reset}`);
|
|
232
|
+
rl.close();
|
|
233
|
+
process.exit(1);
|
|
234
|
+
}
|
|
235
|
+
session = getSavedSession();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
console.clear();
|
|
239
|
+
console.log(`${C.bold}${C.cyan}======================================================${C.reset}`);
|
|
240
|
+
console.log(`${C.bold}${C.magenta} QUANTREXA PRIME — AUTONOMOUS CODING AGENT ${C.reset}`);
|
|
241
|
+
console.log(`${C.cyan}======================================================${C.reset}`);
|
|
242
|
+
console.log(`${C.green}● Cloud Brain:${C.reset} Google Gemini 2.5 Flash (${BACKEND_URL})`);
|
|
243
|
+
console.log(`${C.green}● User Account:${C.reset} ${session.user?.email}`);
|
|
244
|
+
console.log(`${C.yellow}● Local Dir :${C.reset} ${process.cwd()}`);
|
|
245
|
+
console.log(`${C.dim}Commands: 'logout' to switch account | 'exit' to quit.${C.reset}\n`);
|
|
246
|
+
|
|
247
|
+
const conversation = [
|
|
248
|
+
{
|
|
249
|
+
role: "system",
|
|
250
|
+
content: `You are Quantrexa Autonomous Coding Agent powered by Gemini.
|
|
251
|
+
You run directly on the user's terminal inside directory: ${process.cwd()}.
|
|
252
|
+
You have tools to: read_file, write_file, list_directory, run_command, and web_search.
|
|
253
|
+
When tasked with writing, fixing, or building software:
|
|
254
|
+
1. Examine existing files first if necessary.
|
|
255
|
+
2. Write full, complete, production-ready code directly to disk using write_file.
|
|
256
|
+
3. If dependencies or builds are required, execute them using run_command.
|
|
257
|
+
4. Report back concisely once the tasks are completed.`
|
|
258
|
+
}
|
|
259
|
+
];
|
|
260
|
+
|
|
261
|
+
const ask = () => {
|
|
262
|
+
rl.question(`${C.bold}${C.cyan}Quantrexa > ${C.reset}`, async (input) => {
|
|
263
|
+
const query = input.trim();
|
|
264
|
+
if (!query) return ask();
|
|
265
|
+
|
|
266
|
+
if (query.toLowerCase() === 'exit' || query.toLowerCase() === 'quit') {
|
|
267
|
+
console.log(`\n${C.yellow}Goodbye!${C.reset}\n`);
|
|
268
|
+
rl.close();
|
|
269
|
+
process.exit(0);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (query.toLowerCase() === 'logout') {
|
|
273
|
+
clearSession();
|
|
274
|
+
console.log(`\n${C.yellow}Logged out successfully.${C.reset}\n`);
|
|
275
|
+
rl.close();
|
|
276
|
+
process.exit(0);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
conversation.push({ role: "user", content: query });
|
|
280
|
+
console.log(`${C.dim}Agent thinking with Gemini...${C.reset}`);
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
let isComplete = false;
|
|
284
|
+
let loopCount = 0;
|
|
285
|
+
|
|
286
|
+
while (!isComplete && loopCount < 10) {
|
|
287
|
+
loopCount++;
|
|
288
|
+
|
|
289
|
+
const res = await fetch(`${BACKEND_URL}/api/agent`, {
|
|
290
|
+
method: 'POST',
|
|
291
|
+
headers: {
|
|
292
|
+
'Content-Type': 'application/json',
|
|
293
|
+
'Authorization': `Bearer ${session.token}`,
|
|
294
|
+
'x-client-type': 'cli'
|
|
295
|
+
},
|
|
296
|
+
body: JSON.stringify({
|
|
297
|
+
conversation,
|
|
298
|
+
tools: AGENT_TOOLS
|
|
299
|
+
})
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
if (res.status === 401) {
|
|
303
|
+
console.log(`\n${C.red}Session expired. Please login again.${C.reset}`);
|
|
304
|
+
clearSession();
|
|
305
|
+
rl.close();
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const data = await res.json();
|
|
310
|
+
const choice = data.choices?.[0];
|
|
311
|
+
if (!choice) throw new Error(data.error || "Backend returned empty response.");
|
|
312
|
+
|
|
313
|
+
const msg = choice.message;
|
|
314
|
+
|
|
315
|
+
// Agent executes tool on user's machine
|
|
316
|
+
if (msg.tool_calls && msg.tool_calls.length) {
|
|
317
|
+
conversation.push(msg);
|
|
318
|
+
|
|
319
|
+
for (const call of msg.tool_calls) {
|
|
320
|
+
const fnName = call.function.name;
|
|
321
|
+
const fnArgs = JSON.parse(call.function.arguments || "{}");
|
|
322
|
+
|
|
323
|
+
const result = await executeLocalTool(fnName, fnArgs);
|
|
324
|
+
|
|
325
|
+
conversation.push({
|
|
326
|
+
role: "tool",
|
|
327
|
+
tool_call_id: call.id,
|
|
328
|
+
name: fnName,
|
|
329
|
+
content: String(result)
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
console.log(`\n${C.bold}${C.green}Quantrexa Agent:${C.reset}\n${msg.content}\n`);
|
|
334
|
+
conversation.push(msg);
|
|
335
|
+
isComplete = true;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
} catch (err) {
|
|
339
|
+
console.error(`${C.red}Error: ${err.message}${C.reset}\n`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
ask();
|
|
343
|
+
});
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
ask();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
startAgent();
|
package/package.json
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "quantrexa-ai",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
1
|
+
{
|
|
2
|
+
"name": "quantrexa-ai",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Quantrexa Prime AI — Autonomous Developer & System CLI Agent",
|
|
5
|
+
"main": "cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"quantrexa": "./cli.js",
|
|
8
|
+
"quantrexa-ai": "./cli.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["quantrexa", "ai", "cli", "agent", "gemini", "coding", "developer"],
|
|
11
|
+
"author": "Purav Raj",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=18.0.0"
|
|
15
|
+
}
|
|
6
16
|
}
|
package/README.md
DELETED