shoud-cli 1.0.7 → 1.0.10

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/bin/shoud.js CHANGED
@@ -5,7 +5,7 @@ const chalk = require('chalk');
5
5
  const { login } = require('../src/auth/deviceFlow');
6
6
  const { executeTask } = require('../src/runtime/agentLoop');
7
7
 
8
- // ─── Exact SHOUD Banner Art (provided by user) ──────────────────────────────
8
+ // ─── Original SHOUD Banner Art ──────────────────────────────
9
9
  const BANNER_ART = [
10
10
  " ███████╗██╗ ██╗ ██████╗ ██╗ ██╗██████╗ ",
11
11
  " ██╔════╝██║ ██║██╔═══██╗██║ ██║██╔══██╗",
@@ -15,13 +15,21 @@ const BANNER_ART = [
15
15
  " ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ "
16
16
  ];
17
17
 
18
+ // ─── Static Banner (prints at the start of commands) ──────
19
+ function printStaticBanner() {
20
+ const color = chalk.hex('#B5F96C');
21
+ console.log(color(BANNER_ART.join('\n')));
22
+ console.log(color(' ┌──────────────────────────────────────────────────────────┐'));
23
+ console.log(color(' │ Give your computer a job. │'));
24
+ console.log(color(' └──────────────────────────────────────────────────────────┘'));
25
+ }
26
+
27
+ // ─── Animated Banner (for help) ─────────────────────────────
18
28
  const SLEEP = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
19
29
 
20
- // ─── Animated Banner ─────────────────────────────────────────────────────────
21
30
  async function renderAnimatedBanner() {
22
31
  if (!process.stdout.isTTY) {
23
- console.log(chalk.hex('#B5F96C')(BANNER_ART.join('\n')));
24
- console.log(chalk.hex('#B5F96C').bold(' Give your computer a job.\n'));
32
+ printStaticBanner();
25
33
  return;
26
34
  }
27
35
 
@@ -61,7 +69,7 @@ async function renderAnimatedBanner() {
61
69
  process.stdout.write('\x1B[?25h');
62
70
  }
63
71
 
64
- // ─── Custom Help ──────────────────────────────────────────────────────────────
72
+ // ─── Custom Help ──────────────────────────────────────────────
65
73
  async function customHelp() {
66
74
  console.log(chalk.hex('#B5F96C').bold('\n SHOUD Terminal Agent - Command Reference\n'));
67
75
  console.log(chalk.gray(' Usage: shoud [command] OR shoud "[prompt]"\n'));
@@ -78,7 +86,7 @@ async function customHelp() {
78
86
  console.log(` ${chalk.hex('#B5F96C')('>')} shoud "Run npm test and fix any failing test cases"\n`);
79
87
  }
80
88
 
81
- // ─── Main ──────────────────────────────────────────────────────────────────────
89
+ // ─── Main ──────────────────────────────────────────────────────
82
90
  async function main() {
83
91
  const args = process.argv.slice(2);
84
92
 
@@ -87,8 +95,7 @@ async function main() {
87
95
  args[0] === 'help' || args[0] === '--help' || args[0] === '-h' ||
88
96
  args[0] === '--version' || args[0] === '-v') {
89
97
  if (args[0] === '--version' || args[0] === '-v') {
90
- // Let Commander handle version
91
- program.version('1.0.0');
98
+ program.version('1.0.9');
92
99
  program.parse(process.argv);
93
100
  return;
94
101
  }
@@ -98,15 +105,18 @@ async function main() {
98
105
  }
99
106
 
100
107
  program
101
- .version('1.0.0')
108
+ .version('1.0.9')
102
109
  .description('SHOUD Terminal Agent');
103
110
 
104
111
  program
105
112
  .command('login')
106
113
  .description('Authenticate this device with your SHOUD account')
107
- .action(login);
114
+ .action(() => {
115
+ // Optionally show banner before login? We'll keep it clean.
116
+ login();
117
+ });
108
118
 
109
- // Placeholder for status – you can replace with real implementation
119
+ // Placeholder for status
110
120
  program
111
121
  .command('status')
112
122
  .description('Check your current credit balance and active plan')
@@ -122,7 +132,10 @@ async function main() {
122
132
  return;
123
133
  }
124
134
  const taskPrompt = promptArr.join(' ');
135
+ // ✅ Show the static banner at the start
136
+ printStaticBanner();
125
137
  await executeTask(taskPrompt);
138
+ // No banner at the end (removed)
126
139
  });
127
140
 
128
141
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shoud-cli",
3
- "version": "1.0.7",
3
+ "version": "1.0.10",
4
4
  "description": "SHOUD Terminal Agent: Give your computer a job.",
5
5
  "main": "bin/shoud.js",
6
6
  "bin": {
@@ -1,140 +1,115 @@
1
- #!/usr/bin/env node
1
+ const { execSync } = require('child_process');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
2
5
 
3
- const { program } = require('commander');
4
- const chalk = require('chalk');
5
- const { login } = require('../src/auth/deviceFlow');
6
- const { executeTask } = require('../src/runtime/agentLoop');
6
+ const MAX_OUTPUT_SIZE = 1024 * 1024;
7
+ const PROJECT_ROOT = process.cwd();
7
8
 
8
- // ─── Original SHOUD Banner Art ──────────────────────────────
9
- const BANNER_ART = [
10
- " ███████╗██╗ ██╗ ██████╗ ██╗ ██╗██████╗ ",
11
- " ██╔════╝██║ ██║██╔═══██╗██║ ██║██╔══██╗",
12
- " ███████╗███████║██║ ██║██║ ██║██║ ██║",
13
- " ╚════██║██╔══██║██║ ██║██║ ██║██║ ██║",
14
- " ███████║██║ ██║╚██████╔╝╚██████╔╝██████╔╝",
15
- " ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ "
16
- ];
17
-
18
- // ─── Static Banner (no animation) ──────────────────────────
19
- function printStaticBanner() {
20
- const color = chalk.hex('#B5F96C');
21
- console.log(color(BANNER_ART.join('\n')));
22
- console.log(color(' ┌──────────────────────────────────────────────────────────┐'));
23
- console.log(color(' │ Give your computer a job. │'));
24
- console.log(color(' └──────────────────────────────────────────────────────────┘'));
9
+ function getShell() {
10
+ if (os.platform() === 'win32') {
11
+ return 'powershell.exe';
12
+ }
13
+ return '/bin/bash';
25
14
  }
26
15
 
27
- // ─── Animated Banner (for help) ─────────────────────────────
28
- const SLEEP = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
29
-
30
- async function renderAnimatedBanner() {
31
- if (!process.stdout.isTTY) {
32
- printStaticBanner();
33
- return;
16
+ function sanitizePath(inputPath) {
17
+ const resolved = path.resolve(PROJECT_ROOT, inputPath);
18
+ if (!resolved.startsWith(PROJECT_ROOT)) {
19
+ throw new Error('Path is outside the project directory.');
34
20
  }
21
+ return resolved;
22
+ }
35
23
 
36
- const colorGradients = [
37
- ['#364B1D', '#5E8232', '#86B947', '#B5F96C'],
38
- ['#5E8232', '#86B947', '#B5F96C', '#DDFFA8'],
39
- ['#86B947', '#B5F96C', '#FFFFFF', '#B5F96C'],
40
- ['#B5F96C', '#B5F96C', '#B5F96C', '#9EEB49']
41
- ];
24
+ /**
25
+ * Execute a shell command and return plain text output.
26
+ * On Windows, we use PowerShell with `Out-String` to get text.
27
+ */
28
+ function executeShell(commandString) {
29
+ if (!commandString || commandString.trim().length === 0) {
30
+ throw new Error('Empty command.');
31
+ }
42
32
 
43
- for (const colors of colorGradients) {
44
- process.stdout.write('\x1B[?25l');
45
- process.stdout.write('\r\x1B[K');
46
- console.clear();
47
- console.log();
33
+ let finalCommand = commandString;
34
+ if (os.platform() === 'win32') {
35
+ // ✅ Force PowerShell to output plain text
36
+ // Escape double quotes in the command
37
+ const escaped = commandString.replace(/"/g, '`"');
38
+ // Wrap in a PowerShell script block and pipe to Out-String
39
+ finalCommand = `& { ${escaped} } | Out-String -Width 4096`;
40
+ }
48
41
 
49
- BANNER_ART.forEach((line, index) => {
50
- const color = colors[index % colors.length];
51
- console.log(chalk.hex(color).bold(line));
42
+ try {
43
+ const output = execSync(finalCommand, {
44
+ cwd: PROJECT_ROOT,
45
+ encoding: 'utf-8',
46
+ shell: getShell(),
47
+ timeout: 30000,
48
+ maxBuffer: MAX_OUTPUT_SIZE,
49
+ stdio: 'pipe',
52
50
  });
53
-
54
- console.log(
55
- chalk.hex('#3B472E')(
56
- ' ┌──────────────────────────────────────────────────────────┐\n' +
57
- ' │'
58
- ) +
59
- chalk.hex('#B5F96C').bold(' Give your computer a job. ') +
60
- chalk.hex('#3B472E')(
61
- '│\n' +
62
- ' └──────────────────────────────────────────────────────────┘\n'
63
- )
64
- );
65
-
66
- await SLEEP(75);
51
+ const trimmed = output.trim();
52
+ return trimmed || 'Command executed successfully with no output.';
53
+ } catch (err) {
54
+ // If the command fails, return the error message
55
+ if (err.stderr) {
56
+ return `Error: ${err.stderr.trim()}`;
57
+ }
58
+ if (err.stdout) {
59
+ // Some commands output to stdout even on failure (e.g., `dir`)
60
+ return err.stdout.trim();
61
+ }
62
+ throw new Error(`Command execution failed: ${err.message}`);
67
63
  }
68
-
69
- process.stdout.write('\x1B[?25h');
70
64
  }
71
65
 
72
- // ─── Custom Help ──────────────────────────────────────────────
73
- async function customHelp() {
74
- console.log(chalk.hex('#B5F96C').bold('\n SHOUD Terminal Agent - Command Reference\n'));
75
- console.log(chalk.gray(' Usage: shoud [command] OR shoud "[prompt]"\n'));
76
-
77
- console.log(chalk.white.bold(' Commands:'));
78
- console.log(` ${chalk.hex('#B5F96C')('shoud login')} Authenticate this device with your Google account.`);
79
- console.log(` ${chalk.hex('#B5F96C')('shoud help')} Display this help menu.`);
80
- console.log(` ${chalk.hex('#B5F96C')('shoud status')} Check your current credit balance and active plan.`);
81
- console.log(` ${chalk.hex('#B5F96C')('shoud --version')} Show the version number.\n`);
82
-
83
- console.log(chalk.white.bold(' Autonomous Execution:'));
84
- console.log(chalk.gray(' Wrap your instructions in quotes to trigger the agent loop.'));
85
- console.log(` ${chalk.hex('#B5F96C')('>')} shoud "Refactor the auth middleware to use JWTs"`);
86
- console.log(` ${chalk.hex('#B5F96C')('>')} shoud "Run npm test and fix any failing test cases"\n`);
66
+ function readFile(filePath) {
67
+ const safePath = sanitizePath(filePath);
68
+ try {
69
+ const stats = fs.statSync(safePath);
70
+ if (!stats.isFile()) throw new Error('Path is not a file.');
71
+ if (stats.size > MAX_OUTPUT_SIZE) {
72
+ const buffer = Buffer.alloc(MAX_OUTPUT_SIZE);
73
+ const fd = fs.openSync(safePath, 'r');
74
+ fs.readSync(fd, buffer, 0, MAX_OUTPUT_SIZE, 0);
75
+ fs.closeSync(fd);
76
+ return buffer.toString('utf-8') + '\n... (file truncated)';
77
+ }
78
+ return fs.readFileSync(safePath, 'utf-8');
79
+ } catch (err) {
80
+ throw new Error(`Failed to read file: ${err.message}`);
81
+ }
87
82
  }
88
83
 
89
- // ─── Main ──────────────────────────────────────────────────────
90
- async function main() {
91
- const args = process.argv.slice(2);
92
-
93
- // Intercept help, version, and empty input
94
- if (args.length === 0 ||
95
- args[0] === 'help' || args[0] === '--help' || args[0] === '-h' ||
96
- args[0] === '--version' || args[0] === '-v') {
97
- if (args[0] === '--version' || args[0] === '-v') {
98
- program.version('1.0.6');
99
- program.parse(process.argv);
100
- return;
84
+ function writeFile(filePath, content) {
85
+ const safePath = sanitizePath(filePath);
86
+ try {
87
+ const dir = path.dirname(safePath);
88
+ if (!fs.existsSync(dir)) {
89
+ fs.mkdirSync(dir, { recursive: true });
101
90
  }
102
- await renderAnimatedBanner();
103
- await customHelp();
104
- return;
91
+ fs.writeFileSync(safePath, content, 'utf-8');
92
+ return `File written successfully: ${filePath}`;
93
+ } catch (err) {
94
+ throw new Error(`Failed to write file: ${err.message}`);
105
95
  }
96
+ }
106
97
 
107
- program
108
- .version('1.0.6')
109
- .description('SHOUD Terminal Agent');
110
-
111
- program
112
- .command('login')
113
- .description('Authenticate this device with your SHOUD account')
114
- .action(login);
115
-
116
- // Placeholder for status
117
- program
118
- .command('status')
119
- .description('Check your current credit balance and active plan')
120
- .action(() => {
121
- console.log(chalk.yellow(' Status command not yet implemented.'));
122
- });
123
-
124
- program
125
- .argument('[prompt...]', 'The task you want SHOUD to execute')
126
- .action(async (promptArr) => {
127
- if (!promptArr || promptArr.length === 0) {
128
- console.log(chalk.gray(' Usage: shoud "fix the build errors"'));
129
- return;
130
- }
131
- const taskPrompt = promptArr.join(' ');
132
- await executeTask(taskPrompt);
133
- // ✅ After task completes, show the static banner
134
- printStaticBanner();
135
- });
136
-
137
- program.parse(process.argv);
98
+ function executeTool(toolName, input) {
99
+ try {
100
+ switch (toolName) {
101
+ case 'execute_shell':
102
+ return executeShell(input.command);
103
+ case 'read_file':
104
+ return readFile(input.path);
105
+ case 'write_file':
106
+ return writeFile(input.path, input.content);
107
+ default:
108
+ throw new Error(`Tool "${toolName}" is not supported.`);
109
+ }
110
+ } catch (error) {
111
+ return `Tool execution failed: ${error.message}`;
112
+ }
138
113
  }
139
114
 
140
- main();
115
+ module.exports = { executeTool };