nyxora 1.6.6 → 1.6.7
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/CHANGELOG.md +44 -0
- package/LICENSE +21 -0
- package/README.md +21 -18
- package/package.json +2 -1
- package/packages/core/package.json +7 -1
- package/packages/core/src/agent/reasoning.ts +49 -4
- package/packages/core/src/gateway/cli.ts +6 -1
- package/packages/core/src/gateway/googleAuthModule.ts +181 -0
- package/packages/core/src/gateway/server.ts +110 -6
- package/packages/core/src/gateway/setup.ts +4 -4
- package/packages/core/src/system/skills/analyzeDocument.ts +64 -0
- package/packages/core/src/system/skills/googleWorkspace.ts +250 -0
- package/packages/core/src/system/skills/searchWeb.ts +46 -0
- package/packages/core/src/utils/dynamicTokenUpdater.ts +72 -0
- package/packages/core/src/utils/skillManager.ts +44 -0
- package/packages/core/src/utils/userWhitelistManager.ts +50 -0
- package/packages/core/src/web3/config.ts +29 -2
- package/packages/core/src/web3/skills/checkPortfolio.ts +51 -2
- package/packages/core/src/web3/skills/getBalance.ts +4 -0
- package/packages/core/src/web3/skills/swapToken.ts +9 -0
- package/packages/dashboard/dist/assets/index-24OeXn-k.css +1 -0
- package/packages/dashboard/dist/assets/index-BuYfTEKE.js +295 -0
- package/packages/dashboard/dist/favicon.svg +10 -1
- package/packages/dashboard/dist/index.html +2 -2
- package/packages/dashboard/package.json +1 -1
- package/packages/dashboard/public/favicon.svg +10 -1
- package/packages/dashboard/src/App.tsx +28 -24
- package/packages/dashboard/src/NetworkSelector.tsx +64 -0
- package/packages/dashboard/src/NyxoraLogo.tsx +25 -0
- package/packages/dashboard/src/OsSkills.tsx +352 -0
- package/packages/dashboard/src/Overview.tsx +3 -3
- package/packages/dashboard/src/PendingTransactions.tsx +2 -2
- package/packages/dashboard/src/Settings.tsx +114 -61
- package/packages/dashboard/src/Skills.tsx +135 -20
- package/packages/dashboard/src/components/PillSelect.tsx +65 -0
- package/packages/dashboard/src/index.css +205 -18
- package/packages/dashboard/src/utils/api.ts +8 -1
- package/packages/mcp-server/package.json +1 -1
- package/packages/policy/package.json +1 -1
- package/packages/signer/package.json +1 -1
- package/test-address.ts +11 -0
- package/test-all-chains.ts +19 -0
- package/test-portfolio.ts +14 -0
- package/IDENTITY.md +0 -17
- package/nyxora-1.5.2.tgz +0 -0
- package/packages/dashboard/dist/assets/index-CfIids2e.js +0 -170
- package/packages/dashboard/dist/assets/index-POJM-7Fd.css +0 -1
- package/security_policy.md +0 -2
- package/user.md +0 -9
|
@@ -26,11 +26,28 @@ import { checkPortfolioToolDefinition } from '../web3/skills/checkPortfolio';
|
|
|
26
26
|
import { marketAnalysisToolDefinition } from '../web3/skills/marketAnalysis';
|
|
27
27
|
import { createWalletToolDefinition } from '../web3/skills/createWallet';
|
|
28
28
|
import { createLimitOrderToolDefinition, listLimitOrdersToolDefinition, cancelLimitOrderToolDefinition } from '../agent/limitOrderManager';
|
|
29
|
+
import { isSkillActive, toggleSkill } from '../utils/skillManager';
|
|
29
30
|
import { executeBridge, bridgeTokenToolDefinition } from '../web3/skills/bridgeToken';
|
|
30
31
|
import { executeMintNft, mintNftToolDefinition } from '../web3/skills/mintNft';
|
|
31
32
|
import { executeCustomTx, customTxToolDefinition } from '../web3/skills/customTx';
|
|
33
|
+
|
|
34
|
+
// System Skills
|
|
35
|
+
import { browseWebsiteToolDefinition } from '../system/skills/browseWeb';
|
|
36
|
+
import { runTerminalCommandToolDefinition } from '../system/skills/executeShell';
|
|
37
|
+
import { installExternalSkillToolDefinition } from '../system/skills/installSkill';
|
|
38
|
+
import { readLocalFileToolDefinition } from '../system/skills/readFile';
|
|
39
|
+
import { updateSecurityPolicyToolDefinition } from '../system/skills/updateSecurityPolicy';
|
|
40
|
+
import { writeLocalFileToolDefinition } from '../system/skills/writeFile';
|
|
41
|
+
import { analyzeDocumentToolDefinition } from '../system/skills/analyzeDocument';
|
|
42
|
+
import { searchWebToolDefinition } from '../system/skills/searchWeb';
|
|
43
|
+
import { readGmailInboxToolDefinition, listCalendarEventsToolDefinition, appendRowToSheetsToolDefinition, readGoogleDocsToolDefinition, readGoogleFormResponsesToolDefinition } from '../system/skills/googleWorkspace';
|
|
44
|
+
|
|
32
45
|
import { startTelegramBot } from './telegram';
|
|
33
46
|
import { formatTransactionSuccess, formatTransactionError } from '../utils/formatter';
|
|
47
|
+
import { initGoogleAuth, getAuthUrl, processCallback, isAuthenticated } from './googleAuthModule';
|
|
48
|
+
|
|
49
|
+
// Initialize Google Auth
|
|
50
|
+
initGoogleAuth();
|
|
34
51
|
|
|
35
52
|
import util from 'util';
|
|
36
53
|
|
|
@@ -52,7 +69,16 @@ console.error = function (...args) {
|
|
|
52
69
|
|
|
53
70
|
const app = express();
|
|
54
71
|
app.use(helmet());
|
|
55
|
-
app.use(cors({
|
|
72
|
+
app.use(cors({
|
|
73
|
+
origin: function (origin, callback) {
|
|
74
|
+
if (!origin || /^(http:\/\/(localhost|127\.0\.0\.1):\d+)$/.test(origin)) {
|
|
75
|
+
callback(null, true);
|
|
76
|
+
} else {
|
|
77
|
+
callback(new Error('Not allowed by CORS'));
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
allowedHeaders: ['Content-Type', 'Authorization', 'x-nyxora-token']
|
|
81
|
+
}));
|
|
56
82
|
app.use(express.json());
|
|
57
83
|
|
|
58
84
|
const apiLimiter = rateLimit({
|
|
@@ -64,9 +90,15 @@ app.use('/api/', apiLimiter);
|
|
|
64
90
|
|
|
65
91
|
// API Auth Middleware
|
|
66
92
|
app.use('/api', (req, res, next) => {
|
|
93
|
+
// Bypass auth for Google OAuth callback and URLs since they are handled externally or by the browser
|
|
94
|
+
if (req.path.startsWith('/auth/google')) {
|
|
95
|
+
return next();
|
|
96
|
+
}
|
|
97
|
+
|
|
67
98
|
const token = req.headers['x-nyxora-token'];
|
|
68
99
|
if (token !== getSessionToken()) {
|
|
69
|
-
|
|
100
|
+
console.error(`[Auth] Rejected ${req.method} ${req.originalUrl} - Expected: ${getSessionToken().substring(0,8)}... Received: ${token ? token.toString().substring(0,8) + '...' : 'undefined'}`);
|
|
101
|
+
return res.status(401).json({ error: `Unauthorized: Invalid or missing token.` });
|
|
70
102
|
}
|
|
71
103
|
next();
|
|
72
104
|
});
|
|
@@ -170,7 +202,7 @@ app.get('/api/logs', (req, res) => {
|
|
|
170
202
|
});
|
|
171
203
|
|
|
172
204
|
app.get('/api/skills', (req, res) => {
|
|
173
|
-
|
|
205
|
+
const allSkills = [
|
|
174
206
|
getBalanceToolDefinition,
|
|
175
207
|
transferToolDefinition,
|
|
176
208
|
getPriceToolDefinition,
|
|
@@ -187,7 +219,79 @@ app.get('/api/skills', (req, res) => {
|
|
|
187
219
|
createLimitOrderToolDefinition,
|
|
188
220
|
listLimitOrdersToolDefinition,
|
|
189
221
|
cancelLimitOrderToolDefinition
|
|
190
|
-
]
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
const skillsWithStatus = allSkills.map(skill => ({
|
|
225
|
+
...skill,
|
|
226
|
+
isActive: isSkillActive(skill.function.name)
|
|
227
|
+
}));
|
|
228
|
+
|
|
229
|
+
res.json(skillsWithStatus);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
app.get('/api/skills/system', (req, res) => {
|
|
233
|
+
const systemSkills = [
|
|
234
|
+
runTerminalCommandToolDefinition,
|
|
235
|
+
readLocalFileToolDefinition,
|
|
236
|
+
writeLocalFileToolDefinition,
|
|
237
|
+
browseWebsiteToolDefinition,
|
|
238
|
+
updateSecurityPolicyToolDefinition,
|
|
239
|
+
installExternalSkillToolDefinition,
|
|
240
|
+
analyzeDocumentToolDefinition,
|
|
241
|
+
searchWebToolDefinition,
|
|
242
|
+
readGmailInboxToolDefinition,
|
|
243
|
+
listCalendarEventsToolDefinition,
|
|
244
|
+
appendRowToSheetsToolDefinition,
|
|
245
|
+
readGoogleDocsToolDefinition,
|
|
246
|
+
readGoogleFormResponsesToolDefinition
|
|
247
|
+
];
|
|
248
|
+
|
|
249
|
+
const skillsWithStatus = systemSkills.map(skill => ({
|
|
250
|
+
...skill,
|
|
251
|
+
isActive: isSkillActive(skill.function.name)
|
|
252
|
+
}));
|
|
253
|
+
|
|
254
|
+
res.json(skillsWithStatus);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
app.post('/api/skills/toggle', (req, res) => {
|
|
258
|
+
const { skillName, active } = req.body;
|
|
259
|
+
if (!skillName || typeof active !== 'boolean') {
|
|
260
|
+
return res.status(400).json({ error: 'Invalid payload' });
|
|
261
|
+
}
|
|
262
|
+
toggleSkill(skillName, active);
|
|
263
|
+
res.json({ success: true, skillName, active });
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Google Workspace Auth Routes
|
|
267
|
+
app.get('/api/auth/google/url', (req, res) => {
|
|
268
|
+
const url = getAuthUrl();
|
|
269
|
+
if (!url) return res.status(500).json({ error: 'Google Auth not configured' });
|
|
270
|
+
res.json({ url });
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
app.get('/api/auth/google/callback', async (req, res) => {
|
|
274
|
+
const code = req.query.code as string;
|
|
275
|
+
if (!code) return res.status(400).send('No code provided');
|
|
276
|
+
|
|
277
|
+
const success = await processCallback(code);
|
|
278
|
+
if (success) {
|
|
279
|
+
res.send(`
|
|
280
|
+
<html><body>
|
|
281
|
+
<h2>Authentication Successful!</h2>
|
|
282
|
+
<p>You can close this window and return to the dashboard.</p>
|
|
283
|
+
<script>
|
|
284
|
+
setTimeout(() => window.close(), 2000);
|
|
285
|
+
</script>
|
|
286
|
+
</body></html>
|
|
287
|
+
`);
|
|
288
|
+
} else {
|
|
289
|
+
res.status(500).send('Authentication failed');
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
app.get('/api/auth/google/status', async (req, res) => {
|
|
294
|
+
res.json({ connected: await isAuthenticated() });
|
|
191
295
|
});
|
|
192
296
|
|
|
193
297
|
app.get('/api/transactions', (req, res) => {
|
|
@@ -300,8 +404,8 @@ export function startServer() {
|
|
|
300
404
|
});
|
|
301
405
|
limitOrderManager.startMonitor();
|
|
302
406
|
|
|
303
|
-
const PORT = process.env.PORT || 3000;
|
|
304
|
-
app.listen(PORT, () => {
|
|
407
|
+
const PORT = Number(process.env.PORT || 3000);
|
|
408
|
+
app.listen(PORT, '0.0.0.0', () => {
|
|
305
409
|
console.log(`🤖 Nyxora API Server running on port ${PORT}`);
|
|
306
410
|
|
|
307
411
|
// Start the Telegram bot listener
|
|
@@ -166,12 +166,12 @@ Provider: ${config.llm.provider}`;
|
|
|
166
166
|
message: 'Select Default Chain:',
|
|
167
167
|
initialValue: config.agent.default_chain,
|
|
168
168
|
options: [
|
|
169
|
-
{ value: 'sepolia', label: 'Sepolia (Testnet)' },
|
|
170
|
-
{ value: 'base', label: 'Base' },
|
|
171
|
-
{ value: 'bsc', label: 'BSC' },
|
|
172
169
|
{ value: 'ethereum', label: 'Ethereum Mainnet' },
|
|
173
|
-
{ value: '
|
|
170
|
+
{ value: 'bsc', label: 'BSC' },
|
|
171
|
+
{ value: 'base', label: 'Base' },
|
|
174
172
|
{ value: 'optimism', label: 'Optimism' },
|
|
173
|
+
{ value: 'arbitrum', label: 'Arbitrum' },
|
|
174
|
+
{ value: 'sepolia', label: 'Sepolia (Testnet)' },
|
|
175
175
|
],
|
|
176
176
|
});
|
|
177
177
|
if (isCancel(defaultChain)) return process.exit(0);
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import pdfParse = require('pdf-parse');
|
|
4
|
+
// @ts-ignore
|
|
5
|
+
import mammoth from 'mammoth';
|
|
6
|
+
|
|
7
|
+
export async function analyzeDocument(filePath: string): Promise<string> {
|
|
8
|
+
try {
|
|
9
|
+
const absolutePath = path.resolve(filePath);
|
|
10
|
+
if (!fs.existsSync(absolutePath)) {
|
|
11
|
+
return `Error: File not found at ${absolutePath}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const ext = path.extname(absolutePath).toLowerCase();
|
|
15
|
+
|
|
16
|
+
if (ext === '.pdf') {
|
|
17
|
+
const dataBuffer = fs.readFileSync(absolutePath);
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
const data = await pdfParse(dataBuffer);
|
|
20
|
+
let text = data.text.trim();
|
|
21
|
+
if (text.length > 20000) {
|
|
22
|
+
text = text.substring(0, 20000) + "... [Content Truncated]";
|
|
23
|
+
}
|
|
24
|
+
return text;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (ext === '.docx') {
|
|
28
|
+
const result = await mammoth.extractRawText({ path: absolutePath });
|
|
29
|
+
let text = result.value.trim();
|
|
30
|
+
if (text.length > 20000) {
|
|
31
|
+
text = text.substring(0, 20000) + "... [Content Truncated]";
|
|
32
|
+
}
|
|
33
|
+
return text;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Fallback for TXT, MD, CSV, etc.
|
|
37
|
+
let content = fs.readFileSync(absolutePath, 'utf8');
|
|
38
|
+
if (content.length > 20000) {
|
|
39
|
+
content = content.substring(0, 20000) + "... [Content Truncated]";
|
|
40
|
+
}
|
|
41
|
+
return content;
|
|
42
|
+
|
|
43
|
+
} catch (error: any) {
|
|
44
|
+
return `Failed to analyze document: ${error.message}`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const analyzeDocumentToolDefinition = {
|
|
49
|
+
type: "function",
|
|
50
|
+
function: {
|
|
51
|
+
name: "analyze_document",
|
|
52
|
+
description: "Extracts textual content from documents (PDF, DOCX) and plain text files (TXT, MD, CSV). Useful for reading reports, contracts, and data dumps.",
|
|
53
|
+
parameters: {
|
|
54
|
+
type: "object",
|
|
55
|
+
properties: {
|
|
56
|
+
filePath: {
|
|
57
|
+
type: "string",
|
|
58
|
+
description: "The absolute or relative path to the document file.",
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
required: ["filePath"],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { getAccessToken, isAuthenticated } from '../../gateway/googleAuthModule';
|
|
2
|
+
|
|
3
|
+
export async function readGmailInbox(maxResults: number = 5): Promise<string> {
|
|
4
|
+
const isAuth = await isAuthenticated();
|
|
5
|
+
if (!isAuth) {
|
|
6
|
+
return "Google Auth not configured. Please link your Google account in the dashboard.";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const token = await getAccessToken();
|
|
10
|
+
if (!token) return "Failed to retrieve access token.";
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const listRes = await fetch(`https://gmail.googleapis.com/gmail/v1/users/me/messages?maxResults=${maxResults}&q=in:inbox`, {
|
|
14
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
15
|
+
});
|
|
16
|
+
const listData = await listRes.json();
|
|
17
|
+
|
|
18
|
+
if (!listData.messages || listData.messages.length === 0) {
|
|
19
|
+
return 'No emails found in inbox.';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
let output = `Top ${listData.messages.length} recent emails:\n\n`;
|
|
23
|
+
|
|
24
|
+
for (const msg of listData.messages) {
|
|
25
|
+
const msgRes = await fetch(`https://gmail.googleapis.com/gmail/v1/users/me/messages/${msg.id}?format=metadata&metadataHeaders=Subject&metadataHeaders=From&metadataHeaders=Date`, {
|
|
26
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
27
|
+
});
|
|
28
|
+
const msgData = await msgRes.json();
|
|
29
|
+
const headers = msgData.payload?.headers;
|
|
30
|
+
|
|
31
|
+
if (headers) {
|
|
32
|
+
const subject = headers.find((h: any) => h.name === 'Subject')?.value || 'No Subject';
|
|
33
|
+
const from = headers.find((h: any) => h.name === 'From')?.value || 'Unknown Sender';
|
|
34
|
+
const date = headers.find((h: any) => h.name === 'Date')?.value || 'Unknown Date';
|
|
35
|
+
const snippet = msgData.snippet ? `\nIsi/Snippet: ${msgData.snippet}` : '';
|
|
36
|
+
output += `From: ${from}\nSubject: ${subject}\nDate: ${date}${snippet}\n---\n`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return output.trim();
|
|
41
|
+
} catch (err: any) {
|
|
42
|
+
return `Error reading Gmail: ${err.message}`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function listCalendarEvents(maxResults: number = 5): Promise<string> {
|
|
47
|
+
const isAuth = await isAuthenticated();
|
|
48
|
+
if (!isAuth) return "Google Auth not configured. Please link your Google account in the dashboard.";
|
|
49
|
+
const token = await getAccessToken();
|
|
50
|
+
if (!token) return "Failed to retrieve access token.";
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const timeMin = encodeURIComponent(new Date().toISOString());
|
|
54
|
+
const res = await fetch(`https://www.googleapis.com/calendar/v3/calendars/primary/events?timeMin=${timeMin}&maxResults=${maxResults}&singleEvents=true&orderBy=startTime`, {
|
|
55
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
56
|
+
});
|
|
57
|
+
const data = await res.json();
|
|
58
|
+
|
|
59
|
+
const events = data.items;
|
|
60
|
+
if (!events || events.length === 0) {
|
|
61
|
+
return 'No upcoming events found.';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let output = `Upcoming ${events.length} events:\n\n`;
|
|
65
|
+
events.map((event: any, i: number) => {
|
|
66
|
+
const start = event.start?.dateTime || event.start?.date;
|
|
67
|
+
output += `${i + 1}. ${event.summary} (${start})\n`;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return output.trim();
|
|
71
|
+
} catch (err: any) {
|
|
72
|
+
return `Error reading Calendar: ${err.message}`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function appendRowToSheets(spreadsheetId: string, range: string, values: any[]): Promise<string> {
|
|
77
|
+
const isAuth = await isAuthenticated();
|
|
78
|
+
if (!isAuth) return "Google Auth not configured. Please link your Google account in the dashboard.";
|
|
79
|
+
const token = await getAccessToken();
|
|
80
|
+
if (!token) return "Failed to retrieve access token.";
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const res = await fetch(`https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${range}:append?valueInputOption=USER_ENTERED`, {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: {
|
|
86
|
+
Authorization: `Bearer ${token}`,
|
|
87
|
+
'Content-Type': 'application/json'
|
|
88
|
+
},
|
|
89
|
+
body: JSON.stringify({ values: [values] })
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const data = await res.json();
|
|
93
|
+
if (!res.ok) {
|
|
94
|
+
return `Failed to append to Sheets: ${data.error?.message || JSON.stringify(data)}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return `Successfully appended row to ${spreadsheetId} at range ${data.updates?.updatedRange}.`;
|
|
98
|
+
} catch (err: any) {
|
|
99
|
+
return `Error appending to Sheets: ${err.message}`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function readGoogleDocs(documentId: string): Promise<string> {
|
|
104
|
+
const isAuth = await isAuthenticated();
|
|
105
|
+
if (!isAuth) return "Google Auth not configured. Please link your Google account in the dashboard.";
|
|
106
|
+
const token = await getAccessToken();
|
|
107
|
+
if (!token) return "Failed to retrieve access token.";
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const res = await fetch(`https://docs.googleapis.com/v1/documents/${documentId}`, {
|
|
111
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
112
|
+
});
|
|
113
|
+
const data = await res.json();
|
|
114
|
+
if (!res.ok) return `Failed to read document: ${data.error?.message || JSON.stringify(data)}`;
|
|
115
|
+
|
|
116
|
+
let text = '';
|
|
117
|
+
data.body?.content?.forEach((c: any) => {
|
|
118
|
+
if (c.paragraph) {
|
|
119
|
+
c.paragraph.elements?.forEach((e: any) => {
|
|
120
|
+
if (e.textRun) text += e.textRun.content;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
return text ? text.substring(0, 2000) + (text.length > 2000 ? '... (truncated)' : '') : 'Document is empty.';
|
|
126
|
+
} catch (err: any) {
|
|
127
|
+
return `Error reading Google Docs: ${err.message}`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function readGoogleFormResponses(formId: string): Promise<string> {
|
|
132
|
+
const isAuth = await isAuthenticated();
|
|
133
|
+
if (!isAuth) return "Google Auth not configured. Please link your Google account in the dashboard.";
|
|
134
|
+
const token = await getAccessToken();
|
|
135
|
+
if (!token) return "Failed to retrieve access token.";
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const res = await fetch(`https://forms.googleapis.com/v1/forms/${formId}/responses`, {
|
|
139
|
+
headers: { Authorization: `Bearer ${token}` }
|
|
140
|
+
});
|
|
141
|
+
const data = await res.json();
|
|
142
|
+
if (!res.ok) return `Failed to read form responses: ${data.error?.message || JSON.stringify(data)}`;
|
|
143
|
+
|
|
144
|
+
if (!data.responses || data.responses.length === 0) {
|
|
145
|
+
return 'No responses found for this form.';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let output = `Total ${data.responses.length} responses:\n\n`;
|
|
149
|
+
|
|
150
|
+
// Only return the last 10 responses to avoid overflowing context window
|
|
151
|
+
const recentResponses = data.responses.slice(-10);
|
|
152
|
+
|
|
153
|
+
recentResponses.forEach((response: any, i: number) => {
|
|
154
|
+
output += `Response ${i + 1} (Submitted at ${response.createTime}):\n`;
|
|
155
|
+
if (response.answers) {
|
|
156
|
+
for (const [questionId, answer] of Object.entries(response.answers)) {
|
|
157
|
+
const ans = answer as any;
|
|
158
|
+
const textAnswers = ans.textAnswers?.answers?.map((a: any) => a.value).join(', ') || 'No text answer';
|
|
159
|
+
output += `- Question ID ${questionId}: ${textAnswers}\n`;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
output += '---\n';
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return output.trim();
|
|
166
|
+
} catch (err: any) {
|
|
167
|
+
return `Error reading Google Forms: ${err.message}`;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export const readGmailInboxToolDefinition = {
|
|
172
|
+
type: "function",
|
|
173
|
+
function: {
|
|
174
|
+
name: "read_gmail_inbox",
|
|
175
|
+
description: "Reads the most recent emails from the user's Gmail inbox.",
|
|
176
|
+
parameters: {
|
|
177
|
+
type: "object",
|
|
178
|
+
properties: {
|
|
179
|
+
maxResults: { type: "number", description: "Number of emails to fetch (default: 5)." }
|
|
180
|
+
},
|
|
181
|
+
required: [],
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export const listCalendarEventsToolDefinition = {
|
|
187
|
+
type: "function",
|
|
188
|
+
function: {
|
|
189
|
+
name: "list_calendar_events",
|
|
190
|
+
description: "Lists upcoming events from the user's Google Calendar.",
|
|
191
|
+
parameters: {
|
|
192
|
+
type: "object",
|
|
193
|
+
properties: {
|
|
194
|
+
maxResults: { type: "number", description: "Number of events to fetch (default: 5)." }
|
|
195
|
+
},
|
|
196
|
+
required: [],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
export const appendRowToSheetsToolDefinition = {
|
|
202
|
+
type: "function",
|
|
203
|
+
function: {
|
|
204
|
+
name: "append_row_to_sheets",
|
|
205
|
+
description: "Appends a row of data to a Google Spreadsheet.",
|
|
206
|
+
parameters: {
|
|
207
|
+
type: "object",
|
|
208
|
+
properties: {
|
|
209
|
+
spreadsheetId: { type: "string", description: "The ID of the spreadsheet (found in the URL)." },
|
|
210
|
+
range: { type: "string", description: "The A1 notation of a range to search for a logical table of data, e.g. 'Sheet1!A:D'." },
|
|
211
|
+
values: {
|
|
212
|
+
type: "array",
|
|
213
|
+
items: { type: "string" },
|
|
214
|
+
description: "An array of strings representing the columns of the new row."
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
required: ["spreadsheetId", "range", "values"],
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
export const readGoogleDocsToolDefinition = {
|
|
223
|
+
type: "function",
|
|
224
|
+
function: {
|
|
225
|
+
name: "read_google_docs",
|
|
226
|
+
description: "Reads the text content of a Google Document.",
|
|
227
|
+
parameters: {
|
|
228
|
+
type: "object",
|
|
229
|
+
properties: {
|
|
230
|
+
documentId: { type: "string", description: "The ID of the document (found in the URL)." }
|
|
231
|
+
},
|
|
232
|
+
required: ["documentId"],
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export const readGoogleFormResponsesToolDefinition = {
|
|
238
|
+
type: "function",
|
|
239
|
+
function: {
|
|
240
|
+
name: "read_google_form_responses",
|
|
241
|
+
description: "Reads the most recent responses from a Google Form.",
|
|
242
|
+
parameters: {
|
|
243
|
+
type: "object",
|
|
244
|
+
properties: {
|
|
245
|
+
formId: { type: "string", description: "The ID of the form (found in the URL)." }
|
|
246
|
+
},
|
|
247
|
+
required: ["formId"],
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { search, SafeSearchType } from 'duck-duck-scrape';
|
|
2
|
+
|
|
3
|
+
export async function searchWeb(query: string): Promise<string> {
|
|
4
|
+
try {
|
|
5
|
+
const searchResults = await search(query, {
|
|
6
|
+
safeSearch: SafeSearchType.MODERATE
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
if (!searchResults.results || searchResults.results.length === 0) {
|
|
10
|
+
return "No results found for your query.";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Limit to top 8 results
|
|
14
|
+
const topResults = searchResults.results.slice(0, 8);
|
|
15
|
+
|
|
16
|
+
let responseText = `Search Results for "${query}":\n\n`;
|
|
17
|
+
|
|
18
|
+
topResults.forEach((result, index) => {
|
|
19
|
+
responseText += `${index + 1}. ${result.title}\n`;
|
|
20
|
+
responseText += `URL: ${result.url}\n`;
|
|
21
|
+
responseText += `Snippet: ${result.description}\n\n`;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return responseText.trim();
|
|
25
|
+
} catch (error: any) {
|
|
26
|
+
return `Failed to search the web: ${error.message}`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const searchWebToolDefinition = {
|
|
31
|
+
type: "function",
|
|
32
|
+
function: {
|
|
33
|
+
name: "search_web",
|
|
34
|
+
description: "Searches the internet for information using a search engine. Returns top titles, snippets, and URLs. Use this to find current events, documentation, or general facts.",
|
|
35
|
+
parameters: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {
|
|
38
|
+
query: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "The search query to look up.",
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
required: ["query"],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
const CACHE_FILE = path.join(os.homedir(), '.nyxora', 'dynamic_tokens.json');
|
|
6
|
+
const CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
|
|
7
|
+
|
|
8
|
+
const TOKEN_LIST_URLS: Record<string, string> = {
|
|
9
|
+
ethereum: 'https://tokens.coingecko.com/uniswap/all.json',
|
|
10
|
+
base: 'https://raw.githubusercontent.com/ethereum-optimism/ethereum-optimism.github.io/master/optimism.tokenlist.json',
|
|
11
|
+
arbitrum: 'https://bridge.arbitrum.io/token-list-42161.json',
|
|
12
|
+
optimism: 'https://static.optimism.io/optimism.tokenlist.json',
|
|
13
|
+
bsc: 'https://tokens.pancakeswap.finance/pancakeswap-extended.json'
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface DynamicTokens {
|
|
17
|
+
[chainName: string]: Array<{ symbol: string, address: string }>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function fetchDynamicTokens(): Promise<DynamicTokens> {
|
|
21
|
+
// Check cache first
|
|
22
|
+
try {
|
|
23
|
+
if (fs.existsSync(CACHE_FILE)) {
|
|
24
|
+
const stats = fs.statSync(CACHE_FILE);
|
|
25
|
+
if (Date.now() - stats.mtimeMs < CACHE_TTL) {
|
|
26
|
+
return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
} catch (e) {
|
|
30
|
+
// Ignore cache error
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const result: DynamicTokens = {};
|
|
34
|
+
console.log('[DynamicTokenUpdater] Fetching updated token lists...');
|
|
35
|
+
|
|
36
|
+
for (const [chain, url] of Object.entries(TOKEN_LIST_URLS)) {
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch(url);
|
|
39
|
+
const data = await res.json();
|
|
40
|
+
|
|
41
|
+
let tokens: Array<{symbol: string, address: string}> = [];
|
|
42
|
+
|
|
43
|
+
if (data && data.tokens && Array.isArray(data.tokens)) {
|
|
44
|
+
// Limit to top 50 to avoid massive multicall
|
|
45
|
+
const topTokens = data.tokens.slice(0, 50);
|
|
46
|
+
tokens = topTokens.map((t: any) => ({
|
|
47
|
+
symbol: t.symbol,
|
|
48
|
+
address: t.address
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
result[chain] = tokens;
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.warn(`[DynamicTokenUpdater] Failed to fetch list for ${chain}:`, err);
|
|
55
|
+
result[chain] = [];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Save to cache
|
|
60
|
+
try {
|
|
61
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify(result, null, 2), 'utf-8');
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.error('[DynamicTokenUpdater] Failed to write cache', e);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function getDynamicTokensForChain(chainName: string): Promise<Array<{symbol: string, address: string}>> {
|
|
70
|
+
const allTokens = await fetchDynamicTokens();
|
|
71
|
+
return allTokens[chainName] || [];
|
|
72
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { getPath } from '../config/paths';
|
|
3
|
+
|
|
4
|
+
let disabledSkillsCache: string[] | null = null;
|
|
5
|
+
|
|
6
|
+
function getDisabledSkillsFile(): string {
|
|
7
|
+
return getPath('disabled_skills.json');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getDisabledSkills(): string[] {
|
|
11
|
+
if (disabledSkillsCache !== null) {
|
|
12
|
+
return disabledSkillsCache;
|
|
13
|
+
}
|
|
14
|
+
const filepath = getDisabledSkillsFile();
|
|
15
|
+
if (!fs.existsSync(filepath)) {
|
|
16
|
+
disabledSkillsCache = [];
|
|
17
|
+
return disabledSkillsCache;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const data = fs.readFileSync(filepath, 'utf-8');
|
|
21
|
+
disabledSkillsCache = JSON.parse(data);
|
|
22
|
+
if (!Array.isArray(disabledSkillsCache)) disabledSkillsCache = [];
|
|
23
|
+
return disabledSkillsCache;
|
|
24
|
+
} catch (e) {
|
|
25
|
+
disabledSkillsCache = [];
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function toggleSkill(skillName: string, active: boolean): void {
|
|
31
|
+
const current = new Set(getDisabledSkills());
|
|
32
|
+
if (active) {
|
|
33
|
+
current.delete(skillName); // Remove from disabled list to activate
|
|
34
|
+
} else {
|
|
35
|
+
current.add(skillName); // Add to disabled list to deactivate
|
|
36
|
+
}
|
|
37
|
+
disabledSkillsCache = Array.from(current);
|
|
38
|
+
fs.writeFileSync(getDisabledSkillsFile(), JSON.stringify(disabledSkillsCache, null, 2));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isSkillActive(skillName: string): boolean {
|
|
42
|
+
const disabled = getDisabledSkills();
|
|
43
|
+
return !disabled.includes(skillName);
|
|
44
|
+
}
|