yunti-browser-runtime 0.1.0 → 0.1.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/README.md CHANGED
@@ -59,7 +59,8 @@ yunti-browser-runtime package-extension
59
59
  http://127.0.0.1:48887
60
60
  ```
61
61
 
62
- bridge 会在启动时生成或读取本地访问 token,并打印到 stderr。也可以显式配置:
62
+ 默认本地 bridge 只监听 `127.0.0.1`,不需要配置 token。需要额外加固本机访问时,
63
+ 可以显式启用 token:
63
64
 
64
65
  ```bash
65
66
  YUNTI_BROWSER_BRIDGE_TOKEN="$(openssl rand -hex 24)" npm run bridge
@@ -78,6 +79,30 @@ npm run doctor
78
79
  npm run doctor:json
79
80
  ```
80
81
 
82
+ ## 复制给 Agent 的安装引导
83
+
84
+ 把下面这段话复制给你正在使用的 Agent,让它一步步引导你安装和验证:
85
+
86
+ ```text
87
+ 请帮我安装并接入 Yunti Browser Runtime。它是一个本地浏览器运行时,让你通过 MCP 操作我本机 Chrome/Edge 页面。
88
+
89
+ 请按步骤引导我完成,不要跳步:
90
+
91
+ 1. 确认我本机有 Node.js 22+。
92
+ 2. 执行:npm install -g yunti-browser-runtime
93
+ 3. 执行:yunti-browser-runtime print-config -- --agent 当前Agent名称 --human
94
+ 4. 根据输出,把 MCP server 配置加入当前 Agent 的 MCP 配置。
95
+ 5. 执行:yunti-browser-runtime bridge
96
+ 6. 引导我打开 Chrome/Edge 的扩展管理页,开启开发者模式,手动加载扩展目录:
97
+ $(npm root -g)/yunti-browser-runtime/extension
98
+ 7. 告诉我扩展里默认 bridge URL 是 http://127.0.0.1:48887,本地默认不需要 token,也不需要保存设置。
99
+ 8. 打开任意 http/https 页面并刷新。
100
+ 9. 执行:yunti-browser-runtime doctor
101
+ 10. 如果 doctor 正常,再调用 yunti_list_browser_targets 或 yunti_get_tool_usage_hints 验证你能看到浏览器页面。
102
+
103
+ 注意:Chrome 扩展不能由 npm 静默安装,必须由我手动在浏览器扩展页加载。你要一步步告诉我点哪里、复制什么、检查什么。
104
+ ```
105
+
81
106
  ## 安装浏览器扩展
82
107
 
83
108
  开发加载方式:
@@ -87,10 +112,11 @@ npm run doctor:json
87
112
  3. 点击“加载已解压的扩展程序”。
88
113
  4. 选择本项目的 `extension/` 目录。
89
114
  5. 打开任意 `http` 或 `https` 页面。
90
- 6. 点击扩展图标,填写 bridge token,保存设置。
91
- 7. 刷新目标页面,确认页面已连接。
115
+ 6. 刷新目标页面,确认页面已连接。
92
116
 
93
- 扩展只负责把页面注册到本地 bridge,并执行 Agent 发来的浏览器动作。
117
+ 扩展默认使用 `http://127.0.0.1:48887`,本地安装不需要 token,也不需要保存设置。
118
+ 扩展 popup 只用于自定义 bridge URL、页面匹配或可选 token。扩展只负责把页面注册到
119
+ 本地 bridge,并执行 Agent 发来的浏览器动作。
94
120
 
95
121
  也可以生成 zip 包用于分发或归档:
96
122
 
@@ -239,7 +265,10 @@ YUNTI_E2E=1 npm run test:e2e
239
265
 
240
266
  ### bridge token 是什么?
241
267
 
242
- bridge token 是本地 HTTP bridge 的访问凭证。MCP server、extension 和 doctor 使用同一个 token,避免普通网页误调用本地接口。
268
+ 默认本地安装不需要 bridge token。bridge 只监听 `127.0.0.1`,扩展和 MCP server
269
+ 可以直接连接,减少首次使用心智。需要加固共享机器或自定义部署时,可设置
270
+ `YUNTI_BROWSER_BRIDGE_TOKEN`,这时 MCP server、extension 和 doctor 必须使用同一个
271
+ token。
243
272
 
244
273
  ### 会读取 cookie 或密码吗?
245
274
 
@@ -249,8 +278,9 @@ bridge token 是本地 HTTP bridge 的访问凭证。MCP server、extension 和
249
278
 
250
279
  调用 `yunti_list_browser_targets` 获取最新浏览器路由,再用返回的 `browserSessionId` 继续操作。
251
280
 
252
- ## 当前未完成项
281
+ ## 发布状态与后续事项
253
282
 
254
- - 如确认要发布 `0.1.0`,运行 `npm run release:publish`。
255
- - 当前 `npm view yunti-browser-runtime` 返回 E404;如 `0.1.0` 是首次发布,这是预期状态。
256
- - 发布前如要上架浏览器扩展商店,需要再次审查 broad host permissions。
283
+ - `yunti-browser-runtime@0.1.2` 已发布到官方 npm registry。
284
+ - `0.1.2` 将扩展 popup 的 token 移入高级设置,并补充可复制给 Agent 的安装引导。
285
+ - 发布后验证命令:`npm run release:verify-published`。
286
+ - 如要上架浏览器扩展商店,需要再次审查 broad host permissions。
@@ -54,7 +54,9 @@
54
54
  | P4.16 | 已完成 | 正式发布脚本内置门禁 |
55
55
  | P4.17 | 已完成 | npm 登录预检门禁 |
56
56
  | P4.18 | 已完成 | npm 发布后验证命令 |
57
- | P4.19 | 进行中 | npm 正式发布执行 |
57
+ | P4.19 | 已完成 | npm 正式发布执行 |
58
+ | P5.1 | 已完成 | 本地默认免 bridge token |
59
+ | P5.2 | 已完成 | 扩展 popup 与安装引导简化 |
58
60
 
59
61
  ## P0.1 bridge token + CORS 收紧
60
62
 
@@ -1151,8 +1153,9 @@ release gate、npm package 内容检查和 extension zip 内容检查。
1151
1153
 
1152
1154
  ### 验收记录
1153
1155
 
