screepsmod-exec-cli-in-console 1.0.3 → 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.
Files changed (4) hide show
  1. package/README.md +43 -13
  2. package/README.zh.md +43 -13
  3. package/index.js +121 -9
  4. 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
- Edit `execute-cli.js` and update `SETTINGS`:
46
-
47
- - `allowAllUsers`: if `true`, every real player becomes a **normal** user (NOT super admin). Keep `false` unless this is a local/dev server.
48
- - `normalUserIds` / `normalUsernames`: allow-list for **normal** users when `allowAllUsers=false`.
49
- - `superAdminUserIds` / `superAdminUsernames`: allow-list for **super** users.
50
- - `superAdminUsersCodeSelfOnly`: if `true`, even super admins cannot read/modify other users' code in `users.code`.
51
- - `allowedCodePrefixes`: if non-empty, only code starting with one of these prefixes is allowed.
52
- - Limits:
53
- - `maxCodeLength`
54
- - `maxOutputLines`
55
- - `evalTimeoutMs`
56
- - `promiseTimeoutMs`
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
 
@@ -101,6 +125,12 @@ Game.cli.finishConstructionSites(['W1N9'])
101
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);});});});})()")
102
126
  ```
103
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
+
104
134
  ## Security notes
105
135
 
106
136
  Executing arbitrary server CLI JS is powerful:
@@ -121,7 +151,7 @@ Role behavior:
121
151
  - Make sure the mod is enabled in `mods.json` and the server restarted.
122
152
  - Ensure your server has `isolated-vm` available (Screeps uses it for player sandbox).
123
153
  - **`[cli] denied: not allowed user`**:
124
- - Add your user id / username to `SETTINGS.normalUserIds/normalUsernames` or `superAdmin*`.
154
+ - Add your user id / username to `normalUserIds/normalUsernames` or `superAdminUserIds/superAdminUsernames` in `execute-cli.config.json`.
125
155
  - **No output / output truncated**:
126
156
  - Adjust `maxOutputLines`, `evalTimeoutMs`, `promiseTimeoutMs`.
127
157
 
package/README.zh.md CHANGED
@@ -42,18 +42,42 @@
42
42
 
43
43
  ## 配置(非常重要)
44
44
 
45
- 请直接编辑 `execute-cli.js` 顶部的 `SETTINGS`:
46
-
47
- - `allowAllUsers`:若为 `true`,所有真实玩家都会被视为**普通用户**(不是超管)。除非本地/单机测试服,否则不要开。
48
- - `normalUserIds` / `normalUsernames`:当 `allowAllUsers=false` 时,普通用户白名单。
49
- - `superAdminUserIds` / `superAdminUsernames`:超管白名单。
50
- - `superAdminUsersCodeSelfOnly`:若为 `true`,即便是超管也不允许读/改其他用户在 `users.code` 里的代码(隐私保护)。
51
- - `allowedCodePrefixes`:如果不为空,只允许执行以这些前缀开头的代码字符串。
52
- - 限制项:
53
- - `maxCodeLength`
54
- - `maxOutputLines`
55
- - `evalTimeoutMs`
56
- - `promiseTimeoutMs`
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
 
@@ -101,6 +125,12 @@ Game.cli.finishConstructionSites(['W1N9'])
101
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);});});});})()")
102
126
  ```
103
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
+
104
134
  ## 安全说明
105
135
 
106
136
  执行服务端 CLI JS 权限很大:
@@ -121,7 +151,7 @@ Game.cli.exec("(function(){var hp=300000000;return storage.db.rooms.find({}).the
121
151
  - 确认已在 `mods.json` 启用并重启私服。
122
152
  - 确认环境可用 `isolated-vm`(Screeps 玩家沙箱依赖它)。
123
153
  - **提示 `[cli] denied: not allowed user`**:
124
- - 把你的 userId / 用户名加入 `SETTINGS.normalUserIds/normalUsernames` 或 `superAdmin*`。
154
+ - `execute-cli.config.json` 中把你的 userId / 用户名加入 `normalUserIds/normalUsernames` 或 `superAdminUserIds/superAdminUsernames`。
125
155
  - **输出缺失/被截断**:
126
156
  - 调整 `maxOutputLines`、`evalTimeoutMs`、`promiseTimeoutMs`。
127
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 (edit me) ----------------------------------------------------
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
- const SETTINGS = {
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
- * - [] -> allow any JS (still subject to user allow-list)
100
+ * - [] -> allow any JS (still subject to user allow-list)
70
101
  */
71
102
  allowedCodePrefixes: [],
72
103
 
73
104
  /**
74
- * Basic abuse limits.
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
- evalTimeoutMs: 2000, // Node vm timeout for sync execution
79
- promiseTimeoutMs: 5000, // waiting for returned promise/thenable
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).map(i => i.toLowerCase()));
257
+ return new Set(normalizeList(list));
146
258
  }
147
259
 
148
260
  function getEffectiveSuperAdminSets() {
@@ -464,7 +576,7 @@ async function resolveRole(userId) {
464
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).toLowerCase() : '';
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
582
  return defaultRole;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "screepsmod-exec-cli-in-console",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Allow players to execute (some) server CLI-only commands from the in-game console.",
5
5
  "main": "index.js",
6
6
  "scripts": {},