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.
- package/README.md +1331 -0
- package/bin/dank +118 -0
- package/docker/Dockerfile +57 -0
- package/docker/entrypoint.js +1227 -0
- package/docker/package.json +19 -0
- package/lib/agent.js +644 -0
- package/lib/cli/build.js +43 -0
- package/lib/cli/clean.js +30 -0
- package/lib/cli/init.js +38 -0
- package/lib/cli/logs.js +122 -0
- package/lib/cli/run.js +176 -0
- package/lib/cli/status.js +125 -0
- package/lib/cli/stop.js +87 -0
- package/lib/config.js +180 -0
- package/lib/constants.js +58 -0
- package/lib/docker/manager.js +968 -0
- package/lib/index.js +26 -0
- package/lib/project.js +280 -0
- package/lib/tools/builtin.js +445 -0
- package/lib/tools/index.js +335 -0
- package/package.json +52 -0
|
@@ -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
|
+
};
|