1154
- - `npm run release:whoami` 当前返回 `ENEEDAUTH`,说明本机尚未登录
1155
- `https://registry.npmjs.org/`。
1156
+ - 发布前,`npm run release:whoami` 曾在未登录状态返回 `ENEEDAUTH`,验证缺少 npm
1157
+ 登录时会提前失败。
1158
+ - 登录后,`npm run release:whoami` 返回 `xuanzhu`,并在正式发布前通过预检。
1156
1159
  - `release:publish` 已内置登录预检;登录前不会进入正式 npm publish。
1157
1160
 
1158
1161
  ## P4.18 npm 发布后验证命令
@@ -1186,9 +1189,10 @@ metadata 与当前 `package.json` 一致。
1186
1189
 
1187
1190
  ### 验收记录
1188
1191
 
1189
- - `npm run release:verify-published` 当前返回非零退出码,报告
1190
- `yunti-browser-runtime@0.1.0` 尚未发布到 `https://registry.npmjs.org/`;
1191
- 失败路径输出结构化 JSON,且不包含 npm 本机 debug log 路径。
1192
+ - 发布前,`npm run release:verify-published` 曾返回非零退出码并输出结构化未发布报告,
1193
+ 且失败路径不包含 npm 本机 debug log 路径。
1194
+ - 发布后,`npm run release:verify-published` 返回 0,确认
1195
+ `yunti-browser-runtime@0.1.0` 已发布到 `https://registry.npmjs.org/`。
1192
1196
  - `node --check scripts/check-published-package.js` 通过。
1193
1197
  - `npm run release:check` 通过:`npm run check` 已覆盖
1194
1198
  `scripts/check-published-package.js`,npm package contents check 确认 tarball
@@ -1198,7 +1202,7 @@ metadata 与当前 `package.json` 一致。
1198
1202
 
1199
1203
  ## P4.19 npm 正式发布执行
1200
1204
 
