shoud-cli 3.0.1 → 3.0.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shoud-cli",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "SHOUD Terminal Agent: Give your computer a job.",
5
5
  "main": "bin/shoud.js",
6
6
  "bin": {
@@ -1,82 +0,0 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const os = require('os');
4
-
5
- const CONFIG_DIR = path.join(os.homedir(), '.shoud');
6
- const CHECKPOINT_FILE = path.join(CONFIG_DIR, 'checkpoint.json');
7
- const MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
8
-
9
- // Ensure config directory exists with secure permissions
10
- if (!fs.existsSync(CONFIG_DIR)) {
11
- fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
12
- }
13
-
14
- /**
15
- * Save the execution state (messages, etc.) to a checkpoint file.
16
- * Includes a timestamp for TTL.
17
- */
18
- function saveCheckpoint(state) {
19
- try {
20
- const data = {
21
- timestamp: Date.now(),
22
- ...state
23
- };
24
- fs.writeFileSync(CHECKPOINT_FILE, JSON.stringify(data, null, 2), {
25
- encoding: 'utf-8',
26
- mode: 0o600
27
- });
28
- } catch (err) {
29
- console.error(`Failed to save checkpoint: ${err.message}`);
30
- // We don't throw; the agent can continue without checkpointing
31
- }
32
- }
33
-
34
- /**
35
- * Load a checkpoint if it exists and is not too old.
36
- * Returns the state object (e.g., { messages }) or null if none or expired.
37
- */
38
- function loadCheckpoint() {
39
- if (!fs.existsSync(CHECKPOINT_FILE)) {
40
- return null;
41
- }
42
- try {
43
- const raw = fs.readFileSync(CHECKPOINT_FILE, 'utf-8');
44
- const data = JSON.parse(raw);
45
- const { timestamp, ...state } = data;
46
-
47
- // Check TTL
48
- if (timestamp && (Date.now() - timestamp) > MAX_AGE_MS) {
49
- // Stale checkpoint – delete it and ignore
50
- clearCheckpoint();
51
- return null;
52
- }
53
-
54
- // Validate that the state contains at least a messages array
55
- if (!state.messages || !Array.isArray(state.messages)) {
56
- // Corrupted or invalid state; delete it
57
- clearCheckpoint();
58
- return null;
59
- }
60
-
61
- return state;
62
- } catch (err) {
63
- // If file is corrupt, delete it
64
- clearCheckpoint();
65
- return null;
66
- }
67
- }
68
-
69
- /**
70
- * Clear (delete) the checkpoint file.
71
- */
72
- function clearCheckpoint() {
73
- if (fs.existsSync(CHECKPOINT_FILE)) {
74
- try {
75
- fs.unlinkSync(CHECKPOINT_FILE);
76
- } catch (_) {
77
- // Ignore errors during cleanup
78
- }
79
- }
80
- }
81
-
82
- module.exports = { saveCheckpoint, loadCheckpoint, clearCheckpoint };
@@ -1,133 +0,0 @@
1
- const inquirer = require('inquirer');
2
- const chalk = require('chalk');
3
- const fs = require('fs');
4
- const path = require('path');
5
- const os = require('os');
6
-
7
- const CONFIG_DIR = path.join(os.homedir(), '.shoud');
8
- const ALLOWLIST_FILE = path.join(CONFIG_DIR, 'allowlist.json');
9
-
10
- // Ensure config directory exists
11
- if (!fs.existsSync(CONFIG_DIR)) {
12
- fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
13
- }
14
-
15
- // Load persistent allowlist (or create empty)
16
- function loadAllowlist() {
17
- try {
18
- if (fs.existsSync(ALLOWLIST_FILE)) {
19
- return JSON.parse(fs.readFileSync(ALLOWLIST_FILE, 'utf-8'));
20
- }
21
- } catch (_) {}
22
- return { commands: [], paths: [] };
23
- }
24
-
25
- // Save allowlist
26
- function saveAllowlist(data) {
27
- try {
28
- fs.writeFileSync(ALLOWLIST_FILE, JSON.stringify(data, null, 2), {
29
- encoding: 'utf-8',
30
- mode: 0o600
31
- });
32
- } catch (_) {}
33
- }
34
-
35
- // In‑memory session allowlist (cleared on CLI restart)
36
- let sessionAllowList = {
37
- commands: new Set(),
38
- paths: new Set()
39
- };
40
-
41
- /**
42
- * Check if a command is allowed by persistent or session allowlists.
43
- */
44
- function isAllowed(toolName, input) {
45
- if (toolName === 'read_file') return true; // always safe
46
-
47
- if (toolName === 'execute_shell') {
48
- const cmd = input.command?.trim().split(/\s+/)[0] || '';
49
- // Persistent allowlist
50
- const persistent = loadAllowlist();
51
- if (persistent.commands.includes(cmd)) return true;
52
- // Session allowlist
53
- if (sessionAllowList.commands.has(cmd)) return true;
54
- }
55
-
56
- if (toolName === 'write_file' || toolName === 'read_file') {
57
- const filePath = input.path || '';
58
- // Check persistent and session for paths (exact match or prefix)
59
- // For simplicity, we'll just match exact paths; could be extended
60
- const persistent = loadAllowlist();
61
- if (persistent.paths.includes(filePath)) return true;
62
- if (sessionAllowList.paths.has(filePath)) return true;
63
- }
64
-
65
- return false;
66
- }
67
-
68
- /**
69
- * Prompt user for permission, with options for session‑wide or persistent allow.
70
- */
71
- async function verifyPermission(toolName, input) {
72
- // If already allowed, skip prompt
73
- if (isAllowed(toolName, input)) return true;
74
-
75
- // Build display info
76
- let target = '';
77
- if (toolName === 'execute_shell') target = input.command || 'unknown command';
78
- else if (toolName === 'read_file' || toolName === 'write_file') target = input.path || 'unknown path';
79
- else target = JSON.stringify(input);
80
-
81
- console.log(chalk.yellow(`\n⚠ SHOUD requests permission to run: ${chalk.bold(toolName)}`));
82
- console.log(chalk.gray(`Target: ${target}`));
83
-
84
- const { permission } = await inquirer.prompt([
85
- {
86
- type: 'list',
87
- name: 'permission',
88
- message: 'Allow this operation?',
89
- choices: [
90
- { name: 'Allow once', value: 'once' },
91
- { name: 'Allow for this session', value: 'session' },
92
- { name: 'Always allow (save to config)', value: 'always' },
93
- { name: 'Deny', value: 'deny' }
94
- ]
95
- }
96
- ]);
97
-
98
- if (permission === 'deny') return false;
99
-
100
- // Add to session allowlist if requested
101
- if (permission === 'session') {
102
- if (toolName === 'execute_shell') {
103
- const cmd = input.command.trim().split(/\s+/)[0];
104
- sessionAllowList.commands.add(cmd);
105
- } else if (toolName === 'read_file' || toolName === 'write_file') {
106
- sessionAllowList.paths.add(input.path);
107
- }
108
- return true;
109
- }
110
-
111
- // Add to persistent allowlist
112
- if (permission === 'always') {
113
- const allowlist = loadAllowlist();
114
- if (toolName === 'execute_shell') {
115
- const cmd = input.command.trim().split(/\s+/)[0];
116
- if (!allowlist.commands.includes(cmd)) {
117
- allowlist.commands.push(cmd);
118
- saveAllowlist(allowlist);
119
- }
120
- } else if (toolName === 'read_file' || toolName === 'write_file') {
121
- if (!allowlist.paths.includes(input.path)) {
122
- allowlist.paths.push(input.path);
123
- saveAllowlist(allowlist);
124
- }
125
- }
126
- return true;
127
- }
128
-
129
- // Once
130
- return true;
131
- }
132
-
133
- module.exports = { verifyPermission };