shoud-cli 1.0.2 → 1.0.4
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/package.json +2 -2
- package/src/tools/index.js +34 -48
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shoud-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "SHOUD Terminal Agent: Give your computer a job.",
|
|
5
5
|
"main": "bin/shoud.js",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"ora": "^5.4.1"
|
|
19
19
|
},
|
|
20
20
|
"engines": {
|
|
21
|
-
"node": "
|
|
21
|
+
"node": "18.0.0"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"bin/",
|
package/src/tools/index.js
CHANGED
|
@@ -1,78 +1,74 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
4
5
|
|
|
5
|
-
// Allowed shell commands (whitelist)
|
|
6
|
-
const ALLOWED_COMMANDS = new Set([
|
|
7
|
-
'ls', 'pwd', 'cat', 'grep', 'find', 'echo',
|
|
8
|
-
'mkdir', 'rmdir', 'touch', 'head', 'tail',
|
|
9
|
-
'wc', 'sort', 'uniq', 'diff', 'patch',
|
|
10
|
-
'git' // optional, but careful
|
|
11
|
-
]);
|
|
12
|
-
|
|
13
|
-
// Maximum output size (1 MB)
|
|
14
6
|
const MAX_OUTPUT_SIZE = 1024 * 1024;
|
|
15
|
-
|
|
16
|
-
// Get the project root (current working directory where CLI was started)
|
|
17
7
|
const PROJECT_ROOT = process.cwd();
|
|
18
8
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
function getShell() {
|
|
10
|
+
if (os.platform() === 'win32') {
|
|
11
|
+
return 'powershell.exe';
|
|
12
|
+
}
|
|
13
|
+
return '/bin/bash';
|
|
14
|
+
}
|
|
15
|
+
|
|
22
16
|
function sanitizePath(inputPath) {
|
|
23
|
-
// Resolve relative paths
|
|
24
17
|
const resolved = path.resolve(PROJECT_ROOT, inputPath);
|
|
25
|
-
// Ensure it starts with the project root
|
|
26
18
|
if (!resolved.startsWith(PROJECT_ROOT)) {
|
|
27
19
|
throw new Error('Path is outside the project directory.');
|
|
28
20
|
}
|
|
29
21
|
return resolved;
|
|
30
22
|
}
|
|
31
23
|
|
|
32
|
-
/**
|
|
33
|
-
* Execute a shell command securely.
|
|
34
|
-
* Only commands from the whitelist are allowed.
|
|
35
|
-
*/
|
|
36
24
|
function executeShell(commandString) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (parts.length === 0) throw new Error('Empty command.');
|
|
40
|
-
|
|
41
|
-
const cmd = parts[0];
|
|
42
|
-
if (!ALLOWED_COMMANDS.has(cmd)) {
|
|
43
|
-
throw new Error(`Command "${cmd}" is not allowed.`);
|
|
25
|
+
if (!commandString || commandString.trim().length === 0) {
|
|
26
|
+
throw new Error('Empty command.');
|
|
44
27
|
}
|
|
45
28
|
|
|
46
|
-
|
|
29
|
+
// ✅ On Windows, ensure PowerShell outputs text (not objects)
|
|
30
|
+
let finalCommand = commandString;
|
|
31
|
+
if (os.platform() === 'win32') {
|
|
32
|
+
// Convert simple commands to PowerShell-friendly format
|
|
33
|
+
if (commandString.trim() === 'Get-ChildItem') {
|
|
34
|
+
finalCommand = 'Get-ChildItem | Out-String -Width 4096';
|
|
35
|
+
} else if (commandString.trim() === 'dir') {
|
|
36
|
+
finalCommand = 'cmd.exe /c dir';
|
|
37
|
+
} else if (commandString.trim() === 'ls') {
|
|
38
|
+
// ls is not a native PowerShell command – use Get-ChildItem
|
|
39
|
+
finalCommand = 'Get-ChildItem | Out-String -Width 4096';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
47
42
|
|
|
48
43
|
try {
|
|
49
|
-
const output =
|
|
44
|
+
const output = execSync(finalCommand, {
|
|
50
45
|
cwd: PROJECT_ROOT,
|
|
51
46
|
encoding: 'utf-8',
|
|
52
|
-
|
|
47
|
+
shell: getShell(),
|
|
48
|
+
timeout: 30000,
|
|
53
49
|
maxBuffer: MAX_OUTPUT_SIZE,
|
|
54
|
-
stdio: 'pipe'
|
|
50
|
+
stdio: 'pipe',
|
|
55
51
|
});
|
|
56
|
-
|
|
52
|
+
const trimmed = output.trim();
|
|
53
|
+
return trimmed || 'Command executed successfully with no output.';
|
|
57
54
|
} catch (err) {
|
|
58
|
-
// If command fails, return the error message (safe to show to AI)
|
|
59
55
|
if (err.stderr) {
|
|
60
56
|
return `Error: ${err.stderr.trim()}`;
|
|
61
57
|
}
|
|
58
|
+
// If the command fails but there's output, return it
|
|
59
|
+
if (err.stdout) {
|
|
60
|
+
return err.stdout.trim();
|
|
61
|
+
}
|
|
62
62
|
throw new Error(`Command execution failed: ${err.message}`);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
/**
|
|
67
|
-
* Read a file securely, ensuring it's within the project root.
|
|
68
|
-
*/
|
|
69
66
|
function readFile(filePath) {
|
|
70
67
|
const safePath = sanitizePath(filePath);
|
|
71
68
|
try {
|
|
72
69
|
const stats = fs.statSync(safePath);
|
|
73
70
|
if (!stats.isFile()) throw new Error('Path is not a file.');
|
|
74
71
|
if (stats.size > MAX_OUTPUT_SIZE) {
|
|
75
|
-
// Read only first 1 MB
|
|
76
72
|
const buffer = Buffer.alloc(MAX_OUTPUT_SIZE);
|
|
77
73
|
const fd = fs.openSync(safePath, 'r');
|
|
78
74
|
fs.readSync(fd, buffer, 0, MAX_OUTPUT_SIZE, 0);
|
|
@@ -85,14 +81,9 @@ function readFile(filePath) {
|
|
|
85
81
|
}
|
|
86
82
|
}
|
|
87
83
|
|
|
88
|
-
/**
|
|
89
|
-
* Write content to a file securely (optional, but useful for agent).
|
|
90
|
-
* Ensure file is within project root.
|
|
91
|
-
*/
|
|
92
84
|
function writeFile(filePath, content) {
|
|
93
85
|
const safePath = sanitizePath(filePath);
|
|
94
86
|
try {
|
|
95
|
-
// Ensure directory exists
|
|
96
87
|
const dir = path.dirname(safePath);
|
|
97
88
|
if (!fs.existsSync(dir)) {
|
|
98
89
|
fs.mkdirSync(dir, { recursive: true });
|
|
@@ -104,10 +95,6 @@ function writeFile(filePath, content) {
|
|
|
104
95
|
}
|
|
105
96
|
}
|
|
106
97
|
|
|
107
|
-
/**
|
|
108
|
-
* Main tool dispatcher.
|
|
109
|
-
* Returns the result as a string (to be sent back to the AI).
|
|
110
|
-
*/
|
|
111
98
|
function executeTool(toolName, input) {
|
|
112
99
|
try {
|
|
113
100
|
switch (toolName) {
|
|
@@ -121,7 +108,6 @@ function executeTool(toolName, input) {
|
|
|
121
108
|
throw new Error(`Tool "${toolName}" is not supported.`);
|
|
122
109
|
}
|
|
123
110
|
} catch (error) {
|
|
124
|
-
// Return a generic error message to the AI (don't leak internal details)
|
|
125
111
|
return `Tool execution failed: ${error.message}`;
|
|
126
112
|
}
|
|
127
113
|
}
|