screepsmod-exec-cli-in-console 1.0.2 → 1.0.5
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 +51 -13
- package/README.zh.md +51 -13
- package/index.js +124 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,18 +42,42 @@ Example `mods.json`:
|
|
|
42
42
|
|
|
43
43
|
## Configuration (IMPORTANT)
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
45
|
+
Create a `execute-cli.config.json` file in your server root directory (same level as `mods.json`). Only include the fields you want to override; missing fields use defaults.
|
|
46
|
+
|
|
47
|
+
Example `execute-cli.config.json`:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"allowAllUsers": false,
|
|
52
|
+
"normalUsernames": ["player1", "player2"],
|
|
53
|
+
"superAdminUsernames": ["admin"],
|
|
54
|
+
"superAdminUserIds": ["1"]
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Config file search order:
|
|
59
|
+
1. Directory of `MODFILE` env var (Screeps server sets this to `mods.json` path)
|
|
60
|
+
2. Current working directory (`process.cwd()`)
|
|
61
|
+
3. One level up from the mod directory
|
|
62
|
+
4. Two/three levels up (for `node_modules/` installations)
|
|
63
|
+
|
|
64
|
+
### Available options
|
|
65
|
+
|
|
66
|
+
| Option | Type | Default | Description |
|
|
67
|
+
|--------|------|---------|-------------|
|
|
68
|
+
| `allowAllUsers` | boolean | `false` | If `true`, every real player becomes a **normal** user (NOT super admin). Keep `false` unless this is a local/dev server. |
|
|
69
|
+
| `normalUserIds` | string[] | `[]` | User IDs for **normal** users (e.g. `["1", "4"]`). Used when `allowAllUsers=false`. |
|
|
70
|
+
| `normalUsernames` | string[] | `[]` | Usernames for **normal** users. Case-sensitive. |
|
|
71
|
+
| `superAdminUserIds` | string[] | `[]` | User IDs for **super admin** users. |
|
|
72
|
+
| `superAdminUsernames` | string[] | `[]` | Usernames for **super admin** users. Case-sensitive. |
|
|
73
|
+
| `superAdminUsersCodeSelfOnly` | boolean | `true` | If `true`, even super admins cannot read/modify other users' code in `users.code`. |
|
|
74
|
+
| `allowedCodePrefixes` | string[] | `[]` | If non-empty, only code starting with one of these prefixes is allowed. |
|
|
75
|
+
| `maxCodeLength` | number | `2000` | Maximum length of CLI code string allowed per call. |
|
|
76
|
+
| `maxOutputLines` | number | `60` | Maximum number of output lines sent back to the player's console. |
|
|
77
|
+
| `evalTimeoutMs` | number | `2000` | Node vm timeout (ms) for synchronous code execution. |
|
|
78
|
+
| `promiseTimeoutMs` | number | `5000` | Timeout (ms) for waiting on returned promise/thenable results. |
|
|
79
|
+
|
|
80
|
+
> **Note**: Username matching is **case-sensitive**. Make sure to use the exact username as registered.
|
|
57
81
|
|
|
58
82
|
## Usage (in-game console)
|
|
59
83
|
|
|
@@ -93,6 +117,20 @@ Sets `progress = progressTotal - 1` for construction sites in the given rooms.
|
|
|
93
117
|
Game.cli.finishConstructionSites(['W1N9'])
|
|
94
118
|
```
|
|
95
119
|
|
|
120
|
+
### Common commands
|
|
121
|
+
|
|
122
|
+
#### Modify all ramparts HP
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
Game.cli.exec("(function(){var hp=300000000;return storage.db.rooms.find({}).then(function(rs){var rooms=(rs||[]).map(function(r){return r._id});return storage.db['rooms.objects'].find({type:'rampart',room:{$in:rooms}}).then(function(ws){var p=storage.db['rooms.objects'].count({_id:{$in:[]}});(ws||[]).forEach(function(w){p=p.then(function(){return storage.db['rooms.objects'].update({_id:w._id},{$set:{hits:hp,hitsMax:hp}});});});return p.then(function(){return 'OK ramparts='+(ws?ws.length:0);});});});})()")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### Modify walls/ramparts HP in a specific room (e.g. W4N1)
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
Game.cli.exec("storage.db['rooms.objects'].find({ type: { $in: ['constructedWall', 'rampart'] }, room: { $in: ['W4N1'] } }).then(resp => resp.map(cs => storage.db['rooms.objects'].findOne({ _id: cs._id }).then(csDetail => storage.db['rooms.objects'].update({ _id: cs._id }, { $set: { hits: 10000000 } }))))")
|
|
132
|
+
```
|
|
133
|
+
|
|
96
134
|
## Security notes
|
|
97
135
|
|
|
98
136
|
Executing arbitrary server CLI JS is powerful:
|
|
@@ -113,7 +151,7 @@ Role behavior:
|
|
|
113
151
|
- Make sure the mod is enabled in `mods.json` and the server restarted.
|
|
114
152
|
- Ensure your server has `isolated-vm` available (Screeps uses it for player sandbox).
|
|
115
153
|
- **`[cli] denied: not allowed user`**:
|
|
116
|
-
- Add your user id / username to `
|
|
154
|
+
- Add your user id / username to `normalUserIds/normalUsernames` or `superAdminUserIds/superAdminUsernames` in `execute-cli.config.json`.
|
|
117
155
|
- **No output / output truncated**:
|
|
118
156
|
- Adjust `maxOutputLines`, `evalTimeoutMs`, `promiseTimeoutMs`.
|
|
119
157
|
|
package/README.zh.md
CHANGED
|
@@ -42,18 +42,42 @@
|
|
|
42
42
|
|
|
43
43
|
## 配置(非常重要)
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
45
|
+
在私服根目录(与 `mods.json` 同级)创建 `execute-cli.config.json` 配置文件。只需填写你想覆盖的字段,未填写的字段使用默认值。
|
|
46
|
+
|
|
47
|
+
`execute-cli.config.json` 示例:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"allowAllUsers": false,
|
|
52
|
+
"normalUsernames": ["player1", "player2"],
|
|
53
|
+
"superAdminUsernames": ["admin"],
|
|
54
|
+
"superAdminUserIds": ["1"]
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
配置文件搜索顺序:
|
|
59
|
+
1. `MODFILE` 环境变量指定的目录(Screeps 私服会将其设为 `mods.json` 路径)
|
|
60
|
+
2. 当前工作目录(`process.cwd()`)
|
|
61
|
+
3. mod 目录的上一级
|
|
62
|
+
4. 上两/三级目录(适用于 `node_modules/` 安装方式)
|
|
63
|
+
|
|
64
|
+
### 可用配置项
|
|
65
|
+
|
|
66
|
+
| 选项 | 类型 | 默认值 | 说明 |
|
|
67
|
+
|------|------|--------|------|
|
|
68
|
+
| `allowAllUsers` | boolean | `false` | 若为 `true`,所有真实玩家都会被视为**普通用户**(不是超管)。除非本地/单机测试服,否则不要开。 |
|
|
69
|
+
| `normalUserIds` | string[] | `[]` | 普通用户的 User ID 列表(如 `["1", "4"]`)。仅当 `allowAllUsers=false` 时生效。 |
|
|
70
|
+
| `normalUsernames` | string[] | `[]` | 普通用户的用户名列表。区分大小写。 |
|
|
71
|
+
| `superAdminUserIds` | string[] | `[]` | 超管的 User ID 列表。 |
|
|
72
|
+
| `superAdminUsernames` | string[] | `[]` | 超管的用户名列表。区分大小写。 |
|
|
73
|
+
| `superAdminUsersCodeSelfOnly` | boolean | `true` | 若为 `true`,即便是超管也不允许读/改其他用户在 `users.code` 里的代码(隐私保护)。 |
|
|
74
|
+
| `allowedCodePrefixes` | string[] | `[]` | 如果不为空,只允许执行以这些前缀开头的代码字符串。 |
|
|
75
|
+
| `maxCodeLength` | number | `2000` | 每次调用允许的 CLI 代码最大长度。 |
|
|
76
|
+
| `maxOutputLines` | number | `60` | 返回给玩家控制台的最大输出行数。 |
|
|
77
|
+
| `evalTimeoutMs` | number | `2000` | 同步代码执行的 Node vm 超时时间(毫秒)。 |
|
|
78
|
+
| `promiseTimeoutMs` | number | `5000` | 等待返回的 Promise/thenable 结果的超时时间(毫秒)。 |
|
|
79
|
+
|
|
80
|
+
> **注意**:用户名匹配**区分大小写**,请确保使用与注册时完全一致的用户名。
|
|
57
81
|
|
|
58
82
|
## 用法(游戏内控制台)
|
|
59
83
|
|
|
@@ -93,6 +117,20 @@ Game.cli.setControllerLevel('67c8...df8', 8)
|
|
|
93
117
|
Game.cli.finishConstructionSites(['W1N9'])
|
|
94
118
|
```
|
|
95
119
|
|
|
120
|
+
### 常用命令
|
|
121
|
+
|
|
122
|
+
#### 修改所有墙的血量
|
|
123
|
+
|
|
124
|
+
```js
|
|
125
|
+
Game.cli.exec("(function(){var hp=300000000;return storage.db.rooms.find({}).then(function(rs){var rooms=(rs||[]).map(function(r){return r._id});return storage.db['rooms.objects'].find({type:'rampart',room:{$in:rooms}}).then(function(ws){var p=storage.db['rooms.objects'].count({_id:{$in:[]}});(ws||[]).forEach(function(w){p=p.then(function(){return storage.db['rooms.objects'].update({_id:w._id},{$set:{hits:hp,hitsMax:hp}});});});return p.then(function(){return 'OK ramparts='+(ws?ws.length:0);});});});})()")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### 修改指定房间内的墙血量(如 W4N1)
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
Game.cli.exec("storage.db['rooms.objects'].find({ type: { $in: ['constructedWall', 'rampart'] }, room: { $in: ['W4N1'] } }).then(resp => resp.map(cs => storage.db['rooms.objects'].findOne({ _id: cs._id }).then(csDetail => storage.db['rooms.objects'].update({ _id: cs._id }, { $set: { hits: 10000000 } }))))")
|
|
132
|
+
```
|
|
133
|
+
|
|
96
134
|
## 安全说明
|
|
97
135
|
|
|
98
136
|
执行服务端 CLI JS 权限很大:
|
|
@@ -113,7 +151,7 @@ Game.cli.finishConstructionSites(['W1N9'])
|
|
|
113
151
|
- 确认已在 `mods.json` 启用并重启私服。
|
|
114
152
|
- 确认环境可用 `isolated-vm`(Screeps 玩家沙箱依赖它)。
|
|
115
153
|
- **提示 `[cli] denied: not allowed user`**:
|
|
116
|
-
-
|
|
154
|
+
- 在 `execute-cli.config.json` 中把你的 userId / 用户名加入 `normalUserIds/normalUsernames` 或 `superAdminUserIds/superAdminUsernames`。
|
|
117
155
|
- **输出缺失/被截断**:
|
|
118
156
|
- 调整 `maxOutputLines`、`evalTimeoutMs`、`promiseTimeoutMs`。
|
|
119
157
|
|
package/index.js
CHANGED
|
@@ -19,10 +19,29 @@ const EventEmitter = require('events').EventEmitter;
|
|
|
19
19
|
const vm = require('vm');
|
|
20
20
|
const util = require('util');
|
|
21
21
|
|
|
22
|
-
// ---- Settings (
|
|
22
|
+
// ---- Settings (defaults, can be overridden via execute-cli.config.json in server root) ----
|
|
23
|
+
//
|
|
24
|
+
// To customize settings WITHOUT editing this file, create `execute-cli.config.json` in the
|
|
25
|
+
// server root directory (same level as mods.json). Example:
|
|
26
|
+
//
|
|
27
|
+
// {
|
|
28
|
+
// "allowAllUsers": false,
|
|
29
|
+
// "normalUsernames": ["player1", "player2"],
|
|
30
|
+
// "superAdminUsernames": ["admin"]
|
|
31
|
+
// }
|
|
32
|
+
//
|
|
33
|
+
// Only include the fields you want to override; missing fields use defaults below.
|
|
34
|
+
|
|
35
|
+
const fs = require('fs');
|
|
23
36
|
|
|
24
|
-
|
|
37
|
+
/**
|
|
38
|
+
* DEFAULT_SETTINGS - These are the default values for all configuration options.
|
|
39
|
+
* They can be overridden by `execute-cli.config.json` in the server root.
|
|
40
|
+
*/
|
|
41
|
+
const DEFAULT_SETTINGS = {
|
|
25
42
|
/**
|
|
43
|
+
* allowAllUsers (boolean, default: false)
|
|
44
|
+
*
|
|
26
45
|
* If true, EVERY real player is treated as a "normal" allowed user (see `resolveRole()`),
|
|
27
46
|
* i.e. they can call `Game.cli.exec()` without being in `normalUserIds/normalUsernames`.
|
|
28
47
|
*
|
|
@@ -36,23 +55,33 @@ const SETTINGS = {
|
|
|
36
55
|
allowAllUsers: false,
|
|
37
56
|
|
|
38
57
|
/**
|
|
58
|
+
* normalUserIds / normalUsernames (array of strings, default: [])
|
|
59
|
+
*
|
|
39
60
|
* Normal permission allow-list (used only when allowAllUsers=false).
|
|
40
61
|
* Normal users can ONLY access:
|
|
41
62
|
* - storage.db.rooms (restricted to rooms they own)
|
|
42
63
|
* - storage.db.objects / storage.db['rooms.objects'] (restricted to {user: self})
|
|
43
64
|
* - storage.db.creeps (view over rooms.objects with {type:'creep', user:self})
|
|
65
|
+
*
|
|
66
|
+
* In many private servers user ids are simple strings like "1", "4", ...
|
|
67
|
+
* NPC users are usually "2" (Invader) and "3" (Source Keeper) and are blocked anyway.
|
|
44
68
|
*/
|
|
45
69
|
normalUserIds: [],
|
|
46
70
|
normalUsernames: [],
|
|
47
71
|
|
|
48
72
|
/**
|
|
73
|
+
* superAdminUserIds / superAdminUsernames (array of strings, default: [])
|
|
74
|
+
*
|
|
49
75
|
* Super admin allow-list.
|
|
50
|
-
* Super admins can execute any CLI JS and access all CLI sandbox objects
|
|
76
|
+
* Super admins can execute any CLI JS and access all CLI sandbox objects
|
|
77
|
+
* (storage, system, map, bots, strongholds, etc).
|
|
51
78
|
*/
|
|
52
79
|
superAdminUserIds: [],
|
|
53
80
|
superAdminUsernames: [],
|
|
54
81
|
|
|
55
82
|
/**
|
|
83
|
+
* superAdminUsersCodeSelfOnly (boolean, default: true)
|
|
84
|
+
*
|
|
56
85
|
* Privacy guard:
|
|
57
86
|
* Even for super admins, do NOT allow reading/modifying other users' code in `users.code`.
|
|
58
87
|
* (Super admins can still access other tables unless you restrict them elsewhere.)
|
|
@@ -60,25 +89,108 @@ const SETTINGS = {
|
|
|
60
89
|
superAdminUsersCodeSelfOnly: true,
|
|
61
90
|
|
|
62
91
|
/**
|
|
92
|
+
* allowedCodePrefixes (array of strings, default: [])
|
|
93
|
+
*
|
|
63
94
|
* Optional prefix allow-list for the CLI JS string.
|
|
64
95
|
* If non-empty, only commands starting with one of these prefixes will be allowed.
|
|
65
96
|
*
|
|
66
97
|
* Examples:
|
|
67
98
|
* - ['system.', 'map.'] -> allow only system.* and map.* helpers
|
|
68
99
|
* - ['help(', 'print('] -> allow only help()/print()
|
|
69
|
-
* - []
|
|
100
|
+
* - [] -> allow any JS (still subject to user allow-list)
|
|
70
101
|
*/
|
|
71
102
|
allowedCodePrefixes: [],
|
|
72
103
|
|
|
73
104
|
/**
|
|
74
|
-
*
|
|
105
|
+
* maxCodeLength (number, default: 2000)
|
|
106
|
+
*
|
|
107
|
+
* Maximum length of CLI code string allowed per call.
|
|
75
108
|
*/
|
|
76
109
|
maxCodeLength: 2000,
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* maxOutputLines (number, default: 60)
|
|
113
|
+
*
|
|
114
|
+
* Maximum number of output lines sent back to the player's console per call.
|
|
115
|
+
*/
|
|
77
116
|
maxOutputLines: 60,
|
|
78
|
-
|
|
79
|
-
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* evalTimeoutMs (number, default: 2000)
|
|
120
|
+
*
|
|
121
|
+
* Node vm timeout (in milliseconds) for synchronous code execution.
|
|
122
|
+
* Prevents infinite loops from blocking the server.
|
|
123
|
+
*/
|
|
124
|
+
evalTimeoutMs: 2000,
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* promiseTimeoutMs (number, default: 5000)
|
|
128
|
+
*
|
|
129
|
+
* Timeout (in milliseconds) for waiting on returned promise/thenable results.
|
|
130
|
+
*/
|
|
131
|
+
promiseTimeoutMs: 5000,
|
|
80
132
|
};
|
|
81
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Load external config from `execute-cli.config.json` in the server root directory
|
|
136
|
+
* (same level as mods.json). If the file exists and is valid JSON, its values will
|
|
137
|
+
* be merged into SETTINGS. This way you can update the mod code without losing your config.
|
|
138
|
+
*
|
|
139
|
+
* Config file search order:
|
|
140
|
+
* 1. Directory of MODFILE env var (Screeps server sets this to mods.json path)
|
|
141
|
+
* 2. Current working directory (process.cwd())
|
|
142
|
+
* 3. One level up from this file's directory (for example-mods/ installation)
|
|
143
|
+
*/
|
|
144
|
+
function loadExternalConfig() {
|
|
145
|
+
const CONFIG_FILENAME = 'execute-cli.config.json';
|
|
146
|
+
const candidateDirs = [];
|
|
147
|
+
|
|
148
|
+
// 1. Use MODFILE env var if available (Screeps server sets this)
|
|
149
|
+
if (process.env.MODFILE) {
|
|
150
|
+
candidateDirs.push(path.dirname(path.resolve(process.cwd(), process.env.MODFILE)));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 2. Current working directory
|
|
154
|
+
candidateDirs.push(process.cwd());
|
|
155
|
+
|
|
156
|
+
// 3. One level up from __dirname (for example-mods/ or similar)
|
|
157
|
+
candidateDirs.push(path.resolve(__dirname, '..'));
|
|
158
|
+
|
|
159
|
+
// 4. Two levels up (for node_modules/xxx/ installation)
|
|
160
|
+
candidateDirs.push(path.resolve(__dirname, '..', '..'));
|
|
161
|
+
|
|
162
|
+
// 5. Three levels up (for node_modules/@scope/xxx/ installation)
|
|
163
|
+
candidateDirs.push(path.resolve(__dirname, '..', '..', '..'));
|
|
164
|
+
|
|
165
|
+
// Deduplicate
|
|
166
|
+
const seen = new Set();
|
|
167
|
+
const uniqueDirs = candidateDirs.filter(d => {
|
|
168
|
+
const resolved = path.resolve(d);
|
|
169
|
+
if (seen.has(resolved)) return false;
|
|
170
|
+
seen.add(resolved);
|
|
171
|
+
return true;
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
for (const dir of uniqueDirs) {
|
|
175
|
+
const configPath = path.join(dir, CONFIG_FILENAME);
|
|
176
|
+
try {
|
|
177
|
+
if (fs.existsSync(configPath)) {
|
|
178
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
179
|
+
const ext = JSON.parse(raw);
|
|
180
|
+
console.log('[execute-cli mod] Loaded config from', configPath);
|
|
181
|
+
return ext;
|
|
182
|
+
}
|
|
183
|
+
} catch (e) {
|
|
184
|
+
console.error('[execute-cli mod] Failed to load config from', configPath, ':', e && (e.message || e));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log('[execute-cli mod] No config file found, using defaults. Searched:', uniqueDirs.map(d => path.join(d, CONFIG_FILENAME)).join(', '));
|
|
189
|
+
return {};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const SETTINGS = Object.assign({}, DEFAULT_SETTINGS, loadExternalConfig());
|
|
193
|
+
|
|
82
194
|
// ---------------------------------------------------------------------------
|
|
83
195
|
|
|
84
196
|
// In Steam-distributed Screeps server, dependencies may live under `package/node_modules/`
|
|
@@ -142,7 +254,7 @@ function normalizeIdSet(list) {
|
|
|
142
254
|
}
|
|
143
255
|
|
|
144
256
|
function normalizeNameSet(list) {
|
|
145
|
-
return new Set(normalizeList(list)
|
|
257
|
+
return new Set(normalizeList(list));
|
|
146
258
|
}
|
|
147
259
|
|
|
148
260
|
function getEffectiveSuperAdminSets() {
|
|
@@ -455,19 +567,19 @@ async function resolveRole(userId) {
|
|
|
455
567
|
userId = String(userId);
|
|
456
568
|
const superSets = getEffectiveSuperAdminSets();
|
|
457
569
|
const normalSets = getEffectiveNormalSets();
|
|
570
|
+
const defaultRole = SETTINGS.allowAllUsers ? 'normal': null
|
|
458
571
|
|
|
459
572
|
if (superSets.ids.has(userId)) return 'super';
|
|
460
|
-
if (SETTINGS.allowAllUsers) return 'normal';
|
|
461
573
|
if (normalSets.ids.has(userId)) return 'normal';
|
|
462
574
|
|
|
463
575
|
const needNameLookup = superSets.names.size > 0 || normalSets.names.size > 0;
|
|
464
|
-
if (!needNameLookup) return
|
|
576
|
+
if (!needNameLookup) return defaultRole;
|
|
465
577
|
|
|
466
578
|
const user = await common.storage.db.users.findOne({_id: userId});
|
|
467
|
-
const usernameLower = user && user.username ? String(user.username)
|
|
579
|
+
const usernameLower = user && user.username ? String(user.username) : '';
|
|
468
580
|
if (usernameLower && superSets.names.has(usernameLower)) return 'super';
|
|
469
581
|
if (usernameLower && normalSets.names.has(usernameLower)) return 'normal';
|
|
470
|
-
return
|
|
582
|
+
return defaultRole;
|
|
471
583
|
}
|
|
472
584
|
|
|
473
585
|
function withTimeout(promise, ms, label) {
|