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 +64 -1
- package/lib/cli/init.js +62 -0
- package/lib/docker/manager.js +186 -45
- package/package.json +1 -1
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 `.
|
|
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
|
*/
|
package/lib/docker/manager.js
CHANGED
|
@@ -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
|
-
//
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
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
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
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
|
-
|
|
1809
|
-
const
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
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
|
|