neuro-cli 4.1.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/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- package/package.json +83 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
export interface SandboxConfig {
|
|
2
|
+
/** Whether sandbox mode is enabled */
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
/** Root directory - only files under this dir can be modified */
|
|
5
|
+
rootDir: string;
|
|
6
|
+
/** Allowed directories (in addition to rootDir) */
|
|
7
|
+
allowedDirs: string[];
|
|
8
|
+
/** Denied directories (even under rootDir) */
|
|
9
|
+
deniedDirs: string[];
|
|
10
|
+
/** Denied file patterns (glob) */
|
|
11
|
+
deniedPatterns: string[];
|
|
12
|
+
/** Whether to allow command execution */
|
|
13
|
+
allowCommands: boolean;
|
|
14
|
+
/** Allowed commands (whitelist) */
|
|
15
|
+
allowedCommands: string[];
|
|
16
|
+
/** Denied commands (blacklist) */
|
|
17
|
+
deniedCommands: string[];
|
|
18
|
+
/** Whether to create backups before modifications */
|
|
19
|
+
backupOnModify: boolean;
|
|
20
|
+
/** Backup directory */
|
|
21
|
+
backupDir: string;
|
|
22
|
+
/** Maximum file size that can be written (bytes) */
|
|
23
|
+
maxFileSize: number;
|
|
24
|
+
/** Whether to allow network access */
|
|
25
|
+
allowNetwork: boolean;
|
|
26
|
+
/** Whether to allow environment variable access */
|
|
27
|
+
allowEnvAccess: boolean;
|
|
28
|
+
/** Read-only mode (no modifications at all) */
|
|
29
|
+
readOnly: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface SandboxViolation {
|
|
32
|
+
type: 'file_write' | 'file_delete' | 'file_read' | 'command' | 'network' | 'env';
|
|
33
|
+
path?: string;
|
|
34
|
+
command?: string;
|
|
35
|
+
reason: string;
|
|
36
|
+
timestamp: number;
|
|
37
|
+
}
|
|
38
|
+
export declare const DEFAULT_SANDBOX_CONFIG: SandboxConfig;
|
|
39
|
+
export declare class Sandbox {
|
|
40
|
+
private config;
|
|
41
|
+
private violations;
|
|
42
|
+
private backups;
|
|
43
|
+
private originalContents;
|
|
44
|
+
constructor(config?: Partial<SandboxConfig>);
|
|
45
|
+
/**
|
|
46
|
+
* Check if a file path is allowed for reading
|
|
47
|
+
*/
|
|
48
|
+
canRead(filePath: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Check if a file path is allowed for writing
|
|
51
|
+
*/
|
|
52
|
+
canWrite(filePath: string, contentSize?: number): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Check if a file can be deleted
|
|
55
|
+
*/
|
|
56
|
+
canDelete(filePath: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Check if a command is allowed
|
|
59
|
+
*/
|
|
60
|
+
canRunCommand(command: string): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Check if network access is allowed
|
|
63
|
+
*/
|
|
64
|
+
canAccessNetwork(): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Check if environment variable access is allowed
|
|
67
|
+
*/
|
|
68
|
+
canAccessEnv(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Create a backup of a file before modification
|
|
71
|
+
*/
|
|
72
|
+
backupFile(filePath: string): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Undo a file modification by restoring from backup
|
|
75
|
+
*/
|
|
76
|
+
undoFile(filePath: string): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* Undo all modifications made in this session
|
|
79
|
+
*/
|
|
80
|
+
undoAll(): number;
|
|
81
|
+
/**
|
|
82
|
+
* Get all violations recorded
|
|
83
|
+
*/
|
|
84
|
+
getViolations(): SandboxViolation[];
|
|
85
|
+
/**
|
|
86
|
+
* Get the number of violations
|
|
87
|
+
*/
|
|
88
|
+
getViolationCount(): number;
|
|
89
|
+
/**
|
|
90
|
+
* Clear all violations
|
|
91
|
+
*/
|
|
92
|
+
clearViolations(): void;
|
|
93
|
+
/**
|
|
94
|
+
* Enable sandbox mode
|
|
95
|
+
*/
|
|
96
|
+
enable(): void;
|
|
97
|
+
/**
|
|
98
|
+
* Disable sandbox mode
|
|
99
|
+
*/
|
|
100
|
+
disable(): void;
|
|
101
|
+
/**
|
|
102
|
+
* Check if sandbox is enabled
|
|
103
|
+
*/
|
|
104
|
+
isEnabled(): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Toggle sandbox mode
|
|
107
|
+
*/
|
|
108
|
+
toggle(): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Get current sandbox configuration
|
|
111
|
+
*/
|
|
112
|
+
getConfig(): SandboxConfig;
|
|
113
|
+
/**
|
|
114
|
+
* Update sandbox configuration
|
|
115
|
+
*/
|
|
116
|
+
updateConfig(updates: Partial<SandboxConfig>): void;
|
|
117
|
+
/**
|
|
118
|
+
* Print sandbox status
|
|
119
|
+
*/
|
|
120
|
+
printStatus(): void;
|
|
121
|
+
private resolvePath;
|
|
122
|
+
private isUnderRootDir;
|
|
123
|
+
private isInDeniedDir;
|
|
124
|
+
private matchesDeniedPattern;
|
|
125
|
+
private recordViolation;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=sandbox.d.ts.map
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - Sandbox Mode
|
|
3
|
+
// File isolation and safe execution environment
|
|
4
|
+
// Prevents modifications outside the project directory
|
|
5
|
+
// ============================================================
|
|
6
|
+
import { join, resolve, relative, normalize } from 'path';
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync } from 'fs';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
export const DEFAULT_SANDBOX_CONFIG = {
|
|
10
|
+
enabled: false,
|
|
11
|
+
rootDir: process.cwd(),
|
|
12
|
+
allowedDirs: [],
|
|
13
|
+
deniedDirs: [
|
|
14
|
+
'node_modules',
|
|
15
|
+
'.git',
|
|
16
|
+
'__pycache__',
|
|
17
|
+
'.env',
|
|
18
|
+
],
|
|
19
|
+
deniedPatterns: [
|
|
20
|
+
'**/.env',
|
|
21
|
+
'**/.env.*',
|
|
22
|
+
'**/id_rsa*',
|
|
23
|
+
'**/id_ed25519*',
|
|
24
|
+
'**/*.pem',
|
|
25
|
+
'**/*.key',
|
|
26
|
+
'**/credentials.json',
|
|
27
|
+
'**/secrets.*',
|
|
28
|
+
],
|
|
29
|
+
allowCommands: true,
|
|
30
|
+
allowedCommands: [],
|
|
31
|
+
deniedCommands: [
|
|
32
|
+
'rm -rf /',
|
|
33
|
+
'sudo rm',
|
|
34
|
+
'mkfs',
|
|
35
|
+
'dd if=',
|
|
36
|
+
':(){ :|:& };:', // fork bomb
|
|
37
|
+
'chmod 777 /',
|
|
38
|
+
'curl | sh',
|
|
39
|
+
'wget | sh',
|
|
40
|
+
],
|
|
41
|
+
backupOnModify: true,
|
|
42
|
+
backupDir: '.neuro/backups',
|
|
43
|
+
maxFileSize: 10 * 1024 * 1024, // 10MB
|
|
44
|
+
allowNetwork: true,
|
|
45
|
+
allowEnvAccess: false,
|
|
46
|
+
readOnly: false,
|
|
47
|
+
};
|
|
48
|
+
export class Sandbox {
|
|
49
|
+
config;
|
|
50
|
+
violations = [];
|
|
51
|
+
backups = new Map(); // original path -> backup path
|
|
52
|
+
originalContents = new Map(); // for undo
|
|
53
|
+
constructor(config) {
|
|
54
|
+
this.config = { ...DEFAULT_SANDBOX_CONFIG, ...config };
|
|
55
|
+
if (!this.config.rootDir)
|
|
56
|
+
this.config.rootDir = process.cwd();
|
|
57
|
+
this.config.rootDir = resolve(this.config.rootDir);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Check if a file path is allowed for reading
|
|
61
|
+
*/
|
|
62
|
+
canRead(filePath) {
|
|
63
|
+
if (!this.config.enabled)
|
|
64
|
+
return true;
|
|
65
|
+
const absPath = this.resolvePath(filePath);
|
|
66
|
+
// Check denied dirs
|
|
67
|
+
if (this.isInDeniedDir(absPath)) {
|
|
68
|
+
this.recordViolation('file_read', absPath, 'Access denied: path is in a denied directory');
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
// Check denied patterns
|
|
72
|
+
if (this.matchesDeniedPattern(absPath)) {
|
|
73
|
+
this.recordViolation('file_read', absPath, 'Access denied: path matches a denied pattern');
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if a file path is allowed for writing
|
|
80
|
+
*/
|
|
81
|
+
canWrite(filePath, contentSize) {
|
|
82
|
+
if (!this.config.enabled)
|
|
83
|
+
return true;
|
|
84
|
+
if (this.config.readOnly) {
|
|
85
|
+
this.recordViolation('file_write', this.resolvePath(filePath), 'Write denied: sandbox is in read-only mode');
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
const absPath = this.resolvePath(filePath);
|
|
89
|
+
// Must be under rootDir
|
|
90
|
+
if (!this.isUnderRootDir(absPath)) {
|
|
91
|
+
this.recordViolation('file_write', absPath, `Write denied: path is outside project directory (${this.config.rootDir})`);
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
// Check denied dirs
|
|
95
|
+
if (this.isInDeniedDir(absPath)) {
|
|
96
|
+
this.recordViolation('file_write', absPath, 'Write denied: path is in a denied directory');
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
// Check denied patterns
|
|
100
|
+
if (this.matchesDeniedPattern(absPath)) {
|
|
101
|
+
this.recordViolation('file_write', absPath, 'Write denied: path matches a denied pattern');
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
// Check file size
|
|
105
|
+
if (contentSize !== undefined && contentSize > this.config.maxFileSize) {
|
|
106
|
+
this.recordViolation('file_write', absPath, `Write denied: file size (${contentSize} bytes) exceeds maximum (${this.config.maxFileSize} bytes)`);
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Check if a file can be deleted
|
|
113
|
+
*/
|
|
114
|
+
canDelete(filePath) {
|
|
115
|
+
if (!this.config.enabled)
|
|
116
|
+
return true;
|
|
117
|
+
if (this.config.readOnly) {
|
|
118
|
+
this.recordViolation('file_delete', this.resolvePath(filePath), 'Delete denied: sandbox is in read-only mode');
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
const absPath = this.resolvePath(filePath);
|
|
122
|
+
if (!this.isUnderRootDir(absPath)) {
|
|
123
|
+
this.recordViolation('file_delete', absPath, 'Delete denied: path is outside project directory');
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
if (this.isInDeniedDir(absPath)) {
|
|
127
|
+
this.recordViolation('file_delete', absPath, 'Delete denied: path is in a denied directory');
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Check if a command is allowed
|
|
134
|
+
*/
|
|
135
|
+
canRunCommand(command) {
|
|
136
|
+
if (!this.config.enabled)
|
|
137
|
+
return true;
|
|
138
|
+
if (!this.config.allowCommands) {
|
|
139
|
+
this.recordViolation('command', undefined, `Command denied: command execution is disabled`);
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
// Check denied commands
|
|
143
|
+
const cmdBase = command.trim().split(/\s+/)[0];
|
|
144
|
+
for (const denied of this.config.deniedCommands) {
|
|
145
|
+
if (command.includes(denied) || cmdBase === denied.split(' ')[0]) {
|
|
146
|
+
this.recordViolation('command', undefined, `Command denied: matches denied pattern "${denied}"`);
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Check allowed commands whitelist (if set)
|
|
151
|
+
if (this.config.allowedCommands.length > 0) {
|
|
152
|
+
const isAllowed = this.config.allowedCommands.some(allowed => cmdBase === allowed || command.startsWith(allowed));
|
|
153
|
+
if (!isAllowed) {
|
|
154
|
+
this.recordViolation('command', undefined, `Command denied: "${cmdBase}" is not in the allowed commands list`);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Check if network access is allowed
|
|
162
|
+
*/
|
|
163
|
+
canAccessNetwork() {
|
|
164
|
+
if (!this.config.enabled)
|
|
165
|
+
return true;
|
|
166
|
+
if (!this.config.allowNetwork) {
|
|
167
|
+
this.recordViolation('network', undefined, 'Network access denied');
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Check if environment variable access is allowed
|
|
174
|
+
*/
|
|
175
|
+
canAccessEnv() {
|
|
176
|
+
if (!this.config.enabled)
|
|
177
|
+
return true;
|
|
178
|
+
if (!this.config.allowEnvAccess) {
|
|
179
|
+
this.recordViolation('env', undefined, 'Environment variable access denied');
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Create a backup of a file before modification
|
|
186
|
+
*/
|
|
187
|
+
backupFile(filePath) {
|
|
188
|
+
if (!this.config.backupOnModify)
|
|
189
|
+
return true;
|
|
190
|
+
const absPath = this.resolvePath(filePath);
|
|
191
|
+
if (!existsSync(absPath))
|
|
192
|
+
return true;
|
|
193
|
+
try {
|
|
194
|
+
const backupDir = join(this.config.rootDir, this.config.backupDir);
|
|
195
|
+
if (!existsSync(backupDir))
|
|
196
|
+
mkdirSync(backupDir, { recursive: true });
|
|
197
|
+
const relPath = relative(this.config.rootDir, absPath);
|
|
198
|
+
const backupPath = join(backupDir, `${Date.now()}_${relPath.replace(/\//g, '_')}`);
|
|
199
|
+
copyFileSync(absPath, backupPath);
|
|
200
|
+
this.backups.set(absPath, backupPath);
|
|
201
|
+
// Store original content for undo
|
|
202
|
+
const content = readFileSync(absPath, 'utf-8');
|
|
203
|
+
this.originalContents.set(absPath, content);
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Undo a file modification by restoring from backup
|
|
212
|
+
*/
|
|
213
|
+
undoFile(filePath) {
|
|
214
|
+
const absPath = this.resolvePath(filePath);
|
|
215
|
+
const originalContent = this.originalContents.get(absPath);
|
|
216
|
+
if (originalContent === undefined)
|
|
217
|
+
return false;
|
|
218
|
+
try {
|
|
219
|
+
writeFileSync(absPath, originalContent, 'utf-8');
|
|
220
|
+
this.originalContents.delete(absPath);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Undo all modifications made in this session
|
|
229
|
+
*/
|
|
230
|
+
undoAll() {
|
|
231
|
+
let undone = 0;
|
|
232
|
+
for (const [path] of this.originalContents) {
|
|
233
|
+
if (this.undoFile(path))
|
|
234
|
+
undone++;
|
|
235
|
+
}
|
|
236
|
+
return undone;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Get all violations recorded
|
|
240
|
+
*/
|
|
241
|
+
getViolations() {
|
|
242
|
+
return [...this.violations];
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Get the number of violations
|
|
246
|
+
*/
|
|
247
|
+
getViolationCount() {
|
|
248
|
+
return this.violations.length;
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Clear all violations
|
|
252
|
+
*/
|
|
253
|
+
clearViolations() {
|
|
254
|
+
this.violations = [];
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Enable sandbox mode
|
|
258
|
+
*/
|
|
259
|
+
enable() {
|
|
260
|
+
this.config.enabled = true;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Disable sandbox mode
|
|
264
|
+
*/
|
|
265
|
+
disable() {
|
|
266
|
+
this.config.enabled = false;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Check if sandbox is enabled
|
|
270
|
+
*/
|
|
271
|
+
isEnabled() {
|
|
272
|
+
return this.config.enabled;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Toggle sandbox mode
|
|
276
|
+
*/
|
|
277
|
+
toggle() {
|
|
278
|
+
this.config.enabled = !this.config.enabled;
|
|
279
|
+
return this.config.enabled;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Get current sandbox configuration
|
|
283
|
+
*/
|
|
284
|
+
getConfig() {
|
|
285
|
+
return { ...this.config };
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Update sandbox configuration
|
|
289
|
+
*/
|
|
290
|
+
updateConfig(updates) {
|
|
291
|
+
Object.assign(this.config, updates);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Print sandbox status
|
|
295
|
+
*/
|
|
296
|
+
printStatus() {
|
|
297
|
+
const status = this.config.enabled ? chalk.green('ENABLED') : chalk.gray('DISABLED');
|
|
298
|
+
console.log(chalk.bold(`\n Sandbox: ${status}`));
|
|
299
|
+
if (this.config.enabled) {
|
|
300
|
+
console.log(` Root: ${chalk.cyan(this.config.rootDir)}`);
|
|
301
|
+
console.log(` Read-only: ${this.config.readOnly ? chalk.red('YES') : chalk.green('NO')}`);
|
|
302
|
+
console.log(` Commands: ${this.config.allowCommands ? chalk.green('allowed') : chalk.red('denied')}`);
|
|
303
|
+
console.log(` Network: ${this.config.allowNetwork ? chalk.green('allowed') : chalk.red('denied')}`);
|
|
304
|
+
console.log(` Backups: ${this.config.backupOnModify ? chalk.green('enabled') : chalk.gray('disabled')}`);
|
|
305
|
+
console.log(` Denied dirs: ${this.config.deniedDirs.length > 0 ? chalk.yellow(this.config.deniedDirs.join(', ')) : chalk.gray('none')}`);
|
|
306
|
+
console.log(` Denied patterns: ${this.config.deniedPatterns.length > 0 ? chalk.yellow(this.config.deniedPatterns.length + ' patterns') : chalk.gray('none')}`);
|
|
307
|
+
if (this.violations.length > 0) {
|
|
308
|
+
console.log(` Violations: ${chalk.red(String(this.violations.length))}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
console.log();
|
|
312
|
+
}
|
|
313
|
+
// --- Private Helpers ---
|
|
314
|
+
resolvePath(filePath) {
|
|
315
|
+
if (filePath.startsWith('/'))
|
|
316
|
+
return normalize(filePath);
|
|
317
|
+
if (filePath.startsWith('~/'))
|
|
318
|
+
return normalize(join(require('os').homedir(), filePath.slice(2)));
|
|
319
|
+
return normalize(resolve(this.config.rootDir, filePath));
|
|
320
|
+
}
|
|
321
|
+
isUnderRootDir(absPath) {
|
|
322
|
+
const relativePath = relative(this.config.rootDir, absPath);
|
|
323
|
+
return !relativePath.startsWith('..') && !relativePath.startsWith('/');
|
|
324
|
+
}
|
|
325
|
+
isInDeniedDir(absPath) {
|
|
326
|
+
const relativePath = relative(this.config.rootDir, absPath);
|
|
327
|
+
// Check built-in denied dirs
|
|
328
|
+
for (const deniedDir of this.config.deniedDirs) {
|
|
329
|
+
if (relativePath.startsWith(deniedDir + '/') || relativePath === deniedDir) {
|
|
330
|
+
return true;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
// Check custom allowed dirs (everything else is under rootDir)
|
|
334
|
+
// If allowedDirs is set, deny anything not in those dirs
|
|
335
|
+
if (this.config.allowedDirs.length > 0) {
|
|
336
|
+
const inAllowed = this.config.allowedDirs.some(allowedDir => {
|
|
337
|
+
const absAllowed = resolve(this.config.rootDir, allowedDir);
|
|
338
|
+
return absPath.startsWith(absAllowed);
|
|
339
|
+
});
|
|
340
|
+
if (!inAllowed && this.isUnderRootDir(absPath))
|
|
341
|
+
return false; // Under root but not in allowed
|
|
342
|
+
}
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
matchesDeniedPattern(absPath) {
|
|
346
|
+
const relativePath = relative(this.config.rootDir, absPath);
|
|
347
|
+
for (const pattern of this.config.deniedPatterns) {
|
|
348
|
+
const globPattern = pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*');
|
|
349
|
+
try {
|
|
350
|
+
const regex = new RegExp('^' + globPattern + '$');
|
|
351
|
+
if (regex.test(relativePath) || regex.test(absPath))
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
// Invalid pattern, skip
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
recordViolation(type, path, reason) {
|
|
361
|
+
this.violations.push({
|
|
362
|
+
type,
|
|
363
|
+
path,
|
|
364
|
+
reason: reason || 'Access denied by sandbox',
|
|
365
|
+
timestamp: Date.now(),
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
//# sourceMappingURL=sandbox.js.map
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
export type TaskStatus = 'scheduled' | 'running' | 'paused' | 'completed' | 'failed' | 'cancelled';
|
|
2
|
+
export type IntervalUnit = 'minutes' | 'hours' | 'days';
|
|
3
|
+
export interface ScheduledTaskConfig {
|
|
4
|
+
/** Unique identifier (auto-generated if not provided) */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Human-readable name */
|
|
7
|
+
name: string;
|
|
8
|
+
/** The prompt / task description to execute each run */
|
|
9
|
+
prompt: string;
|
|
10
|
+
/** Interval value (used together with intervalUnit) */
|
|
11
|
+
interval: number;
|
|
12
|
+
/** Unit for the interval */
|
|
13
|
+
intervalUnit: IntervalUnit;
|
|
14
|
+
/** Maximum number of runs – 0 = unlimited */
|
|
15
|
+
maxRuns: number;
|
|
16
|
+
/** Optional model override */
|
|
17
|
+
model?: string;
|
|
18
|
+
/** Whether to auto-approve tool calls during execution */
|
|
19
|
+
autoApprove: boolean;
|
|
20
|
+
/** Whether to notify on completion (platform-dependent) */
|
|
21
|
+
notifyOnComplete: boolean;
|
|
22
|
+
/** Whether to save results to a file */
|
|
23
|
+
saveResults: boolean;
|
|
24
|
+
/** Working directory for the task */
|
|
25
|
+
workingDirectory: string;
|
|
26
|
+
}
|
|
27
|
+
export interface TaskExecution {
|
|
28
|
+
/** Unique execution ID */
|
|
29
|
+
id: string;
|
|
30
|
+
/** The task this execution belongs to */
|
|
31
|
+
taskId: string;
|
|
32
|
+
/** ISO timestamp when execution started */
|
|
33
|
+
startedAt: string;
|
|
34
|
+
/** ISO timestamp when execution ended */
|
|
35
|
+
endedAt?: string;
|
|
36
|
+
/** Duration in ms */
|
|
37
|
+
durationMs?: number;
|
|
38
|
+
/** Execution status */
|
|
39
|
+
status: 'success' | 'error' | 'timeout' | 'aborted';
|
|
40
|
+
/** The prompt that was executed (may differ from task if templated) */
|
|
41
|
+
prompt: string;
|
|
42
|
+
/** Result text */
|
|
43
|
+
result?: string;
|
|
44
|
+
/** Error message if status is error */
|
|
45
|
+
error?: string;
|
|
46
|
+
/** Number of iterations used */
|
|
47
|
+
iterations: number;
|
|
48
|
+
/** Cost in USD */
|
|
49
|
+
cost: number;
|
|
50
|
+
/** Files modified during this execution */
|
|
51
|
+
filesChanged: number;
|
|
52
|
+
/** Commands run during this execution */
|
|
53
|
+
commandsRun: number;
|
|
54
|
+
}
|
|
55
|
+
export interface ScheduledTaskState {
|
|
56
|
+
config: ScheduledTaskConfig;
|
|
57
|
+
status: TaskStatus;
|
|
58
|
+
/** Number of completed runs so far */
|
|
59
|
+
runCount: number;
|
|
60
|
+
/** Number of failed runs */
|
|
61
|
+
failCount: number;
|
|
62
|
+
/** ISO timestamp of last successful run */
|
|
63
|
+
lastRunAt?: string;
|
|
64
|
+
/** ISO timestamp of next scheduled run */
|
|
65
|
+
nextRunAt?: string;
|
|
66
|
+
/** ISO timestamp when the task was created */
|
|
67
|
+
createdAt: string;
|
|
68
|
+
/** ISO timestamp when the task was last updated */
|
|
69
|
+
updatedAt: string;
|
|
70
|
+
/** Execution history (most recent first, capped at 100) */
|
|
71
|
+
history: TaskExecution[];
|
|
72
|
+
/** Accumulated cost across all runs */
|
|
73
|
+
totalCost: number;
|
|
74
|
+
/** Accumulated iterations across all runs */
|
|
75
|
+
totalIterations: number;
|
|
76
|
+
}
|
|
77
|
+
export interface ScheduledTaskEngine {
|
|
78
|
+
/** Run a prompt and return structured results */
|
|
79
|
+
runPrompt(prompt: string, model?: string, workingDir?: string): Promise<{
|
|
80
|
+
text: string;
|
|
81
|
+
inputTokens: number;
|
|
82
|
+
outputTokens: number;
|
|
83
|
+
cost: number;
|
|
84
|
+
filesChanged: number;
|
|
85
|
+
commandsRun: number;
|
|
86
|
+
iterations: number;
|
|
87
|
+
error?: string;
|
|
88
|
+
}>;
|
|
89
|
+
}
|
|
90
|
+
export declare class ScheduledTaskManager {
|
|
91
|
+
private tasks;
|
|
92
|
+
private timers;
|
|
93
|
+
private engine;
|
|
94
|
+
private shuttingDown;
|
|
95
|
+
constructor(engine?: ScheduledTaskEngine);
|
|
96
|
+
/** Set or replace the execution engine */
|
|
97
|
+
setEngine(engine: ScheduledTaskEngine): void;
|
|
98
|
+
/**
|
|
99
|
+
* Schedule a new recurring task.
|
|
100
|
+
* Returns the full task state including its generated ID.
|
|
101
|
+
*/
|
|
102
|
+
scheduleTask(config: Omit<ScheduledTaskConfig, 'id'> & {
|
|
103
|
+
id?: string;
|
|
104
|
+
}): ScheduledTaskState;
|
|
105
|
+
/** Cancel a scheduled task permanently */
|
|
106
|
+
cancelTask(taskId: string): boolean;
|
|
107
|
+
/** List all tasks, optionally filtered by status */
|
|
108
|
+
listTasks(status?: TaskStatus): ScheduledTaskState[];
|
|
109
|
+
/** Temporarily pause a task */
|
|
110
|
+
pauseTask(taskId: string): boolean;
|
|
111
|
+
/** Resume a paused task */
|
|
112
|
+
resumeTask(taskId: string): boolean;
|
|
113
|
+
/** Get execution status and history for a task */
|
|
114
|
+
getTaskStatus(taskId: string): ScheduledTaskState | undefined;
|
|
115
|
+
/** Update a task's configuration (resets timer) */
|
|
116
|
+
updateTask(taskId: string, updates: Partial<Omit<ScheduledTaskConfig, 'id'>>): boolean;
|
|
117
|
+
/** Delete a task entirely (removes from memory and persistence) */
|
|
118
|
+
deleteTask(taskId: string): boolean;
|
|
119
|
+
/** Manually trigger a task run (outside its schedule) */
|
|
120
|
+
runTaskNow(taskId: string): Promise<TaskExecution | null>;
|
|
121
|
+
/** Pause all scheduled tasks */
|
|
122
|
+
pauseAll(): number;
|
|
123
|
+
/** Resume all paused tasks */
|
|
124
|
+
resumeAll(): number;
|
|
125
|
+
/** Cancel all tasks */
|
|
126
|
+
cancelAll(): number;
|
|
127
|
+
/** Get aggregate stats across all tasks */
|
|
128
|
+
getAggregateStats(): {
|
|
129
|
+
totalTasks: number;
|
|
130
|
+
activeTasks: number;
|
|
131
|
+
pausedTasks: number;
|
|
132
|
+
totalRuns: number;
|
|
133
|
+
totalFailures: number;
|
|
134
|
+
totalCost: number;
|
|
135
|
+
};
|
|
136
|
+
/** Get recent executions across all tasks, sorted by time (most recent first) */
|
|
137
|
+
getRecentExecutions(limit?: number): TaskExecution[];
|
|
138
|
+
/** Gracefully shut down – cancels all timers */
|
|
139
|
+
shutdown(): void;
|
|
140
|
+
private scheduleTimer;
|
|
141
|
+
private clearTimer;
|
|
142
|
+
private onTimerFire;
|
|
143
|
+
private executeTask;
|
|
144
|
+
private intervalToMs;
|
|
145
|
+
private saveResultToFile;
|
|
146
|
+
private notifyCompletion;
|
|
147
|
+
private persistTasks;
|
|
148
|
+
private loadTasks;
|
|
149
|
+
private registerShutdownHooks;
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=scheduled-tasks.d.ts.map
|