dank-ai 1.0.46 → 1.0.49

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 CHANGED
@@ -70,6 +70,7 @@ dank logs assistant --follow
70
70
  ```
71
71
  my-project/
72
72
  ├── dank.config.js # Agent configuration
73
+ ├── .dankignore # Build ignore patterns (optional)
73
74
  ├── agents/ # Custom agent code (optional)
74
75
  │ └── example-agent.js
75
76
  └── .dank/ # Generated files
@@ -243,7 +244,7 @@ agent
243
244
 
244
245
  #### Passing Custom Data to Handlers
245
246
 
246
- You can pass any custom data in the request body to the `/prompt` endpoint, and it will be available in your handlers via `data.metadata`. This enables powerful use cases like user authentication, conversation tracking, RAG (Retrieval-Augmented Generation), and custom lookups.
247
+ You can pass any custom data in the request body to the `/prompt` endpoint, and it will be available in your handlers via `data.params`. This enables powerful use cases like user authentication, conversation tracking, RAG (Retrieval-Augmented Generation), and custom lookups.
247
248
 
248
249
  **Client Request:**
249
250
  ```javascript
@@ -264,9 +265,9 @@ You can pass any custom data in the request body to the `/prompt` endpoint, and
264
265
  ```javascript
265
266
  agent
266
267
  .addHandler('request_output:start', async (data) => {
267
- // Access custom data via data.metadata
268
- const userId = data.metadata.userId;
269
- const conversationId = data.metadata.conversationId;
268
+ // Access custom data via data.params
269
+ const userId = data.params.userId;
270
+ const conversationId = data.params.conversationId;
270
271
 
271
272
  // Perform authentication
272
273
  const user = await authenticateUser(userId);
@@ -287,16 +288,16 @@ agent
287
288
  .addHandler('request_output', async (data) => {
288
289
  // Log with user context
289
290
  await logInteraction({
290
- userId: data.metadata.userId,
291
- conversationId: data.metadata.conversationId,
291
+ userId: data.params.userId,
292
+ conversationId: data.params.conversationId,
292
293
  prompt: data.prompt,
293
294
  response: data.response,
294
295
  timestamp: data.timestamp
295
296
  });
296
297
 
297
298
  // Update user preferences based on interaction
298
- if (data.metadata.userPreferences) {
299
- await updateUserPreferences(data.metadata.userId, data.metadata.userPreferences);
299
+ if (data.params.userPreferences) {
300
+ await updateUserPreferences(data.params.userId, data.params.userPreferences);
300
301
  }
301
302
  });
302
303
  ```
@@ -495,6 +496,68 @@ Dank uses a layered Docker approach:
495
496
  2. **Agent Images**: Extend base image with agent-specific code
496
497
  3. **Containers**: Running instances with resource limits and networking
497
498
 
499
+ ### Build File Management (.dankignore)
500
+
501
+ Dank automatically copies files from your project directory into Docker containers during the build process. Use `.dankignore` to control which files are included.
502
+
503
+ **Default Behavior:**
504
+ - If `.dankignore` doesn't exist: **All files** are copied to the container
505
+ - If `.dankignore` exists: Only files **not matching** the patterns are copied
506
+
507
+ **Creating `.dankignore`:**
508
+ ```bash
509
+ # Created automatically during dank init
510
+ dank init my-project
511
+
512
+ # Or create manually
513
+ touch .dankignore
514
+ ```
515
+
516
+ **Example `.dankignore`:**
517
+ ```
518
+ # Security - Environment variables (IMPORTANT: Never commit .env files!)
519
+ .env
520
+ .env.*
521
+ *.key
522
+ *.pem
523
+ secrets/
524
+
525
+ # Dependencies (installed fresh in container)
526
+ node_modules/
527
+
528
+ # Version control
529
+ .git/
530
+
531
+ # Build artifacts
532
+ dist/
533
+ build/
534
+ coverage/
535
+
536
+ # OS files
537
+ .DS_Store
538
+ Thumbs.db
539
+
540
+ # IDE files
541
+ .vscode/
542
+ .idea/
543
+
544
+ # Logs
545
+ *.log
546
+ logs/
547
+ ```
548
+
549
+ **Pattern Matching:**
550
+ - `node_modules` - Exact match
551
+ - `*.log` - Wildcard (matches any `.log` file)
552
+ - `dist/` - Directory pattern (matches `dist` directory and contents)
553
+ - `.env.*` - Pattern matching (matches `.env.local`, `.env.production`, etc.)
554
+
555
+ **Best Practices:**
556
+ - ✅ Always exclude `.env` files (security)
557
+ - ✅ Exclude `node_modules/` (dependencies installed in container)
558
+ - ✅ Exclude build artifacts (`dist/`, `build/`)
559
+ - ✅ Include source files, assets, and configuration needed at runtime
560
+
498
561
  ### Container Features