1201
- 状态:进行中(2026-07-03,阻塞于 npm 2FA OTP 或具备 bypass 2FA 的发布 token
1205
+ 状态:已完成(2026-07-03)
1202
1206
 
1203
1207
  执行摘要:
1204
1208
 
@@ -1206,8 +1210,8 @@ metadata 与当前 `package.json` 一致。
1206
1210
  - 已按用户确认执行 `npm run release:publish`。
1207
1211
  - `release:publish` 已通过内置 `release:prepublish`、`release:whoami` 和全部本地
1208
1212
  发布门禁;本次测试覆盖 59 项,58 项通过,1 项真实浏览器 smoke 按配置跳过。
1209
- - 本次正式发布已进入 `npm publish --registry=https://registry.npmjs.org/`,tarball
1210
- 包含 40 个文件。
1213
+ - 正式发布进入 `npm publish --registry=https://registry.npmjs.org/`,tarball 包含
1214
+ 40 个文件。
1211
1215
  - npm registry 返回 `E403`:当前账号发布包需要双因素认证 OTP,或使用开启
1212
1216
  bypass 2FA 的 granular access token。
1213
1217
  - 已使用临时 npm token 重试 `npm run release:publish`;本地发布门禁再次通过,但
@@ -1217,7 +1221,11 @@ metadata 与当前 `package.json` 一致。
1217
1221
  但重新执行 `npm run release:publish` 仍在 `npm publish` 阶段返回相同 `E403`。
1218
1222
  - 已用新的 npm token 覆盖项目级 `.npmrc` 后再次确认 `npm whoami` 返回 `xuanzhu`;
1219
1223
  `npm run release:publish` 仍通过本地门禁并在 `npm publish` 阶段返回相同 `E403`。
1220
- - npm 2FA 阻塞,`yunti-browser-runtime@0.1.0` 尚未发布成功。
1224
+ - 已用具备发布权限的 npm token 覆盖项目级 `.npmrc` 后再次执行
1225
+ `npm run release:publish`,正式发布成功:`yunti-browser-runtime@0.1.0` 已发布到
1226
+ `https://registry.npmjs.org/`。
1227
+ - `npm run release:verify-published` 通过,确认 registry 上的 name、version、
1228
+ repository、homepage、bugs 和 tarball URL 与本地 `package.json` 匹配。
1221
1229
 
1222
1230
  ### 目标
1223
1231
 
@@ -1234,11 +1242,98 @@ metadata 与当前 `package.json` 一致。
1234
1242
  repository、homepage、bugs 和 tarball URL 与本地 `package.json` 匹配。
1235
1243
  - 发布结果同步到 `docs/PROJECT_STATUS.md`。
1236
1244
 
1237
- ### 当前阻塞
1245
+ ### 验收记录
1246
+
1247
+ - `npm run release:publish` 成功发布 `yunti-browser-runtime@0.1.0`。
1248
+ - 发布前门禁通过:metadata URL 检查、公开文档残留检查、Markdown 链接检查、版本一致性、
1249
+ CLI smoke、print-config smoke、doctor smoke、`npm run check`、`npm test`、npm
1250
+ package contents check 和 extension zip contents check。
1251
+ - `npm test` 覆盖 59 项测试:58 项通过,1 项真实浏览器 smoke 按配置跳过。
1252
+ - `npm run release:verify-published` 返回 0。
1253
+ - `npm view yunti-browser-runtime@0.1.0 ... --registry=https://registry.npmjs.org/`
1254
+ 返回已发布版本和正确 metadata。
1255
+
1256
+ ## P5.1 本地默认免 bridge token
1257
+
1258
+ 状态:已完成(2026-07-03)
1259
+
1260
+ 实现摘要:
1261
+
1262
+ - 默认 `127.0.0.1` 本地 bridge 不再自动生成随机 token,也不要求 extension、doctor
1263
+ 或 MCP proxy 携带 `x-yunti-browser-token`。
1264
+ - 用户显式设置 `YUNTI_BROWSER_BRIDGE_TOKEN` 时,bridge 继续强制校验
1265
+ `x-yunti-browser-token` 或 `Authorization: Bearer <token>`。
1266
+ - bridge 绑定到非 loopback host 时必须设置 `YUNTI_BROWSER_BRIDGE_TOKEN`,避免把
1267
+ 本地免 token 默认值误用于远程或局域网部署。
1268
+ - `npm run doctor` 默认显示 token not required;只有 bridge 明确要求 token 且请求
1269
+ 未授权时才提示设置 token。
1270
+ - `npm run print-config` 默认不再要求用户配置 token,仅保留需要加固时的可选说明。
1271
+ - 真实浏览器 smoke test 改为覆盖默认免 token 路径。
1272
+ - package 和 extension 版本同步提升到 `0.1.1`。
1273
+
1274
+ ### 目标
1275
+
1276
+ 降低本地安装和首次使用心智:用户只需要启动 bridge、加载扩展、确认 bridge URL,
1277
+ 不需要复制或理解 bridge token。安全边界仍保持本地 loopback 默认,显式 token 用于
1278
+ 共享机器、非本地绑定或额外加固场景。
1279
+
1280
+ ### 验收标准
1281
+
1282
+ - 默认 `npm run bridge` 启动的 `127.0.0.1` bridge 不要求 token。
1283
+ - 未配置 token 时,extension 可以注册页面,doctor 可以读取完整 health。
1284
+ - 配置 `YUNTI_BROWSER_BRIDGE_TOKEN` 后,未携带 token 的受保护 HTTP route 仍返回 401。
1285
+ - `YUNTI_BROWSER_BRIDGE_HOST` 绑定非 loopback 地址且未配置 token 时启动失败。
1286
+ - README、INSTALL、SECURITY、PROJECT_STATUS 同步新的默认使用路径。
1287
+ - `npm run release:check` 通过。
1288
+ - `npm run release:publish` 发布 `yunti-browser-runtime@0.1.1`,并通过
1289
+ `npm run release:verify-published` 验证。
1290
+
1291
+ ### 验收记录
1292
+
1293
+ - `npm test` 通过:61 项测试,60 项通过,1 项真实浏览器 smoke 按配置跳过。
1294
+ - 新增测试覆盖本地默认免 token、显式 token 仍强制校验、非 loopback 无 token
1295
+ 启动失败。
1296
+ - `npm run check` 通过。
1297
+ - `npm run release:check` 通过。
1298
+ - `npm run release:publish` 成功发布 `yunti-browser-runtime@0.1.1`。
1299
+ - `npm run release:verify-published` 返回 0。
1300
+
1301
+ ## P5.2 扩展 popup 与安装引导简化
1238
1302
 
1239
- 等待 npm 2FA OTP 或可绕过 2FA 的发布 token。已确认普通 token 或未开启 bypass
1240
- 2FA 的 token 无法完成本次发布;项目级 `.npmrc` 方式和两次 token 认证都已复验。
1241
- 拿到 OTP 后只需要重跑正式发布命令,不需要继续增加新的发布门禁。
1303
+ 状态:已完成(2026-07-03)
1304
+
1305
+ 实现摘要:
1306
+
1307
+ - 扩展 popup 首屏不再展示 Bridge Token 输入框,避免用户误以为本地默认安装需要填写
1308
+ token。
1309
+ - Bridge Token 移入“高级设置”,并标注为可选;如果用户已有 token 配置,高级设置会
1310
+ 自动展开。
1311
+ - popup 默认状态文案改为“默认无需设置,打开或刷新 http/https 页面即可”。
1312
+ - README 增加“复制给 Agent 的安装引导”,让用户可以把一段话交给 Agent,由 Agent
1313
+ 分步骤带用户完成 npm 安装、MCP 配置、扩展加载和 doctor 验证。
1314
+ - README / INSTALL 明确:加载扩展后默认不需要保存设置,不需要 token。
1315
+ - package 和 extension 版本同步提升到 `0.1.2`。
1316
+
1317
+ ### 目标
1318
+
1319
+ 让用户在安装扩展后尽量立即可用:默认无需填写 token、无需保存 popup 设置;需要
1320
+ 自定义 bridge URL、页面匹配或 token 时再进入高级/设置路径。
1321
+
1322
+ ### 验收标准
1323
+
1324
+ - popup 首屏不出现 Bridge Token 输入框。
1325
+ - popup 仍保留可选 token 配置能力。
1326
+ - README 包含可复制给 Agent 的安装引导词。
1327
+ - README / INSTALL 不再要求默认本地安装时点击 popup 保存 token 或保存设置。
1328
+ - `npm run release:check` 通过。
1329
+ - `npm run release:publish` 发布 `yunti-browser-runtime@0.1.2`,并通过
1330
+ `npm run release:verify-published` 验证。
1331
+
1332
+ ### 验收记录
1333
+
1334
+ - `npm run release:check` 通过。
1335
+ - `npm run release:publish` 成功发布 `yunti-browser-runtime@0.1.2`。
1336
+ - `npm run release:verify-published` 返回 0。
1242
1337
 
1243
1338
  ## 每阶段完成后的固定检查
1244
1339
 
@@ -1261,18 +1356,11 @@ rg "/U[s]ers|C[o]deg|x[y]y|y[b]m100" README.md docs skills package.json
1261
1356
 
1262
1357
  ## 当前下一步
1263
1358
 
1264
- P4.3-P4.18 已完成,P4.19 正在执行:
1265
-
1266
- - `npm run release:dry-run` 已通过内置发布前门禁和官方 npm registry 预览。
1267
- - `npm run release:whoami` 已确认登录账号为 `xuanzhu`。
1268
- - `npm run release:publish` 已通过本地发布门禁,但正式 `npm publish` 被 npm 2FA
1269
- 要求拦截。
1270
- - 已尝试临时 npm token 发布,仍被 npm 2FA 策略以相同 `E403` 拦截。
1271
- - 已创建项目级 `.npmrc` 并使用实际 token 复验,`npm whoami` 成功但 publish 仍返回
1272
- 相同 `E403`。
1273
- - 已用新的 npm token 覆盖项目级 `.npmrc` 复验,认证仍成功但 publish 仍返回相同
1274
- `E403`。
1275
- - 下一步使用当前 npm OTP 执行 `npm run release:publish -- --otp=<6-digit-code>`,
1276
- 或改用具备 publish 权限且允许 bypass 2FA 的 granular access token。
1277
- - 发布后执行 `npm run release:verify-published` 确认 npm registry 版本和 metadata。
1278
- - 浏览器扩展如需上架商店,发布前还需重新审查 broad host permissions。
1359
+ P0.1-P5.2 已完成,`yunti-browser-runtime@0.1.2` 已发布到官方 npm registry。
1360
+
1361
+ - 发布后验证已通过:`npm run release:verify-published`。
1362
+ - 本地默认使用不再需要 bridge token;需要加固时可显式设置
1363
+ `YUNTI_BROWSER_BRIDGE_TOKEN`。
1364
+ - 扩展 popup 默认不需要用户保存设置;token 已移入高级可选项。
1365
+ - 后续如要上架浏览器扩展商店,发布前还需重新审查 broad host permissions。
1366
+ - 后续版本开发前,先在本文档新增下一阶段目标、范围和验收标准。
package/docs/INSTALL.md CHANGED
@@ -27,8 +27,8 @@ Use a custom port when needed:
27
27
  YUNTI_BROWSER_BRIDGE_PORT=48999 npm run bridge
28
28
  ```
29
29
 
30
- bridge 会在启动时读取或生成本地访问 token,并把请求头名称和值打印到 stderr。
31
- 推荐在 MCP、doctor 和扩展里使用同一个显式 token:
30
+ 默认本地 bridge 只监听 `127.0.0.1`,不需要配置 token。需要额外加固本机访问、
31
+ 或自定义部署时,可以显式启用 token:
32
32
 
33
33
  ```bash
34
34
  YUNTI_BROWSER_BRIDGE_TOKEN="$(openssl rand -hex 24)" npm run bridge
@@ -54,12 +54,14 @@ Development loading:
54
54
  2. Enable developer mode.
55
55
  3. Choose "Load unpacked".
56
56
  4. Select the project `extension/` directory.
57
- 5. Click the extension icon and save the same bridge token printed by the bridge.
58
- 6. Open any `http` or `https` page and click the extension icon to confirm the
59
- page is connected.
57
+ 5. Open or refresh any `http` or `https` page.
58
+ 6. Click the extension icon only if you want to confirm status or customize
59
+ settings.
60
60
 
61
- The extension registers browser pages with the local bridge. It does not create
62
- agent conversations or depend on a remote workspace.
61
+ The extension defaults to `http://127.0.0.1:48887`. Local installs do not need a
62
+ token and do not require saving popup settings. The extension registers browser
63
+ pages with the local bridge. It does not create agent conversations or depend on
64
+ a remote workspace.
63
65
 
64
66
  To create a distributable zip:
65
67
 
@@ -117,9 +119,10 @@ npm run print-config -- --agent claude-code --human
117
119
  ```
118
120
 
119
121
  Add the printed `mcpServers.yunti-browser-runtime` block to Claude Code's MCP
120
- configuration. If using a bridge token, set `YUNTI_BROWSER_BRIDGE_TOKEN` in the
121
- same environment Claude Code uses to launch the MCP server, and save the same
122
- token in the extension popup.
122
+ configuration. The default local loopback setup does not require a bridge token.
123
+ If using a bridge token, set `YUNTI_BROWSER_BRIDGE_TOKEN` in the same environment
124
+ Claude Code uses to launch the MCP server, and save the same token in the
125
+ extension popup.
123
126
 
124
127
  #### Cursor
125
128
 
@@ -176,8 +179,8 @@ runs the unit test suite, and verifies the npm package contents with
176
179
  ## Common Recovery
177
180
 
178
181
  - If tools say no browser tab is connected, refresh the target page.
179
- - If doctor reports `authorized: false`, set `YUNTI_BROWSER_BRIDGE_TOKEN` and
180
- save the same token in the extension popup.
182
+ - If doctor reports `authorized: false` with `authRequired: true`, set
183
+ `YUNTI_BROWSER_BRIDGE_TOKEN` and save the same token in the extension popup.
181
184
  - If an old `browserSessionId` fails, call `yunti_list_browser_targets` again
182
185
  and use the latest returned session.
183
186
  - If a parameter error appears, call `yunti_get_tool_usage_hints` with the
@@ -2,9 +2,10 @@
2
2
 
3
3
  ## Current Phase
4
4
 
5
- Release readiness: local MVP is prepared for first public release. P4.19 formal
6
- npm publish is in progress and currently blocked only by npm two-factor
7
- authentication OTP.
5
+ Patch release `yunti-browser-runtime@0.1.2` is complete. The local loopback
6
+ bridge no longer requires a token by default, the extension popup no longer
7
+ shows token as a first-run field, and the published package is verified with
8
+ `npm run release:verify-published`.
8
9
 
9
10
  ## Current State
10
11
 
@@ -20,15 +21,18 @@ authentication OTP.
20
21
  - A staged execution plan exists at `docs/EXECUTION_PLAN.md`.
21
22
  - Tool prefix is `yunti_*`.
22
23
  - MCP local mode defaults to `userId=local`.
23
- - Bridge HTTP routes now require `x-yunti-browser-token`; unauthenticated
24
- `/health` only returns limited status.
24
+ - Local loopback bridge HTTP routes do not require a token by default; setting
25
+ `YUNTI_BROWSER_BRIDGE_TOKEN` re-enables `x-yunti-browser-token` checks.
26
+ - Binding the bridge to a non-loopback host requires
27
+ `YUNTI_BROWSER_BRIDGE_TOKEN`.
25
28
  - Bridge CORS now echoes only local debug origins and browser extension origins
26
29
  by default, with `YUNTI_BROWSER_BRIDGE_ALLOW_ORIGINS` as an override.
27
- - Extension popup can save the local bridge URL, token, and page match patterns.
30
+ - Extension popup can save the local bridge URL, optional token, and page match
31
+ patterns.
28
32
  - Browser sessions now track `lastSeenAt`, `lastActivatedAt`, `expiresAt`, and
29
33
  `staleReason`; extension polling refreshes heartbeat and expired sessions are
30
34
  cleaned up with recovery guidance.
31
- - `npm run doctor` now checks Node version, bridge reachability, token validity,
35
+ - `npm run doctor` now checks Node version, bridge reachability, auth mode,
32
36
  registered sessions, active session, extension presence, MCP server path, and
33
37
  skill path; it emits JSON plus a human-readable summary.
34
38
  - `npm run print-config` prints MCP configuration for Codex, Claude Code,
@@ -39,6 +43,9 @@ authentication OTP.
39
43
  - `npm run release:dry-run` and `npm run release:publish` pin the official npm
40
44
  registry at `https://registry.npmjs.org/`, avoiding accidental publication to
41
45
  a locally configured mirror registry.
46
+ - `yunti-browser-runtime@0.1.2` is published on the official npm registry.
47
+ - Extension popup keeps Bridge Token under optional advanced settings; default
48
+ local installs do not require saving popup settings.
42
49
  - `npm run test:e2e` provides an opt-in real-browser Playwright smoke test for
43
50
  extension loading, page registration, MCP target listing, snapshot, click/fill,
44
51
  and CDP `Runtime.evaluate`.
@@ -98,16 +105,15 @@ authentication OTP.
98
105
 
99
106
  ## Open Work
100
107
 
101
- - Follow `docs/EXECUTION_PLAN.md` for the active P4.19 npm publish step.
108
+ - Follow `docs/EXECUTION_PLAN.md` when defining the next post-0.1.1 phase.
102
109
  - P4.1 release-readiness docs and permission review is complete.
103
110
  - P4.2 Agent integration examples are complete.
104
111
  - P4.3 npm publishing URL confirmation is complete with the GitHub repository
105
112
  `https://github.com/dingguangyi0/yunti-browser-runtime`; package metadata
106
113
  points to that repository/homepage/issues URL set and all three URLs return
107
114
  HTTP 200.
108
- - `npm view yunti-browser-runtime` currently returns E404, which is acceptable
109
- only if `0.1.0` is intended to be the first npm release under this package
110
- name.
115
+ - `npm view yunti-browser-runtime@0.1.0` returns the published `0.1.0` package
116
+ metadata from `https://registry.npmjs.org/`.
111
117
  - Latest release gate checks passed after documenting P4.3: public-doc residue
112
118
  check, `npm run check`, `npm test`, and `npm pack --dry-run`.
113
119
  - P4.4 release gate scripting is complete: `npm run release:check` now runs
@@ -217,19 +223,16 @@ authentication OTP.
217
223
  - P4.18 post-publish verification command is complete:
218
224
  `release:verify-published` checks the published npm package name, version,
219
225
  repository, homepage, bugs URL, and tarball URL against local `package.json`.
220
- - Latest P4.18 validation: `release:verify-published` currently fails with a
221
- structured unpublished-package report for `yunti-browser-runtime@0.1.0`,
222
- `node --check scripts/check-published-package.js` passes, and
223
- `release:dry-run` passes with a 40-file npm tarball that includes the new
224
- verifier.
225
- - P4.19 formal npm publish execution is in progress: `npm run release:publish`
226
+ - Latest P4.18 validation: after publish, `npm run release:verify-published`
227
+ returns 0 and confirms the published name, version, repository, homepage,
228
+ bugs URL, and tarball URL.
229
+ - P4.19 formal npm publish execution is complete: `npm run release:publish`
226
230
  passed metadata checks, release gates, `npm run release:whoami`, `npm run
227
- check`, `npm test`, npm package contents validation, and extension zip
228
- validation before reaching `npm publish`.
229
- - Latest P4.19 validation: official npm publish reached
230
- `https://registry.npmjs.org/` with a 40-file tarball, then failed with npm
231
- `E403` because the account requires two-factor authentication OTP or a
232
- granular access token with bypass 2FA enabled.
231
+ check`, `npm test`, npm package contents validation, extension zip validation,
232
+ and official npm publish.
233
+ - Latest P4.19 validation: official npm publish succeeded for
234
+ `yunti-browser-runtime@0.1.0`; the published tarball URL is
235
+ `https://registry.npmjs.org/yunti-browser-runtime/-/yunti-browser-runtime-0.1.0.tgz`.
233
236
  - A temporary npm token publish retry also reached official npm publish after
234
237
  passing the full local release gate, but npm returned the same `E403`; the
235
238
  token does not satisfy npm's publish-time bypass 2FA requirement.
@@ -238,10 +241,23 @@ authentication OTP.
238
241
  `.npmrc` is ignored by git to avoid committing local npm credentials.
239
242
  - A second npm token written to the project-local `.npmrc` also authenticated
240
243
  successfully as `xuanzhu`, then failed at `npm publish` with the same `E403`.
241
- - Next required release step: rerun
242
- `npm run release:publish -- --otp=<6-digit-code>` with a current npm OTP, or
243
- configure an npm granular access token that can publish with bypass 2FA; after
244
- publish succeeds, run `npm run release:verify-published`.
244
+ - A publish-capable npm token written to the project-local `.npmrc` authenticated
245
+ successfully as `xuanzhu`; `npm run release:publish` then succeeded, followed
246
+ by successful `npm run release:verify-published`.
247
+ - P5.1 local default auth simplification is complete: default `127.0.0.1`
248
+ bridge usage no longer needs a token; explicit `YUNTI_BROWSER_BRIDGE_TOKEN`
249
+ still enforces token checks; non-loopback binding without a token fails fast.
250
+ - Latest P5.1 validation: `npm test` passed 61 tests with 60 passing and 1
251
+ real-browser smoke skipped by configuration; `npm run check` and
252
+ `npm run release:check` passed; `npm run release:publish` published
253
+ `yunti-browser-runtime@0.1.1`; `npm run release:verify-published` returned 0.
254
+ - P5.2 extension popup and onboarding simplification is complete: Bridge Token
255
+ is now optional under advanced settings, README contains a copyable Agent
256
+ installation prompt, and default extension loading no longer asks users to
257
+ save popup settings.
258
+ - Latest P5.2 validation: `npm run release:check` passed; `npm run
259
+ release:publish` published `yunti-browser-runtime@0.1.2`; `npm run
260
+ release:verify-published` returned 0.
245
261
 
246
262
  ## Known Risks
247
263
 
@@ -251,9 +267,8 @@ authentication OTP.
251
267
  long-tail tools can still receive the same treatment before a later release.
252
268
  - Extension broad host permissions are now documented, but should still be
253
269
  re-reviewed before any store-distributed release.
254
- - Package repository/homepage/bugs metadata is publicly reachable, and the
255
- final npm publish is now waiting on npm 2FA OTP or a publish-capable granular
256
- access token.
270
+ - Package repository/homepage/bugs metadata is publicly reachable and published
271
+ npm metadata now matches local `package.json`.
257
272
  - Tool names and descriptions must stay clear enough for agents to choose the
258
273
  right route without relying on hidden model knowledge.
259
274
 
@@ -3,13 +3,20 @@
3
3
  This document tracks release blockers that require external confirmation before
4
4
  publishing `yunti-browser-runtime`.
5
5
 
6
- ## Active Blocker: npm 2FA Publish OTP
6
+ ## Current Status
7
7
 
8
- Status: active as of 2026-07-03.
8
+ No active npm publish blockers as of 2026-07-03.
9
+
10
+ `yunti-browser-runtime@0.1.2` is published on the official npm registry and
11
+ verified with `npm run release:verify-published`.
12
+
13
+ ## Resolved Blocker: npm 2FA Publish Token
14
+
15
+ Status: resolved on 2026-07-03.
9
16
 
10
17
  `npm run release:publish` now passes local release gates and npm login preflight,
11
18
  then reaches `npm publish --registry=https://registry.npmjs.org/`. The registry
12
- rejects the publish with `E403` because the logged-in account requires
19
+ initially rejected the publish with `E403` because the logged-in account requires
13
20
  two-factor authentication for package publishing.
14
21
 
15
22
  A temporary npm token retry also passed the local release gate and reached
@@ -23,22 +30,18 @@ The local `.npmrc` is ignored by git and must not be committed.
23
30
  A second token written to the project-root `.npmrc` also authenticated
24
31
  successfully as `xuanzhu`, then failed at `npm publish` with the same `E403`.
25
32
 
26
- Resolution:
27
-
28
- ```bash
29
- npm run release:publish -- --otp=<6-digit-code>
30
- ```
33
+ A publish-capable npm token written to the project-root `.npmrc` authenticated
34
+ successfully as `xuanzhu`, and `npm run release:publish` then succeeded for
35
+ `yunti-browser-runtime@0.1.0`.
31
36
 
32
- Use a fresh npm one-time password from the account authenticator. As an
33
- alternative, configure a granular npm access token with package publish
34
- permission and bypass 2FA enabled, then rerun `npm run release:publish`.
35
-
36
- After publish succeeds, run:
37
+ Verification:
37
38
 
38
39
  ```bash
39
40
  npm run release:verify-published
40
41
  ```
41
42
 
43
+ The verification command returns 0 and confirms the published package metadata.
44
+
42
45
  ## Resolved Blocker: P4.3 Package URLs
43
46
 
44
47
  Status: resolved on 2026-07-02 with
package/docs/SECURITY.md CHANGED
@@ -5,7 +5,9 @@
5
5
  The first release is local single-user software:
6
6
 
7
7
  - bridge binds to `127.0.0.1` by default;
8
- - bridge routes require `x-yunti-browser-token` except limited `/health`;
8
+ - local loopback bridge routes do not require a token by default;
9
+ - setting `YUNTI_BROWSER_BRIDGE_TOKEN` enables `x-yunti-browser-token` checks
10
+ for bridge routes, and binding to a non-loopback host requires a token;
9
11
  - CORS only echoes local debug origins and browser-extension origins by default;
10
12
  - extension talks to the local bridge;
11
13
  - MCP tools default to `userId=local`;
@@ -24,7 +26,7 @@ The extension uses browser permissions required for automation:
24
26
 
25
27
  - `tabs` for tab inventory and activation;
26
28
  - `debugger` for CDP commands;
27
- - `storage` for local bridge URL, token, user, and page-match settings;
29
+ - `storage` for local bridge URL, optional token, user, and page-match settings;
28
30
  - `webRequest` for sanitized network observations;
29
31
  - `activeTab` for visible-tab capture fallback paths.
30
32
 
@@ -52,5 +54,5 @@ Users should only load the extension from a trusted local checkout.
52
54
  - Remote multi-user isolation layer.
53
55
  - Per-tool allow/deny policy.
54
56
 
55
- Remote deployment must be designed separately and should not reuse the local
56
- single-user defaults without an authentication and ownership model.
57
+ Remote deployment must be designed separately and must not use the local
58
+ no-token loopback default without an authentication and ownership model.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Yunti Browser Runtime",
4
- "version": "0.1.0",
4
+ "version": "0.1.2",
5
5
  "description": "Local browser runtime for AI agents through MCP.",
6
6
  "permissions": [
7
7
  "activeTab",
@@ -34,6 +34,22 @@ label {
34
34
  font-size: 12px;
35
35
  }
36
36
 
37
+ details {
38
+ display: grid;
39
+ gap: 6px;
40
+ color: #334155;
41
+ font-size: 12px;
42
+ }
43
+
44
+ summary {
45
+ cursor: pointer;
46
+ font-weight: 600;
47
+ }
48
+
49
+ details label {
50
+ margin-top: 6px;
51
+ }
52
+
37
53
  input,
38
54
  textarea {
39
55
  width: 100%;
@@ -14,14 +14,17 @@
14
14
  Bridge URL
15
15
  <input id="bridgeUrl" autocomplete="off" />
16
16
  </label>
17
- <label>
18
- Bridge Token
19
- <input id="bridgeToken" autocomplete="off" type="password" />
20
- </label>
21
17
  <label>
22
18
  页面匹配
23
19
  <textarea id="platformMatches" rows="3"></textarea>
24
20
  </label>
21
+ <details id="advancedSettings">
22
+ <summary>高级设置</summary>
23
+ <label>
24
+ Bridge Token(可选)
25
+ <input id="bridgeToken" autocomplete="off" type="password" />
26
+ </label>
27
+ </details>
25
28
  <button id="save">保存设置</button>
26
29
  <button id="refresh">刷新状态</button>
27
30
  </main>
@@ -2,6 +2,7 @@ const statusEl = document.querySelector("#status")
2
2
  const bridgeUrlEl = document.querySelector("#bridgeUrl")
3
3
  const bridgeTokenEl = document.querySelector("#bridgeToken")
4
4
  const platformMatchesEl = document.querySelector("#platformMatches")
5
+ const advancedSettingsEl = document.querySelector("#advancedSettings")
5
6
  document.querySelector("#save").addEventListener("click", save)
6
7
  document.querySelector("#refresh").addEventListener("click", refresh)
7
8
 
@@ -17,7 +18,7 @@ async function refresh() {
17
18
  setStatus(
18
19
  state.activeSession
19
20
  ? `页面已连接\n${state.activeSession.title || state.activeSession.url}`
20
- : "请打开任意 http/https 页面,并确认本地 bridge 已启动。"
21
+ : "默认无需设置。请打开或刷新任意 http/https 页面。"
21
22
  )
22
23
  }
23
24
 
@@ -35,12 +36,13 @@ async function save() {
35
36
  return
36
37
  }
37
38
  renderSettings(state.settings || {})
38
- setStatus("设置已保存,请刷新目标页面完成重新注册。")
39
+ setStatus("设置已保存。请刷新目标页面完成重新注册。")
39
40
  }
40
41
 
41
42
  function renderSettings(settings) {
42
43
  bridgeUrlEl.value = settings.bridgeUrl || "http://127.0.0.1:48887"
43
44
  bridgeTokenEl.value = settings.bridgeToken || ""
45
+ advancedSettingsEl.open = Boolean(settings.bridgeToken)
44
46
  platformMatchesEl.value = Array.isArray(settings.platformMatches)
45
47
  ? settings.platformMatches.join("\n")
46
48
  : "*"
@@ -1,5 +1,4 @@
1
1
  import http from "node:http"
2
- import { randomUUID } from "node:crypto"
3
2
  import {
4
3
  BridgeHub,
5
4
  DEFAULT_SESSION_TTL_MS,
@@ -88,6 +87,11 @@ function isAllowedOrigin(origin, allowOrigins) {
88
87
  return allowOrigins.some((pattern) => originMatchesPattern(origin, pattern))
89
88
  }
90
89
 
90
+ function isLoopbackHost(host) {
91
+ const value = String(host || "").trim().toLowerCase()
92
+ return value === "127.0.0.1" || value === "localhost" || value === "::1" || value === "[::1]"
93
+ }
94
+
91
95
  function bridgeCorsHeaders(req, allowOrigins) {
92
96
  const origin = String(req.headers.origin || "")
93
97
  const headers = {
@@ -117,20 +121,33 @@ function bridgeRequestToken(req) {
117
121
  return match ? normalizeBridgeToken(match[1]) : ""
118
122
  }
119
123
 
120
- function isAuthorizedBridgeRequest(req, bridgeToken) {
124
+ function isAuthorizedBridgeRequest(req, bridgeToken, authRequired = true) {
125
+ if (!authRequired) return true
121
126
  const token = normalizeBridgeToken(bridgeToken)
122
127
  return Boolean(token && bridgeRequestToken(req) === token)
123
128
  }
124
129
 
125
- function limitedHealth() {
130
+ function authInfo(authRequired) {
131
+ return {
132
+ required: Boolean(authRequired),
133
+ header: BRIDGE_TOKEN_HEADER,
134
+ }
135
+ }
136
+
137
+ function healthWithAuth(value, authRequired) {
138
+ return {
139
+ ...value,
140
+ authorized: true,
141
+ auth: authInfo(authRequired),
142
+ }
143
+ }
144
+
145
+ function limitedHealth(authRequired) {
126
146
  return {
127
147
  ok: true,
128
148
  name: "yunti-browser-runtime-bridge",
129
149
  authorized: false,
130
- auth: {
131
- required: true,
132
- header: BRIDGE_TOKEN_HEADER,
133
- },
150
+ auth: authInfo(authRequired),
134
151
  }
135
152
  }
136
153
 
@@ -173,7 +190,13 @@ export async function startBridgeServer({
173
190
  const configuredBridgeToken = normalizeBridgeToken(
174
191
  bridgeToken ?? process.env.YUNTI_BROWSER_BRIDGE_TOKEN
175
192
  )
176
- const activeBridgeToken = configuredBridgeToken || randomUUID()
193
+ const authRequired = Boolean(configuredBridgeToken)
194
+ if (!authRequired && !isLoopbackHost(host)) {
195
+ throw new Error(
196
+ "YUNTI_BROWSER_BRIDGE_TOKEN is required when binding the bridge to a non-loopback host"
197
+ )
198
+ }
199
+ const activeBridgeToken = configuredBridgeToken
177
200
  const hub = new BridgeHub({ sessionTtlMs })
178
201
  const cleanupTimer = setInterval(() => {
179
202
  hub.cleanupExpiredSessions()
@@ -182,7 +205,7 @@ export async function startBridgeServer({
182
205
  const server = http.createServer(async (req, res) => {
183
206
  try {
184
207
  const url = new URL(req.url || "/", `http://${host}:${port}`)
185
- const authorized = isAuthorizedBridgeRequest(req, activeBridgeToken)
208
+ const authorized = isAuthorizedBridgeRequest(req, activeBridgeToken, authRequired)
186
209
 
187
210
  if (req.method === "OPTIONS") {
188
211
  const origin = String(req.headers.origin || "")
@@ -195,7 +218,9 @@ export async function startBridgeServer({
195
218
  req,
196
219
  res,
197
220
  200,
198
- authorized ? hub.health({ userId: url.searchParams.get("userId") }) : limitedHealth(),
221
+ authorized
222
+ ? healthWithAuth(hub.health({ userId: url.searchParams.get("userId") }), authRequired)
223
+ : limitedHealth(authRequired),
199
224
  { allowOrigins }
200
225
  )
201
226
  return
@@ -209,7 +234,7 @@ export async function startBridgeServer({
209
234
  {
210
235
  ok: false,
211
236
  error: `missing or invalid ${BRIDGE_TOKEN_HEADER}`,
212
- auth: { required: true, header: BRIDGE_TOKEN_HEADER },
237
+ auth: authInfo(authRequired),
213
238
  },
214
239
  { allowOrigins }
215
240
  )
@@ -320,7 +345,7 @@ export async function startBridgeServer({
320
345
  if (mode === "proxy") {
321
346
  clearInterval(cleanupTimer)
322
347
  server.close()
323
- return { mode: "proxy", host, port, bridgeToken: configuredBridgeToken, allowOrigins, hub: null, server: null }
348
+ return { mode: "proxy", host, port, bridgeToken: configuredBridgeToken, authRequired, allowOrigins, hub: null, server: null }
324
349
  }
325
- return { mode: "owner", host, port, bridgeToken: activeBridgeToken, allowOrigins, hub, server }
350
+ return { mode: "owner", host, port, bridgeToken: activeBridgeToken, authRequired, allowOrigins, hub, server }
326
351
  }
package/mcp/server.js CHANGED
@@ -215,12 +215,17 @@ export async function runStdio() {
215
215
  `[yunti-browser-runtime] bridge ${bridge.mode} on http://${bridge.host}:${bridge.port}`
216
216
  )
217
217
  if (bridge.mode === "owner") {
218
- console.error(
219
- `[yunti-browser-runtime] bridge token header ${BRIDGE_TOKEN_HEADER}: ${bridge.bridgeToken}`
220
- )
218
+ if (bridge.authRequired) {
219
+ console.error(
220
+ `[yunti-browser-runtime] bridge token header ${BRIDGE_TOKEN_HEADER}: ${bridge.bridgeToken}`
221
+ )
222
+ } else {
223
+ console.error("[yunti-browser-runtime] bridge auth disabled for local loopback")
224
+ console.error("[yunti-browser-runtime] set YUNTI_BROWSER_BRIDGE_TOKEN to require a local token")
225
+ }
221
226
  } else if (!bridge.bridgeToken) {
222
227
  console.error(
223
- `[yunti-browser-runtime] proxy mode requires YUNTI_BROWSER_BRIDGE_TOKEN to match the running bridge`
228
+ `[yunti-browser-runtime] proxy mode will connect without a bridge token; set YUNTI_BROWSER_BRIDGE_TOKEN if the running bridge requires one`
224
229
  )
225
230
  }
226
231
  }
@@ -249,12 +254,17 @@ export async function runBridgeOnly() {
249
254
  `[yunti-browser-runtime] bridge ${bridge.mode} on http://${bridge.host}:${bridge.port}`
250
255
  )
251
256
  if (bridge.mode === "owner") {
252
- console.error(
253
- `[yunti-browser-runtime] bridge token header ${BRIDGE_TOKEN_HEADER}: ${bridge.bridgeToken}`
254
- )
257
+ if (bridge.authRequired) {
258
+ console.error(
259
+ `[yunti-browser-runtime] bridge token header ${BRIDGE_TOKEN_HEADER}: ${bridge.bridgeToken}`
260
+ )
261
+ } else {
262
+ console.error("[yunti-browser-runtime] bridge auth disabled for local loopback")
263
+ console.error("[yunti-browser-runtime] set YUNTI_BROWSER_BRIDGE_TOKEN to require a local token")
264
+ }
255
265
  } else if (!bridge.bridgeToken) {
256
266
  console.error(
257
- `[yunti-browser-runtime] proxy mode requires YUNTI_BROWSER_BRIDGE_TOKEN to match the running bridge`
267
+ `[yunti-browser-runtime] proxy mode will connect without a bridge token; set YUNTI_BROWSER_BRIDGE_TOKEN if the running bridge requires one`
258
268
  )
259
269
  }
260
270
  await new Promise(() => {})
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yunti-browser-runtime",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Local browser runtime and MCP tools for AI agents.",
6
6
  "license": "MIT",
package/scripts/doctor.js CHANGED
@@ -58,12 +58,14 @@ async function checkBridge() {
58
58
  `${bridgeUrl}/health?userId=${encodeURIComponent(routeUserId)}`
59
59
  )
60
60
  const authorized = health.json?.authorized !== false
61
+ const authRequired = health.json?.auth?.required !== false && Boolean(health.json?.auth?.required)
61
62
  const sessions = Array.isArray(health.json?.sessions) ? health.json.sessions : []
62
63
  const activeSessionId = health.json?.activeSessionId || null
63
64
  return {
64
65
  ok: health.status === 200 && authorized,
65
66
  reachable: health.status === 200,
66
67
  authorized,
68
+ authRequired,
67
69
  status: health.status,
68
70
  url: bridgeUrl,
69
71
  tokenConfigured: Boolean(bridgeToken),
@@ -81,6 +83,7 @@ async function checkBridge() {
81
83
  ok: false,
82
84
  reachable: false,
83
85
  authorized: false,
86
+ authRequired: Boolean(bridgeToken),
84
87
  url: bridgeUrl,
85
88
  tokenConfigured: Boolean(bridgeToken),
86
89
  tokenHeader: bridgeTokenHeader,
@@ -110,8 +113,8 @@ function buildNextSteps(checks) {
110
113
  if (!checks.bridge.reachable) {
111
114
  steps.push("Start the local bridge with npm run bridge.")
112
115
  }
113
- if (checks.bridge.reachable && !checks.bridge.authorized) {
114
- steps.push("Set YUNTI_BROWSER_BRIDGE_TOKEN to the bridge token printed by npm run bridge.")
116
+ if (checks.bridge.reachable && checks.bridge.authRequired && !checks.bridge.authorized) {
117
+ steps.push("Set YUNTI_BROWSER_BRIDGE_TOKEN to the token used by the running bridge.")
115
118
  steps.push("Save the same token in the extension popup.")
116
119
  }
117
120
  if (checks.bridge.ok && !checks.bridge.extensionConnected) {
@@ -131,7 +134,7 @@ function humanSummary(report) {
131
134
  `Yunti Browser Runtime doctor: ${report.ok ? "OK" : "needs attention"}`,
132
135
  `- Node: ${report.checks.node.version} (${report.checks.node.ok ? "ok" : "requires >=22"})`,
133
136
  `- Bridge: ${report.checks.bridge.reachable ? report.checks.bridge.url : "not reachable"}`,
134
- `- Token: ${report.checks.bridge.authorized ? "valid" : "missing or invalid"}`,
137
+ `- Token: ${report.checks.bridge.authRequired ? report.checks.bridge.authorized ? "valid" : "missing or invalid" : "not required for local loopback"}`,
135
138
  `- Sessions: ${report.checks.bridge.visibleSessionCount} visible, active ${report.checks.bridge.activeSessionId || "none"}`,
136
139
  `- Extension: ${report.checks.bridge.extensionConnected ? "connected" : "not detected"}`,
137
140
  `- MCP server: ${report.checks.mcpServer.ok ? "found" : "missing"}`,
@@ -77,7 +77,7 @@ function buildConfig(agent) {
77
77
  tokenConfigured: bridgeTokenConfigured,
78
78
  tokenInstruction: bridgeTokenConfigured
79
79
  ? "Token is configured in the current environment; copy the same secret into your agent and extension settings without committing it."
80
- : "Set YUNTI_BROWSER_BRIDGE_TOKEN in your agent and save the same token in the extension popup when bridge auth is enabled.",
80
+ : "Local loopback bridge auth is disabled by default; set YUNTI_BROWSER_BRIDGE_TOKEN only when you want to require a local token.",
81
81
  },
82
82
  skill: {
83
83
  sourcePath: skillPath,