yunti-browser-runtime 0.1.0 → 0.1.1

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
@@ -87,7 +88,7 @@ npm run doctor:json
87
88
  3. 点击“加载已解压的扩展程序”。
88
89
  4. 选择本项目的 `extension/` 目录。
89
90
  5. 打开任意 `http` 或 `https` 页面。
90
- 6. 点击扩展图标,填写 bridge token,保存设置。
91
+ 6. 点击扩展图标,确认 bridge 地址为 `http://127.0.0.1:48887`,保存设置。
91
92
  7. 刷新目标页面,确认页面已连接。
92
93
 
93
94
  扩展只负责把页面注册到本地 bridge,并执行 Agent 发来的浏览器动作。
@@ -239,7 +240,10 @@ YUNTI_E2E=1 npm run test:e2e
239
240
 
240
241
  ### bridge token 是什么?
241
242
 
242
- bridge token 是本地 HTTP bridge 的访问凭证。MCP server、extension 和 doctor 使用同一个 token,避免普通网页误调用本地接口。
243
+ 默认本地安装不需要 bridge token。bridge 只监听 `127.0.0.1`,扩展和 MCP server
244
+ 可以直接连接,减少首次使用心智。需要加固共享机器或自定义部署时,可设置
245
+ `YUNTI_BROWSER_BRIDGE_TOKEN`,这时 MCP server、extension 和 doctor 必须使用同一个
246
+ token。
243
247
 
244
248
  ### 会读取 cookie 或密码吗?
245
249
 
@@ -249,8 +253,9 @@ bridge token 是本地 HTTP bridge 的访问凭证。MCP server、extension 和
249
253
 
250
254
  调用 `yunti_list_browser_targets` 获取最新浏览器路由,再用返回的 `browserSessionId` 继续操作。
251
255
 
252
- ## 当前未完成项
256
+ ## 发布状态与后续事项
253
257
 
254
- - 如确认要发布 `0.1.0`,运行 `npm run release:publish`。
255
- - 当前 `npm view yunti-browser-runtime` 返回 E404;如 `0.1.0` 是首次发布,这是预期状态。
256
- - 发布前如要上架浏览器扩展商店,需要再次审查 broad host permissions。
258
+ - `yunti-browser-runtime@0.1.1` 已发布到官方 npm registry。
259
+ - `0.1.1` 将本地默认使用路径改为免 bridge token。
260
+ - 发布后验证命令:`npm run release:verify-published`。
261
+ - 如要上架浏览器扩展商店,需要再次审查 broad host permissions。
@@ -54,7 +54,8 @@
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 |
58
59
 
59
60
  ## P0.1 bridge token + CORS 收紧
60
61
 
@@ -1151,8 +1152,9 @@ release gate、npm package 内容检查和 extension zip 内容检查。
1151
1152
 
1152
1153
  ### 验收记录
1153
1154
 
1154
- - `npm run release:whoami` 当前返回 `ENEEDAUTH`,说明本机尚未登录
1155
- `https://registry.npmjs.org/`。
1155
+ - 发布前,`npm run release:whoami` 曾在未登录状态返回 `ENEEDAUTH`,验证缺少 npm
1156
+ 登录时会提前失败。
1157
+ - 登录后,`npm run release:whoami` 返回 `xuanzhu`,并在正式发布前通过预检。
1156
1158
  - `release:publish` 已内置登录预检;登录前不会进入正式 npm publish。
1157
1159
 
1158
1160
  ## P4.18 npm 发布后验证命令
@@ -1186,9 +1188,10 @@ metadata 与当前 `package.json` 一致。
1186
1188
 
1187
1189
  ### 验收记录
1188
1190
 
1189
- - `npm run release:verify-published` 当前返回非零退出码,报告
1190
- `yunti-browser-runtime@0.1.0` 尚未发布到 `https://registry.npmjs.org/`;
1191
- 失败路径输出结构化 JSON,且不包含 npm 本机 debug log 路径。
1191
+ - 发布前,`npm run release:verify-published` 曾返回非零退出码并输出结构化未发布报告,
1192
+ 且失败路径不包含 npm 本机 debug log 路径。
1193
+ - 发布后,`npm run release:verify-published` 返回 0,确认
1194
+ `yunti-browser-runtime@0.1.0` 已发布到 `https://registry.npmjs.org/`。
1192
1195
  - `node --check scripts/check-published-package.js` 通过。
