dank-ai 1.0.48 → 1.0.50

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
@@ -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
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,45 @@ 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
+ }
1803
1927
 
1804
- try {
1805
- // Copy all files from project directory, filtering out ignored patterns
1806
- const items = await fs.readdir(projectDir);
1928
+ // Explicitly copy .env file from project root if it exists
1929
+ // This ensures .env is available at runtime even if it's filtered by .dankignore
1930
+ // We copy to context root (not agent-code/) so it ends up at /app/.env where dotenv.config() looks
1931
+ const envFilePath = path.join(projectRoot, '.env');
1932
+ this.logger.info(`🔍 Checking for .env file at: ${envFilePath}`);
1933
+
1934
+ if (await fs.pathExists(envFilePath)) {
1935
+ this.logger.info(`✅ .env file found at project root`);
1936
+ const shouldIgnore = this.shouldIgnoreFile(envFilePath, projectRoot, ignorePatterns);
1807
1937
 
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
- }
1938
+ if (!shouldIgnore) {
1939
+ const destPath = path.join(contextDir, '.env');
1940
+ await fs.copy(envFilePath, destPath);
1941
+ this.logger.info(`📝 Copied .env from project root to context root`);
1942
+ } else {
1943
+ this.logger.info(`⏭️ Skipped .env (matched .dankignore pattern)`);
1944
+ this.logger.info(` To include .env, remove it from .dankignore`);
1832
1945
  }
1946
+ } else {
1947
+ this.logger.info(`⚠️ .env file not found at project root: ${envFilePath}`);
1948
+ this.logger.info(` Environment variables will not be available at runtime`);
1949
+ }
1950
+
1951
+ if (ignorePatterns.length > 0) {
1952
+ this.logger.info(`📋 Using .dankignore with ${ignorePatterns.length} pattern(s)`);
1953
+ } else {
1954
+ this.logger.info(`📋 No .dankignore found - copying all files from ${projectDir}`);
1955
+ }
1956
+
1957
+ try {
1958
+ // Copy files recursively, respecting .dankignore patterns
1959
+ await this.copyFilesRecursive(projectDir, agentCodeDir, projectDir, ignorePatterns);
1833
1960
 
1834
1961
  this.logger.info(`📁 Copied project files from ${projectDir} to build context`);
1835
1962
  } catch (error) {
@@ -1911,11 +2038,25 @@ WORKDIR /app
1911
2038
  ` : '';
1912
2039
 
1913
2040
  // Create Dockerfile for agent
2041
+ // Copy .env file to /app/ (where entrypoint runs) so dotenv.config() can find it
2042
+ const envFileInContext = path.join(contextDir, '.env');
2043
+ const hasEnvFile = await fs.pathExists(envFileInContext);
2044
+
2045
+ if (hasEnvFile) {
2046
+ this.logger.info(`📝 .env file found in build context, will be copied to /app/.env in container`);
2047
+ } else {
2048
+ this.logger.info(`⚠️ .env file not found in build context, will not be available in container`);
2049
+ }
2050
+
2051
+ const envCopyStep = hasEnvFile
2052
+ ? `# Copy .env file to /app/ (where entrypoint runs)\nCOPY .env /app/.env\n`
2053
+ : '';
2054
+
1914
2055
  const dockerfile = `FROM ${baseImageName}
1915
2056
  ${npmInstallStep}
1916
2057
  # Copy agent code
1917
2058
  COPY agent-code/ /app/agent-code/
1918
- ${envStatements}
2059
+ ${envCopyStep}${envStatements}
1919
2060
  USER dankuser
1920
2061
  `;
1921
2062
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dank-ai",
3
- "version": "1.0.48",
3
+ "version": "1.0.50",
4
4
  "description": "Dank Agent Service - Docker-based AI agent orchestration platform",
5
5
  "main": "lib/index.js",
6
6
  "exports": {