dank-ai 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.
@@ -0,0 +1,445 @@
1
+ /**
2
+ * Built-in Tools for Dank Agents
3
+ *
4
+ * A collection of commonly used tools that agents can leverage
5
+ * for web search, HTTP requests, file operations, and more.
6
+ */
7
+
8
+ const axios = require('axios');
9
+ const fs = require('fs-extra');
10
+ const path = require('path');
11
+
12
+ /**
13
+ * HTTP Request Tool
14
+ */
15
+ const httpRequest = {
16
+ description: 'Make HTTP requests to external APIs and websites',
17
+ category: 'web',
18
+ parameters: {
19
+ url: {
20
+ type: 'string',
21
+ description: 'The URL to make the request to',
22
+ required: true
23
+ },
24
+ method: {
25
+ type: 'string',
26
+ description: 'HTTP method to use',
27
+ enum: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
28
+ default: 'GET'
29
+ },
30
+ headers: {
31
+ type: 'object',
32
+ description: 'HTTP headers to include',
33
+ default: {}
34
+ },
35
+ data: {
36
+ type: 'object',
37
+ description: 'Request body data',
38
+ default: null
39
+ },
40
+ timeout: {
41
+ type: 'number',
42
+ description: 'Request timeout in milliseconds',
43
+ default: 10000,
44
+ min: 1000,
45
+ max: 60000
46
+ }
47
+ },
48
+ timeout: 15000,
49
+ retries: 2,
50
+ handler: async ({ url, method, headers, data, timeout }) => {
51
+ try {
52
+ const response = await axios({
53
+ url,
54
+ method,
55
+ headers: {
56
+ 'User-Agent': 'Dank-Agent/1.0',
57
+ ...headers
58
+ },
59
+ data,
60
+ timeout,
61
+ validateStatus: () => true // Don't throw on HTTP error status
62
+ });
63
+
64
+ return {
65
+ status: response.status,
66
+ statusText: response.statusText,
67
+ headers: response.headers,
68
+ data: response.data,
69
+ success: response.status >= 200 && response.status < 300
70
+ };
71
+ } catch (error) {
72
+ if (error.code === 'ECONNABORTED') {
73
+ throw new Error(`Request timeout after ${timeout}ms`);
74
+ }
75
+ throw new Error(`HTTP request failed: ${error.message}`);
76
+ }
77
+ }
78
+ };
79
+
80
+ /**
81
+ * Web Search Tool (using DuckDuckGo Instant Answer API)
82
+ */
83
+ const webSearch = {
84
+ description: 'Search the web for information using DuckDuckGo',
85
+ category: 'web',
86
+ parameters: {
87
+ query: {
88
+ type: 'string',
89
+ description: 'Search query',
90
+ required: true,
91
+ min: 1,
92
+ max: 500
93
+ },
94
+ format: {
95
+ type: 'string',
96
+ description: 'Response format',
97
+ enum: ['json'],
98
+ default: 'json'
99
+ }
100
+ },
101
+ cacheable: true,
102
+ cacheTime: 300000, // 5 minutes
103
+ handler: async ({ query, format }) => {
104
+ try {
105
+ const response = await axios.get('https://api.duckduckgo.com/', {
106
+ params: {
107
+ q: query,
108
+ format: format,
109
+ no_html: 1,
110
+ skip_disambig: 1
111
+ },
112
+ timeout: 10000
113
+ });
114
+
115
+ const data = response.data;
116
+
117
+ return {
118
+ query,
119
+ abstract: data.Abstract || null,
120
+ abstractText: data.AbstractText || null,
121
+ abstractSource: data.AbstractSource || null,
122
+ abstractURL: data.AbstractURL || null,
123
+ relatedTopics: data.RelatedTopics || [],
124
+ results: data.Results || [],
125
+ type: data.Type || 'unknown',
126
+ hasResults: !!(data.Abstract || data.Results?.length || data.RelatedTopics?.length)
127
+ };
128
+ } catch (error) {
129
+ throw new Error(`Web search failed: ${error.message}`);
130
+ }
131
+ }
132
+ };
133
+
134
+ /**
135
+ * File Read Tool
136
+ */
137
+ const readFile = {
138
+ description: 'Read contents of a file',
139
+ category: 'file',
140
+ parameters: {
141
+ filePath: {
142
+ type: 'string',
143
+ description: 'Path to the file to read',
144
+ required: true
145
+ },
146
+ encoding: {
147
+ type: 'string',
148
+ description: 'File encoding',
149
+ enum: ['utf8', 'ascii', 'base64', 'binary'],
150
+ default: 'utf8'
151
+ },
152
+ maxSize: {
153
+ type: 'number',
154
+ description: 'Maximum file size in bytes',
155
+ default: 1048576, // 1MB
156
+ min: 1,
157
+ max: 10485760 // 10MB
158
+ }
159
+ },
160
+ handler: async ({ filePath, encoding, maxSize }) => {
161
+ try {
162
+ // Security check - prevent path traversal
163
+ const resolvedPath = path.resolve(filePath);
164
+ if (!resolvedPath.startsWith(process.cwd())) {
165
+ throw new Error('Access denied: Path outside working directory');
166
+ }
167
+
168
+ // Check if file exists
169
+ if (!(await fs.pathExists(resolvedPath))) {
170
+ throw new Error(`File not found: ${filePath}`);
171
+ }
172
+
173
+ // Check file size
174
+ const stats = await fs.stat(resolvedPath);
175
+ if (stats.size > maxSize) {
176
+ throw new Error(`File too large: ${stats.size} bytes (max: ${maxSize})`);
177
+ }
178
+
179
+ const content = await fs.readFile(resolvedPath, encoding);
180
+
181
+ return {
182
+ filePath: resolvedPath,
183
+ size: stats.size,
184
+ encoding,
185
+ content,
186
+ lastModified: stats.mtime.toISOString()
187
+ };
188
+ } catch (error) {
189
+ throw new Error(`Failed to read file: ${error.message}`);
190
+ }
191
+ }
192
+ };
193
+
194
+ /**
195
+ * File Write Tool
196
+ */
197
+ const writeFile = {
198
+ description: 'Write content to a file',
199
+ category: 'file',
200
+ parameters: {
201
+ filePath: {
202
+ type: 'string',
203
+ description: 'Path to the file to write',
204
+ required: true
205
+ },
206
+ content: {
207
+ type: 'string',
208
+ description: 'Content to write to the file',
209
+ required: true
210
+ },
211
+ encoding: {
212
+ type: 'string',
213
+ description: 'File encoding',
214
+ enum: ['utf8', 'ascii', 'base64', 'binary'],
215
+ default: 'utf8'
216
+ },
217
+ createDirs: {
218
+ type: 'boolean',
219
+ description: 'Create parent directories if they don\'t exist',
220
+ default: true
221
+ }
222
+ },
223
+ handler: async ({ filePath, content, encoding, createDirs }) => {
224
+ try {
225
+ // Security check - prevent path traversal
226
+ const resolvedPath = path.resolve(filePath);
227
+ if (!resolvedPath.startsWith(process.cwd())) {
228
+ throw new Error('Access denied: Path outside working directory');
229
+ }
230
+
231
+ // Create parent directories if needed
232
+ if (createDirs) {
233
+ await fs.ensureDir(path.dirname(resolvedPath));
234
+ }
235
+
236
+ await fs.writeFile(resolvedPath, content, encoding);
237
+ const stats = await fs.stat(resolvedPath);
238
+
239
+ return {
240
+ filePath: resolvedPath,
241
+ size: stats.size,
242
+ encoding,
243
+ created: new Date().toISOString()
244
+ };
245
+ } catch (error) {
246
+ throw new Error(`Failed to write file: ${error.message}`);
247
+ }
248
+ }
249
+ };
250
+
251
+ /**
252
+ * JSON Parser Tool
253
+ */
254
+ const parseJson = {
255
+ description: 'Parse JSON string into JavaScript object',
256
+ category: 'utility',
257
+ parameters: {
258
+ jsonString: {
259
+ type: 'string',
260
+ description: 'JSON string to parse',
261
+ required: true
262
+ },
263
+ strict: {
264
+ type: 'boolean',
265
+ description: 'Use strict JSON parsing',
266
+ default: true
267
+ }
268
+ },
269
+ handler: ({ jsonString, strict }) => {
270
+ try {
271
+ const parsed = JSON.parse(jsonString);
272
+ return {
273
+ success: true,
274
+ data: parsed,
275
+ type: Array.isArray(parsed) ? 'array' : typeof parsed,
276
+ size: JSON.stringify(parsed).length
277
+ };
278
+ } catch (error) {
279
+ if (strict) {
280
+ throw new Error(`JSON parsing failed: ${error.message}`);
281
+ }
282
+
283
+ // Try to extract JSON-like content
284
+ try {
285
+ const cleaned = jsonString.replace(/[\n\r\t]/g, '').trim();
286
+ const parsed = JSON.parse(cleaned);
287
+ return {
288
+ success: true,
289
+ data: parsed,
290
+ type: Array.isArray(parsed) ? 'array' : typeof parsed,
291
+ size: JSON.stringify(parsed).length,
292
+ cleaned: true
293
+ };
294
+ } catch (secondError) {
295
+ throw new Error(`JSON parsing failed: ${error.message}`);
296
+ }
297
+ }
298
+ }
299
+ };
300
+
301
+ /**
302
+ * Text Analysis Tool
303
+ */
304
+ const analyzeText = {
305
+ description: 'Analyze text for various metrics and properties',
306
+ category: 'text',
307
+ parameters: {
308
+ text: {
309
+ type: 'string',
310
+ description: 'Text to analyze',
311
+ required: true,
312
+ min: 1,
313
+ max: 50000
314
+ },
315
+ includeStats: {
316
+ type: 'boolean',
317
+ description: 'Include detailed statistics',
318
+ default: true
319
+ },
320
+ includeSentiment: {
321
+ type: 'boolean',
322
+ description: 'Include basic sentiment analysis',
323
+ default: false
324
+ }
325
+ },
326
+ handler: ({ text, includeStats, includeSentiment }) => {
327
+ const result = {
328
+ text: text.substring(0, 100) + (text.length > 100 ? '...' : ''),
329
+ length: text.length
330
+ };
331
+
332
+ if (includeStats) {
333
+ const words = text.split(/\s+/).filter(word => word.length > 0);
334
+ const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0);
335
+ const paragraphs = text.split(/\n\s*\n/).filter(p => p.trim().length > 0);
336
+
337
+ result.stats = {
338
+ characters: text.length,
339
+ charactersNoSpaces: text.replace(/\s/g, '').length,
340
+ words: words.length,
341
+ sentences: sentences.length,
342
+ paragraphs: paragraphs.length,
343
+ averageWordsPerSentence: sentences.length > 0 ? Math.round((words.length / sentences.length) * 10) / 10 : 0,
344
+ readingTime: Math.ceil(words.length / 200), // Assuming 200 WPM
345
+ complexity: words.length > 100 ? 'high' : words.length > 50 ? 'medium' : 'low'
346
+ };
347
+ }
348
+
349
+ if (includeSentiment) {
350
+ const positiveWords = ['good', 'great', 'excellent', 'amazing', 'wonderful', 'fantastic', 'awesome', 'brilliant', 'perfect', 'outstanding'];
351
+ const negativeWords = ['bad', 'terrible', 'awful', 'horrible', 'disappointing', 'poor', 'worst', 'hate', 'dislike', 'fail'];
352
+
353
+ const lowerText = text.toLowerCase();
354
+ const positiveCount = positiveWords.filter(word => lowerText.includes(word)).length;
355
+ const negativeCount = negativeWords.filter(word => lowerText.includes(word)).length;
356
+
357
+ result.sentiment = {
358
+ score: positiveCount - negativeCount,
359
+ label: positiveCount > negativeCount ? 'positive' :
360
+ negativeCount > positiveCount ? 'negative' : 'neutral',
361
+ positiveWords: positiveCount,
362
+ negativeWords: negativeCount,
363
+ confidence: Math.min(0.9, Math.max(0.1, Math.abs(positiveCount - negativeCount) / 10))
364
+ };
365
+ }
366
+
367
+ return result;
368
+ }
369
+ };
370
+
371
+ /**
372
+ * Current Time Tool
373
+ */
374
+ const getCurrentTime = {
375
+ description: 'Get current date and time in various formats',
376
+ category: 'utility',
377
+ parameters: {
378
+ timezone: {
379
+ type: 'string',
380
+ description: 'Timezone (e.g., "America/New_York", "UTC")',
381
+ default: 'UTC'
382
+ },
383
+ format: {
384
+ type: 'string',
385
+ description: 'Output format',
386
+ enum: ['iso', 'unix', 'readable', 'custom'],
387
+ default: 'iso'
388
+ },
389
+ customFormat: {
390
+ type: 'string',
391
+ description: 'Custom date format (when format is "custom")',
392
+ default: 'YYYY-MM-DD HH:mm:ss'
393
+ }
394
+ },
395
+ handler: ({ timezone, format, customFormat }) => {
396
+ const now = new Date();
397
+
398
+ // Basic timezone handling (for production, use a proper library like moment-timezone)
399
+ const utcTime = now.getTime() + (now.getTimezoneOffset() * 60000);
400
+ const targetTime = new Date(utcTime);
401
+
402
+ const result = {
403
+ timestamp: now.toISOString(),
404
+ timezone,
405
+ format
406
+ };
407
+
408
+ switch (format) {
409
+ case 'iso':
410
+ result.formatted = targetTime.toISOString();
411
+ break;
412
+ case 'unix':
413
+ result.formatted = Math.floor(targetTime.getTime() / 1000);
414
+ break;
415
+ case 'readable':
416
+ result.formatted = targetTime.toLocaleString('en-US', {
417
+ timeZone: timezone === 'UTC' ? 'UTC' : timezone
418
+ });
419
+ break;
420
+ case 'custom':
421
+ // Basic custom formatting (for production, use a proper library)
422
+ result.formatted = customFormat
423
+ .replace('YYYY', targetTime.getFullYear())
424
+ .replace('MM', String(targetTime.getMonth() + 1).padStart(2, '0'))
425
+ .replace('DD', String(targetTime.getDate()).padStart(2, '0'))
426
+ .replace('HH', String(targetTime.getHours()).padStart(2, '0'))
427
+ .replace('mm', String(targetTime.getMinutes()).padStart(2, '0'))
428
+ .replace('ss', String(targetTime.getSeconds()).padStart(2, '0'));
429
+ break;
430
+ }
431
+
432
+ return result;
433
+ }
434
+ };
435
+
436
+ // Export all built-in tools
437
+ module.exports = {
438
+ httpRequest,
439
+ webSearch,
440
+ readFile,
441
+ writeFile,
442
+ parseJson,
443
+ analyzeText,
444
+ getCurrentTime
445
+ };