1193
1196
  - `npm run release:check` 通过:`npm run check` 已覆盖
1194
1197
  `scripts/check-published-package.js`,npm package contents check 确认 tarball
@@ -1198,7 +1201,7 @@ metadata 与当前 `package.json` 一致。
1198
1201
 
1199
1202
  ## P4.19 npm 正式发布执行
1200
1203
 
1201
- 状态:进行中(2026-07-03,阻塞于 npm 2FA OTP 或具备 bypass 2FA 的发布 token
1204
+ 状态:已完成(2026-07-03)
1202
1205
 
1203
1206
  执行摘要:
1204
1207
 
@@ -1206,8 +1209,8 @@ metadata 与当前 `package.json` 一致。
1206
1209
  - 已按用户确认执行 `npm run release:publish`。
1207
1210
  - `release:publish` 已通过内置 `release:prepublish`、`release:whoami` 和全部本地
1208
1211
  发布门禁;本次测试覆盖 59 项,58 项通过,1 项真实浏览器 smoke 按配置跳过。
1209
- - 本次正式发布已进入 `npm publish --registry=https://registry.npmjs.org/`,tarball
1210
- 包含 40 个文件。
1212
+ - 正式发布进入 `npm publish --registry=https://registry.npmjs.org/`,tarball 包含
1213
+ 40 个文件。
1211
1214
  - npm registry 返回 `E403`:当前账号发布包需要双因素认证 OTP,或使用开启
1212
1215
  bypass 2FA 的 granular access token。
1213
1216
  - 已使用临时 npm token 重试 `npm run release:publish`;本地发布门禁再次通过,但
@@ -1217,7 +1220,11 @@ metadata 与当前 `package.json` 一致。
1217
1220
  但重新执行 `npm run release:publish` 仍在 `npm publish` 阶段返回相同 `E403`。
1218
1221
  - 已用新的 npm token 覆盖项目级 `.npmrc` 后再次确认 `npm whoami` 返回 `xuanzhu`;
1219
1222
  `npm run release:publish` 仍通过本地门禁并在 `npm publish` 阶段返回相同 `E403`。
1220
- - npm 2FA 阻塞,`yunti-browser-runtime@0.1.0` 尚未发布成功。
1223
+ - 已用具备发布权限的 npm token 覆盖项目级 `.npmrc` 后再次执行
1224
+ `npm run release:publish`,正式发布成功:`yunti-browser-runtime@0.1.0` 已发布到
1225
+ `https://registry.npmjs.org/`。
1226
+ - `npm run release:verify-published` 通过,确认 registry 上的 name、version、
1227
+ repository、homepage、bugs 和 tarball URL 与本地 `package.json` 匹配。
1221
1228
 
1222
1229
  ### 目标
1223
1230
 
@@ -1234,11 +1241,61 @@ metadata 与当前 `package.json` 一致。
1234
1241
  repository、homepage、bugs 和 tarball URL 与本地 `package.json` 匹配。
1235
1242
  - 发布结果同步到 `docs/PROJECT_STATUS.md`。
1236
1243
 
1237
- ### 当前阻塞
1244
+ ### 验收记录
1245
+
1246
+ - `npm run release:publish` 成功发布 `yunti-browser-runtime@0.1.0`。
1247
+ - 发布前门禁通过:metadata URL 检查、公开文档残留检查、Markdown 链接检查、版本一致性、
1248
+ CLI smoke、print-config smoke、doctor smoke、`npm run check`、`npm test`、npm
1249
+ package contents check 和 extension zip contents check。
1250
+ - `npm test` 覆盖 59 项测试:58 项通过,1 项真实浏览器 smoke 按配置跳过。
1251
+ - `npm run release:verify-published` 返回 0。
1252
+ - `npm view yunti-browser-runtime@0.1.0 ... --registry=https://registry.npmjs.org/`
1253
+ 返回已发布版本和正确 metadata。
1238
1254
 
1239
- 等待 npm 2FA OTP 或可绕过 2FA 的发布 token。已确认普通 token 或未开启 bypass
1240
- 2FA 的 token 无法完成本次发布;项目级 `.npmrc` 方式和两次 token 认证都已复验。
1241
- 拿到 OTP 后只需要重跑正式发布命令,不需要继续增加新的发布门禁。
1255
+ ## P5.1 本地默认免 bridge token
1256
+
1257
+ 状态:已完成(2026-07-03)
1258
+
1259
+ 实现摘要:
1260
+
1261
+ - 默认 `127.0.0.1` 本地 bridge 不再自动生成随机 token,也不要求 extension、doctor
1262
+ 或 MCP proxy 携带 `x-yunti-browser-token`。
1263
+ - 用户显式设置 `YUNTI_BROWSER_BRIDGE_TOKEN` 时,bridge 继续强制校验
1264
+ `x-yunti-browser-token` 或 `Authorization: Bearer <token>`。
1265
+ - bridge 绑定到非 loopback host 时必须设置 `YUNTI_BROWSER_BRIDGE_TOKEN`,避免把
1266
+ 本地免 token 默认值误用于远程或局域网部署。
1267
+ - `npm run doctor` 默认显示 token not required;只有 bridge 明确要求 token 且请求
1268
+ 未授权时才提示设置 token。
1269
+ - `npm run print-config` 默认不再要求用户配置 token,仅保留需要加固时的可选说明。
1270
+ - 真实浏览器 smoke test 改为覆盖默认免 token 路径。
1271
+ - package 和 extension 版本同步提升到 `0.1.1`。
1272
+
1273
+ ### 目标
1274
+
1275
+ 降低本地安装和首次使用心智:用户只需要启动 bridge、加载扩展、确认 bridge URL,
1276
+ 不需要复制或理解 bridge token。安全边界仍保持本地 loopback 默认,显式 token 用于
1277
+ 共享机器、非本地绑定或额外加固场景。
1278
+
1279
+ ### 验收标准
1280
+
1281
+ - 默认 `npm run bridge` 启动的 `127.0.0.1` bridge 不要求 token。
1282
+ - 未配置 token 时,extension 可以注册页面,doctor 可以读取完整 health。
1283
+ - 配置 `YUNTI_BROWSER_BRIDGE_TOKEN` 后,未携带 token 的受保护 HTTP route 仍返回 401。
1284
+ - `YUNTI_BROWSER_BRIDGE_HOST` 绑定非 loopback 地址且未配置 token 时启动失败。
1285
+ - README、INSTALL、SECURITY、PROJECT_STATUS 同步新的默认使用路径。
1286
+ - `npm run release:check` 通过。
1287
+ - `npm run release:publish` 发布 `yunti-browser-runtime@0.1.1`,并通过
1288
+ `npm run release:verify-published` 验证。
1289
+
1290
+ ### 验收记录
1291
+
1292
+ - `npm test` 通过:61 项测试,60 项通过,1 项真实浏览器 smoke 按配置跳过。
1293
+ - 新增测试覆盖本地默认免 token、显式 token 仍强制校验、非 loopback 无 token
1294
+ 启动失败。
1295
+ - `npm run check` 通过。
1296
+ - `npm run release:check` 通过。
1297
+ - `npm run release:publish` 成功发布 `yunti-browser-runtime@0.1.1`。
1298
+ - `npm run release:verify-published` 返回 0。
1242
1299
 
1243
1300
  ## 每阶段完成后的固定检查
1244
1301
 
@@ -1261,18 +1318,10 @@ rg "/U[s]ers|C[o]deg|x[y]y|y[b]m100" README.md docs skills package.json
1261
1318
 
1262
1319
  ## 当前下一步
1263
1320
 
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。
1321
+ P0.1-P5.1 已完成,`yunti-browser-runtime@0.1.1` 已发布到官方 npm registry。
1322
+
1323
+ - 发布后验证已通过:`npm run release:verify-published`。
1324
+ - 本地默认使用不再需要 bridge token;需要加固时可显式设置
1325
+ `YUNTI_BROWSER_BRIDGE_TOKEN`。
1326
+ - 后续如要上架浏览器扩展商店,发布前还需重新审查 broad host permissions。
1327
+ - 后续版本开发前,先在本文档新增下一阶段目标、范围和验收标准。
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,7 +54,7 @@ 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.
57
+ 5. Click the extension icon and confirm the bridge URL is `http://127.0.0.1:48887`.
58
58
  6. Open any `http` or `https` page and click the extension icon to confirm the
59
59
  page is connected.
60
60
 
@@ -117,9 +117,10 @@ npm run print-config -- --agent claude-code --human
117
117
  ```
118
118
 
119
119
  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.
120
+ configuration. The default local loopback setup does not require a bridge token.
121
+ If using a bridge token, set `YUNTI_BROWSER_BRIDGE_TOKEN` in the same environment
122
+ Claude Code uses to launch the MCP server, and save the same token in the
123
+ extension popup.
123
124
 
124
125
  #### Cursor
125
126
 
@@ -176,8 +177,8 @@ runs the unit test suite, and verifies the npm package contents with
176
177
  ## Common Recovery
177
178
 
178
179
  - 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.
180
+ - If doctor reports `authorized: false` with `authRequired: true`, set
181
+ `YUNTI_BROWSER_BRIDGE_TOKEN` and save the same token in the extension popup.
181
182
  - If an old `browserSessionId` fails, call `yunti_list_browser_targets` again
182
183
  and use the latest returned session.
183
184
  - If a parameter error appears, call `yunti_get_tool_usage_hints` with the
@@ -2,9 +2,9 @@
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.1` is complete. The local loopback
6
+ bridge no longer requires a token by default, and the published package is
7
+ verified with `npm run release:verify-published`.
8
8
 
9
9
  ## Current State
10
10
 
@@ -20,15 +20,18 @@ authentication OTP.
20
20
  - A staged execution plan exists at `docs/EXECUTION_PLAN.md`.
21
21
  - Tool prefix is `yunti_*`.
22
22
  - MCP local mode defaults to `userId=local`.
23
- - Bridge HTTP routes now require `x-yunti-browser-token`; unauthenticated
24
- `/health` only returns limited status.
23
+ - Local loopback bridge HTTP routes do not require a token by default; setting
24
+ `YUNTI_BROWSER_BRIDGE_TOKEN` re-enables `x-yunti-browser-token` checks.
25
+ - Binding the bridge to a non-loopback host requires
26
+ `YUNTI_BROWSER_BRIDGE_TOKEN`.
25
27
  - Bridge CORS now echoes only local debug origins and browser extension origins
26
28
  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.
29
+ - Extension popup can save the local bridge URL, optional token, and page match
30
+ patterns.
28
31
  - Browser sessions now track `lastSeenAt`, `lastActivatedAt`, `expiresAt`, and
29
32
  `staleReason`; extension polling refreshes heartbeat and expired sessions are
30
33
  cleaned up with recovery guidance.
31
- - `npm run doctor` now checks Node version, bridge reachability, token validity,
34
+ - `npm run doctor` now checks Node version, bridge reachability, auth mode,
32
35
  registered sessions, active session, extension presence, MCP server path, and
33
36
  skill path; it emits JSON plus a human-readable summary.
34
37
  - `npm run print-config` prints MCP configuration for Codex, Claude Code,
@@ -39,6 +42,7 @@ authentication OTP.
39
42
  - `npm run release:dry-run` and `npm run release:publish` pin the official npm
40
43
  registry at `https://registry.npmjs.org/`, avoiding accidental publication to
41
44
  a locally configured mirror registry.
45
+ - `yunti-browser-runtime@0.1.1` is published on the official npm registry.
42
46
  - `npm run test:e2e` provides an opt-in real-browser Playwright smoke test for
43
47
  extension loading, page registration, MCP target listing, snapshot, click/fill,
44
48
  and CDP `Runtime.evaluate`.
@@ -98,16 +102,15 @@ authentication OTP.
98
102
 
99
103
  ## Open Work
100
104
 
101
- - Follow `docs/EXECUTION_PLAN.md` for the active P4.19 npm publish step.
105
+ - Follow `docs/EXECUTION_PLAN.md` when defining the next post-0.1.1 phase.
102
106
  - P4.1 release-readiness docs and permission review is complete.
103
107
  - P4.2 Agent integration examples are complete.
104
108
  - P4.3 npm publishing URL confirmation is complete with the GitHub repository
105
109
  `https://github.com/dingguangyi0/yunti-browser-runtime`; package metadata
106
110
  points to that repository/homepage/issues URL set and all three URLs return
107
111
  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.
112
+ - `npm view yunti-browser-runtime@0.1.0` returns the published `0.1.0` package
113
+ metadata from `https://registry.npmjs.org/`.
111
114
  - Latest release gate checks passed after documenting P4.3: public-doc residue
112
115
  check, `npm run check`, `npm test`, and `npm pack --dry-run`.
113
116
  - P4.4 release gate scripting is complete: `npm run release:check` now runs
@@ -217,19 +220,16 @@ authentication OTP.
217
220
  - P4.18 post-publish verification command is complete:
218
221
  `release:verify-published` checks the published npm package name, version,
219
222
  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`
223
+ - Latest P4.18 validation: after publish, `npm run release:verify-published`
224
+ returns 0 and confirms the published name, version, repository, homepage,
225
+ bugs URL, and tarball URL.
226
+ - P4.19 formal npm publish execution is complete: `npm run release:publish`
226
227
  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.
228
+ check`, `npm test`, npm package contents validation, extension zip validation,
229
+ and official npm publish.
230
+ - Latest P4.19 validation: official npm publish succeeded for
231
+ `yunti-browser-runtime@0.1.0`; the published tarball URL is
232
+ `https://registry.npmjs.org/yunti-browser-runtime/-/yunti-browser-runtime-0.1.0.tgz`.
233
233
  - A temporary npm token publish retry also reached official npm publish after
234
234
  passing the full local release gate, but npm returned the same `E403`; the
235
235
  token does not satisfy npm's publish-time bypass 2FA requirement.
@@ -238,10 +238,16 @@ authentication OTP.
238
238
  `.npmrc` is ignored by git to avoid committing local npm credentials.
239
239
  - A second npm token written to the project-local `.npmrc` also authenticated
240
240
  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`.
241
+ - A publish-capable npm token written to the project-local `.npmrc` authenticated
242
+ successfully as `xuanzhu`; `npm run release:publish` then succeeded, followed
243
+ by successful `npm run release:verify-published`.
244
+ - P5.1 local default auth simplification is complete: default `127.0.0.1`
245
+ bridge usage no longer needs a token; explicit `YUNTI_BROWSER_BRIDGE_TOKEN`
246
+ still enforces token checks; non-loopback binding without a token fails fast.
247
+ - Latest P5.1 validation: `npm test` passed 61 tests with 60 passing and 1
248
+ real-browser smoke skipped by configuration; `npm run check` and
249
+ `npm run release:check` passed; `npm run release:publish` published
250
+ `yunti-browser-runtime@0.1.1`; `npm run release:verify-published` returned 0.
245
251
 
246
252
  ## Known Risks
247
253
 
@@ -251,9 +257,8 @@ authentication OTP.
251
257
  long-tail tools can still receive the same treatment before a later release.
252
258
  - Extension broad host permissions are now documented, but should still be
253
259
  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.
260
+ - Package repository/homepage/bugs metadata is publicly reachable and published
261
+ npm metadata now matches local `package.json`.
257
262
  - Tool names and descriptions must stay clear enough for agents to choose the
258
263
  right route without relying on hidden model knowledge.
259
264
 
@@ -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.0` 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.1",
5
5
  "description": "Local browser runtime for AI agents through MCP.",
6
6
  "permissions": [
7
7
  "activeTab",
@@ -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.1",
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,