499
562
  - **Isolated Environments**: Each agent runs in its own container
500
563
  - **Resource Limits**: Memory and CPU constraints per agent
@@ -780,7 +843,7 @@ docker logs container-id
780
843
  - **Authentication**: `docker login ghcr.io`
781
844
  - **Push Permissions**: Check namespace permissions
782
845
  - **Image Exists**: Use different tag or `--force`
783
- - **Build Context**: Add `.dockerignore` file
846
+ - **Build Context**: Add `.dankignore` file to control which files are copied
784
847
  </details>
785
848
 
786
849
  ## 📦 Package Exports
@@ -1218,12 +1218,12 @@ class AgentRuntime {
1218
1218
  });
1219
1219
  }
1220
1220
 
1221
- // Build metadata object with all user-provided fields from request body (except prompt)
1222
- const metadata = {
1221
+ // Build params object with all user-provided fields from request body (except prompt)
1222
+ const params = {
1223
1223
  ...requestBodyFields,
1224
1224
  };
1225
1225
 
1226
- const response = await this.processDirectPrompt(prompt, metadata, {
1226
+ const response = await this.processDirectPrompt(prompt, params, {
1227
1227
  protocol: "http",
1228
1228
  clientIp: req.ip,
1229
1229
  });
@@ -1251,7 +1251,7 @@ class AgentRuntime {
1251
1251
  /**
1252
1252
  * Process a direct prompt and emit events
1253
1253
  */
1254
- async processDirectPrompt(prompt, metadata = {}, systemFields = {}) {
1254
+ async processDirectPrompt(prompt, params = {}, systemFields = {}) {
1255
1255
  const startTime = Date.now();
1256
1256
  let finalPrompt = prompt; // Declare outside try block so it's available in catch
1257
1257
 
@@ -1259,7 +1259,7 @@ class AgentRuntime {
1259
1259
  // Emit request start event and allow handlers to modify the prompt
1260
1260
  const startEventData = {
1261
1261
  prompt,
1262
- metadata,
1262
+ params,
1263
1263
  ...systemFields, // protocol, clientIp, etc.
1264
1264
  timestamp: new Date().toISOString(),
1265
1265
  };
@@ -1302,7 +1302,7 @@ class AgentRuntime {
1302
1302
  prompt,
1303
1303
  finalPrompt, // Include the final prompt that was sent to LLM
1304
1304
  response: response.content,
1305
- metadata,
1305
+ params,
1306
1306
  ...systemFields, // protocol, clientIp, etc.
1307
1307
  usage: response.usage,
1308
1308
  model: response.model,
@@ -1316,7 +1316,7 @@ class AgentRuntime {
1316
1316
  prompt,
1317
1317
  finalPrompt,
1318
1318
  response: response.content,
1319
- metadata,
1319
+ params,
1320
1320
  ...systemFields, // protocol, clientIp, etc.
1321
1321
  usage: response.usage,
1322
1322
  model: response.model,
@@ -1347,7 +1347,7 @@ class AgentRuntime {
1347
1347
  await this.emitEvent("request_output:error", {
1348
1348
  prompt,
1349
1349
  finalPrompt,
1350
- metadata,
1350
+ params,
1351
1351
  ...systemFields, // protocol, clientIp, etc.
1352
1352
  error: error.message,
1353
1353
  processingTime,
package/lib/cli/init.js CHANGED
@@ -84,6 +84,9 @@ async function initCommand(projectName, options) {
84
84
  // Create .gitignore
85
85
  await createGitignore(project.projectPath);
86
86
 
87
+ // Create .dankignore
88
+ await createDankIgnore(project.projectPath);
89
+
87
90
  // Create README.md
88
91
  await createReadme(name, project.projectPath);
89
92
 
@@ -260,6 +263,65 @@ jspm_packages/
260
263
  console.log(chalk.green(`Created .gitignore: ${gitignorePath}`));
261
264
  }
262
265
 
266
+ /**
267
+ * Create .dankignore file
268
+ */
269
+ async function createDankIgnore(projectPath) {
270
+ const dankIgnore = `# Dank Build Ignore Patterns
271
+ # Files and directories to exclude from Docker builds
272
+ # This file is optional - if it doesn't exist, all files will be copied
273
+
274
+ # Security - Environment variables (IMPORTANT: Never commit .env files!)
275
+ .env
276
+ .env.*
277
+ *.key
278
+ *.pem
279
+ secrets/
280
+
281
+ # Dependencies (installed fresh in container)
282
+ node_modules/
283
+
284
+ # Version control
285
+ .git/
286
+
287
+ # Build artifacts
288
+ dist/
289
+ build/
290
+ coverage/
291
+ .nyc_output/
292
+
293
+ # OS files
294
+ .DS_Store
295
+ Thumbs.db
296
+ *.swp
297
+ *.swo
298
+ *~
299
+
300
+ # IDE files
301
+ .vscode/
302
+ .idea/
303
+ *.sublime-*
304
+ *.code-workspace
305
+
306
+ # Logs
307
+ *.log
308
+ logs/
309
+
310
+ # Dank framework
311
+ .dank/
312
+ .build-context-*
313
+
314
+ # Temporary files
315
+ *.tmp
316
+ *.temp
317
+ .cache/
318
+ `;
319
+
320
+ const dankIgnorePath = path.join(projectPath, '.dankignore');
321
+ await fs.writeFile(dankIgnorePath, dankIgnore, 'utf8');
322
+ console.log(chalk.green(`Created .dankignore: ${dankIgnorePath}`));
323
+ }
324
+
263
325
  /**
264
326
  * Create README.md for the project
265
327
  */
@@ -1759,6 +1759,141 @@ class DockerManager {
1759
1759
  * @param {object} options - Additional options
1760
1760
  * @param {string} options.projectRoot - Root directory for package.json (defaults to process.cwd())
1761
1761
  */
1762
+ /**
1763
+ * Read .dankignore file if it exists
1764
+ * @param {string} projectDir - Project directory
1765
+ * @returns {string[]} Array of ignore patterns
1766
+ */
1767
+ async readDankIgnore(projectDir) {
1768
+ const dankIgnorePath = path.join(projectDir, '.dankignore');
1769
+
1770
+ if (!(await fs.pathExists(dankIgnorePath))) {
1771
+ // No .dankignore = copy everything
1772
+ return [];
1773
+ }
1774
+
1775
+ try {
1776
+ const content = await fs.readFile(dankIgnorePath, 'utf8');
1777
+ return content
1778
+ .split('\n')
1779
+ .map(line => line.trim())
1780
+ .filter(line => {
1781
+ // Remove comments and empty lines
1782
+ return line && !line.startsWith('#');
1783
+ });
1784
+ } catch (error) {
1785
+ this.logger.warn(`⚠️ Failed to read .dankignore: ${error.message}`);
1786
+ return [];
1787
+ }
1788
+ }
1789
+
1790
+ /**
1791
+ * Check if a file path should be ignored based on .dankignore patterns
1792
+ * @param {string} filePath - Full path to the file
1793
+ * @param {string} projectDir - Project root directory
1794
+ * @param {string[]} ignorePatterns - Patterns from .dankignore
1795
+ * @returns {boolean} True if file should be ignored
1796
+ */
1797
+ shouldIgnoreFile(filePath, projectDir, ignorePatterns) {
1798
+ if (ignorePatterns.length === 0) {
1799
+ return false; // No patterns = don't ignore anything
1800
+ }
1801
+
1802
+ // Get relative path from project directory
1803
+ const relativePath = path.relative(projectDir, filePath);
1804
+ const normalizedPath = relativePath.replace(/\\/g, '/'); // Normalize to forward slashes
1805
+
1806
+ // Also check just the filename for patterns like "*.log"
1807
+ const fileName = path.basename(filePath);
1808
+
1809
+ for (const pattern of ignorePatterns) {
1810
+ // Skip package.json and package-lock.json as they're handled separately
1811
+ if (pattern === 'package.json' || pattern === 'package-lock.json') {
1812
+ continue;
1813
+ }
1814
+
1815
+ // Convert pattern to regex
1816
+ // Support:
1817
+ // - Exact matches: "node_modules"
1818
+ // - Wildcards: "*.log", ".env.*"
1819
+ // - Directory patterns: "dist/", "node_modules/"
1820
+ // - Path patterns: "secrets/*.key"
1821
+
1822
+ // Use a placeholder for * to avoid escaping issues
1823
+ const placeholder = '__WILDCARD_PLACEHOLDER__';
1824
+ let regexPattern = pattern.replace(/\*/g, placeholder);
1825
+
1826
+ // Escape special regex characters (except our placeholder)
1827
+ regexPattern = regexPattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&');
1828
+
1829
+ // Replace placeholder with .* for wildcard matching
1830
+ regexPattern = regexPattern.replace(new RegExp(placeholder.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g'), '.*');
1831
+
1832
+ // If pattern ends with /, it's a directory pattern
1833
+ const isDirectoryPattern = pattern.endsWith('/');
1834
+
1835
+ // For directory patterns, match anywhere in path
1836
+ // For file patterns, match full path or filename
1837
+ if (isDirectoryPattern) {
1838
+ // Remove trailing / from pattern for matching
1839
+ const dirPattern = regexPattern.replace(/\/$/, '');
1840
+ // Match if path contains this directory (at any level)
1841
+ const dirRegex = new RegExp(`(^|/)${dirPattern}(/|$)`);
1842
+ if (dirRegex.test(normalizedPath)) {
1843
+ return true;
1844
+ }
1845
+ } else {
1846
+ // File pattern - check full path and filename
1847
+ const fileRegex = new RegExp(`^${regexPattern}$`);
1848
+ if (fileRegex.test(normalizedPath) || fileRegex.test(fileName)) {
1849
+ return true;
1850
+ }
1851
+
1852
+ // Also check if pattern matches any path segment
1853
+ const pathSegments = normalizedPath.split('/');
1854
+ for (const segment of pathSegments) {
1855
+ if (fileRegex.test(segment)) {
1856
+ return true;
1857
+ }
1858
+ }
1859
+ }
1860
+ }
1861
+
1862
+ return false;
1863
+ }
1864
+
1865
+ /**
1866
+ * Recursively copy files respecting .dankignore patterns
1867
+ * @param {string} sourceDir - Source directory
1868
+ * @param {string} destDir - Destination directory
1869
+ * @param {string} projectDir - Project root (for relative path calculation)
1870
+ * @param {string[]} ignorePatterns - Patterns from .dankignore
1871
+ */
1872
+ async copyFilesRecursive(sourceDir, destDir, projectDir, ignorePatterns) {
1873
+ const items = await fs.readdir(sourceDir);
1874
+
1875
+ for (const item of items) {
1876
+ const sourcePath = path.join(sourceDir, item);
1877
+ const destPath = path.join(destDir, item);
1878
+
1879
+ // Check if this file/directory should be ignored
1880
+ if (this.shouldIgnoreFile(sourcePath, projectDir, ignorePatterns)) {
1881
+ continue;
1882
+ }
1883
+
1884
+ const stat = await fs.stat(sourcePath);
1885
+
1886
+ if (stat.isDirectory()) {
1887
+ // Recursively copy directory
1888
+ await fs.ensureDir(destPath);
1889
+ await this.copyFilesRecursive(sourcePath, destPath, projectDir, ignorePatterns);
1890
+ } else if (stat.isFile()) {
1891
+ // Copy file
1892
+ await fs.copy(sourcePath, destPath);
1893
+ }
1894
+ }
1895
+ }
1896
+
1762
1897
  async copyProjectFiles(projectDir, contextDir, options = {}) {
1763
1898
  const agentCodeDir = path.join(contextDir, "agent-code");
1764
1899
  await fs.ensureDir(agentCodeDir);
@@ -1783,53 +1918,22 @@ class DockerManager {
1783
1918
  this.logger.warn(`⚠️ Failed to copy package files: ${error.message}`);
1784
1919
  }
1785
1920
 
1786
- // Patterns to exclude when copying project files
1787
- const ignorePatterns = [
1788
- 'node_modules',
1789
- '.git',
1790
- '.build-context-*',
1791
- '.env',
1792
- '.env.*',
1793
- '*.log',
1794
- '.DS_Store',
1795
- 'dist',
1796
- 'build',
1797
- '.dank',
1798
- 'coverage',
1799
- '.nyc_output',
1800
- 'package.json', // Already copied from project root
1801
- 'package-lock.json' // Already copied from project root
1802
- ];
1921
+ // Read .dankignore patterns from project root (optional - if file doesn't exist, copy everything)
1922
+ // Check both projectRoot and projectDir for .dankignore
1923
+ let ignorePatterns = await this.readDankIgnore(projectRoot);
1924
+ if (ignorePatterns.length === 0 && projectDir !== projectRoot) {
1925
+ ignorePatterns = await this.readDankIgnore(projectDir);
1926
+ }
1927
+
1928
+ if (ignorePatterns.length > 0) {
1929
+ this.logger.info(`📋 Using .dankignore with ${ignorePatterns.length} pattern(s)`);
1930
+ } else {
1931
+ this.logger.info(`📋 No .dankignore found - copying all files from ${projectDir}`);
1932
+ }
1803
1933
 
1804
1934
  try {
1805
- // Copy all files from project directory, filtering out ignored patterns
1806
- const items = await fs.readdir(projectDir);
1807
-
1808
- for (const item of items) {
1809
- const sourcePath = path.join(projectDir, item);
1810
- const destPath = path.join(agentCodeDir, item);
1811
- const stat = await fs.stat(sourcePath);
1812
-
1813
- // Skip if matches ignore pattern
1814
- const shouldIgnore = ignorePatterns.some(pattern => {
1815
- if (pattern.includes('*')) {
1816
- const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
1817
- return regex.test(item);
1818
- }
1819
- return item === pattern;
1820
- });
1821
-
1822
- if (shouldIgnore) {
1823
- continue;
1824
- }
1825
-
1826
- // Copy file or directory
1827
- if (stat.isDirectory()) {
1828
- await fs.copy(sourcePath, destPath);
1829
- } else if (stat.isFile()) {
1830
- await fs.copy(sourcePath, destPath);
1831
- }
1832
- }
1935
+ // Copy files recursively, respecting .dankignore patterns
1936
+ await this.copyFilesRecursive(projectDir, agentCodeDir, projectDir, ignorePatterns);
1833
1937
 
1834
1938
  this.logger.info(`📁 Copied project files from ${projectDir} to build context`);
1835
1939
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dank-ai",
3
- "version": "1.0.46",
3
+ "version": "1.0.49",
4
4
  "description": "Dank Agent Service - Docker-based AI agent orchestration platform",
5
5
  "main": "lib/index.js",
6
6
  "exports": {