{"_id":"@app-public/weaverbird","_rev":"2-4d7af97fa9f6e440d11fbe55d1732783","name":"@app-public/weaverbird","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@app-public/weaverbird","version":"1.0.0","keywords":["weaverbird","ci","vesync"],"license":"UNLICENSED","_id":"@app-public/weaverbird@1.0.0","maintainers":[{"name":"app-public","email":"app-public@vesync.com"}],"bin":{"weaverbird":"index.js"},"dist":{"shasum":"468fa55bbecfde53a0ae5e3a36490d0006fd4f2d","tarball":"https://registry.npmjs.org/@app-public/weaverbird/-/weaverbird-1.0.0.tgz","fileCount":16,"integrity":"sha512-+hWA4bV+Rg+iz92gQExbp99Ymj/+V98uGC1yKlLVlcSveOZC1W4FdLvSBBJc7OQcl7vIZ98jdIjyFdP5DBwh/A==","signatures":[{"sig":"MEUCIQDZ4U8Kgg6h6nmHD40KK1QWn30IxfngJ84mrfkEtH1kTwIgXXXEHNDOuPCPcbJi0oRvgePrOG2n6NPYGKiomSyrN8c=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":40403},"main":"index.js","engines":{"node":">=16"},"gitHead":"e3d5bbe21e2419294bce9e06b9f724377dc7495e","scripts":{"test":"node --test test/*.test.js","build":"node scripts/run-build.js","config":"node scripts/build-config.js","config:test":"node scripts/build-config.js --env=test","publish:all":"npm run publish:release && npm run publish:debug && npm run publish:test","config:debug":"node scripts/build-config.js --env=debug","publish:test":"node scripts/run-publish.js --env=test","publish:debug":"node scripts/run-publish.js --env=debug","config:release":"node scripts/build-config.js --env=release","prepublishOnly":"node scripts/run-build.js","publish:release":"node scripts/run-publish.js --env=release"},"_npmUser":{"name":"app-public","email":"app-public@vesync.com"},"_npmVersion":"11.12.1","description":"WeaverBird CI 开放 CLI — 线上环境","directories":{},"_nodeVersion":"25.9.0","dependencies":{"open":"^8.4.2","yargs":"^17.7.2","express":"^4.18.2","cross-fetch":"^4.0.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/weaverbird_1.0.0_1782888034940_0.7684739631471778","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@app-public/weaverbird","description":"WeaverBird CI 开放 CLI — 线上环境","bin":{"weaverbird":"index.js"},"main":"index.js","scripts":{"config":"node scripts/build-config.js","config:debug":"node scripts/build-config.js --env=debug","config:test":"node scripts/build-config.js --env=test","config:release":"node scripts/build-config.js --env=release","build":"node scripts/run-build.js","test":"node --test test/*.test.js","prepublishOnly":"node scripts/run-build.js","publish:release":"node scripts/run-publish.js --env=release","publish:debug":"node scripts/run-publish.js --env=debug","publish:test":"node scripts/run-publish.js --env=test","publish:all":"npm run publish:release && npm run publish:debug && npm run publish:test"},"engines":{"node":">=16"},"dependencies":{"cross-fetch":"^4.0.0","express":"^4.18.2","open":"^8.4.2","yargs":"^17.7.2"},"keywords":["weaverbird","ci","vesync"],"license":"UNLICENSED","version":"1.0.1","gitHead":"1ad32214fea7b425e6686fe3096387f6f3e29887","_id":"@app-public/weaverbird@1.0.1","_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-pZFTcIwsXt2eH89QQqKXE1ENTurPYzB3BbodzDHvjNuxTdymkzB3XbNMndLFC2owGn6ClKwlF6LS3IfiKT2s7g==","shasum":"ec10405bf061c9a7e4de084aef7b8b815620de15","tarball":"https://registry.npmjs.org/@app-public/weaverbird/-/weaverbird-1.0.1.tgz","fileCount":16,"unpackedSize":40407,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCbUqxH3l/H5hWC7AGRsXn2xxCY5e6Z3HZvc3dIwouUZAIhAJ6y3Ve5DfbI1RfA3o5VHZv0DGqiNZyXGKrujz8xKB0X"}]},"_npmUser":{"name":"app-public","email":"app-public@vesync.com"},"directories":{},"maintainers":[{"name":"app-public","email":"app-public@vesync.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/weaverbird_1.0.1_1783565258788_0.5221937065111018"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-01T06:40:34.792Z","modified":"2026-07-09T02:47:39.050Z","1.0.0":"2026-07-01T06:40:35.088Z","1.0.1":"2026-07-09T02:47:38.923Z"},"license":"UNLICENSED","keywords":["weaverbird","ci","vesync"],"description":"WeaverBird CI 开放 CLI — 线上环境","maintainers":[{"name":"app-public","email":"app-public@vesync.com"}],"readme":"# WeaverBird CLI\r\n\r\nWeaverBird CI 开放 CLI，通过 OAuth 2.0 登录后访问 CI 平台 API。与 CI 流水线共用同一 OAuth 应用，凭证按环境配置在 `config/profiles/` 中。\r\n\r\n设计说明见飞书文档：[CI 系统 2.5 版本方案设计 — CI 开放 Cli 认证](https://vesync.feishu.cn/wiki/EJRjwbQJki7ozXkJOIZcvmppngd)\r\n\r\n## 环境要求\r\n\r\n- Node.js >= 16\r\n\r\n## 安装\r\n\r\n```bash\r\ncd cli\r\nnpm install\r\n```\r\n\r\n全局联调（可选）：\r\n\r\n```bash\r\nnpm link\r\n```\r\n\r\n或从 npm 安装（发布后，按环境选择包，scope 为 `@app-public`）：\r\n\r\n```bash\r\nnpm install -g @app-public/weaverbird          # 线上（命令仍为 weaverbird）\r\nnpm install -g @app-public/weaverbird-debug    # 本地调试\r\nnpm install -g @app-public/weaverbird-test     # 测试环境\r\n```\r\n\r\n> scoped 包 `@app-public/weaverbird` 与公共 npm 上无 scope 的 `weaverbird`（Vue 查询构建器）是**不同包**，不会冲突。\r\n\r\n## CLI 命令\r\n\r\n| 操作 | 命令 |\r\n|------|------|\r\n| 顶层帮助 | `weaverbird --help` |\r\n| cli 子命令帮助 | `weaverbird cli --help` |\r\n| token 帮助 | `weaverbird cli token --help` |\r\n| token set 参数 | `weaverbird cli token set --help` |\r\n| 版本 | `weaverbird --version` |\r\n| OAuth 登录 | `weaverbird cli login` |\r\n| 退出登录 | `weaverbird cli logout` |\r\n| 登录状态 | `weaverbird cli status` |\r\n| 自更新 | `weaverbird update` |\r\n| 设置 Figma token | `weaverbird cli token set --type figma <token>` |\r\n\r\n`update` 可在顶层或 `cli` 下执行，二者等价。`token set` 的 `<token>` 是**位置参数**，放在命令最后；`--type figma` 必填。\r\n\r\n### token set 示例\r\n\r\n```bash\r\nweaverbird cli token set --type figma figd_xxxxxxxxxxxxxxxx\r\nweaverbird cli token set -t figma figd_xxxxxxxxxxxxxxxx\r\n```\r\n\r\n需先 `weaverbird cli login`。Figma token：Settings → Security → Personal access tokens。\r\n\r\n### login 流程\r\n\r\n1. 本地启动 `http://localhost:3333/callback` 回调服务\r\n2. CLI 调用 `POST /v1/api/ci/oauth/createAuthorizeSession` 创建**一次性、5 分钟有效**的授权会话\r\n3. 自动打开浏览器访问 `{WEAVERBIRD_URL}/weaverbird/oauth/authorize?session=...`（链接不可复制复用）\r\n4. 用户授权后回调带 `code`，CLI 调用 `POST /v1/api/ci/oauth/token` 换取 token\r\n5. token 写入 `~/.weaverbird/token.{env}.json`（如 `token.release.json`）\r\n\r\n### status 输出示例\r\n\r\n已登录：\r\n\r\n```json\r\n{\r\n  \"logged_in\": true,\r\n  \"env\": \"debug\",\r\n  \"ci_base_url\": \"http://10.25.72.141:16644\",\r\n  \"token_path\": \"C:\\\\Users\\\\xxx\\\\.weaverbird\\\\token.debug.json\",\r\n  \"user_id\": \"abc123\",\r\n  \"user_name\": \"张三\",\r\n  \"token\": {\r\n    \"access_token\": \"eyJxxx\",\r\n    \"token_type\": \"Bearer\",\r\n    \"expires_in\": 7200,\r\n    \"refresh_token\": \"xxx\",\r\n    \"user\": {\r\n      \"id\": \"abc123\",\r\n      \"name\": \"zhangsan\",\r\n      \"realname\": \"张三\"\r\n    }\r\n  }\r\n}\r\n```\r\n\r\n未登录：\r\n\r\n```json\r\n{\r\n  \"logged_in\": false,\r\n  \"env\": \"debug\",\r\n  \"ci_base_url\": \"http://10.25.72.141:16644\",\r\n  \"token_path\": \"C:\\\\Users\\\\xxx\\\\.weaverbird\\\\token.debug.json\",\r\n  \"user_id\": null,\r\n  \"user_name\": null,\r\n  \"token\": null\r\n}\r\n```\r\n\r\n## 多环境配置\r\n\r\nCLI 通过**命令名**区分环境（`weaverbird` / `weaverbird-debug` / `weaverbird-test`），配置与后端 `core/config/config.py` 对齐，位于 `config/profiles/`：\r\n\r\n| 命令 | 环境 | `CI_BASE_URL`（SERVER_HOST） | `WEAVERBIRD_URL` |\r\n|------|------|------------------------------|------------------|\r\n| `weaverbird` | `release` | `https://apphost.vesync.cn` | `https://apphost.vesync.cn` |\r\n| `weaverbird-debug` | `debug` | `http://10.25.72.141:16644` | `http://10.25.72.141:18833` |\r\n| `weaverbird-test` | `test` | `http://192.168.10.191:16644` | `http://192.168.10.191:18833` |\r\n\r\n### 校验 profile\r\n\r\n```bash\r\nnpm run config -- --env=release\r\nnpm run config:debug\r\nnpm run config:test\r\nnpm run config:release\r\n```\r\n\r\n### 构建（profile 校验 + 语法检查 + 单元测试）\r\n\r\n```bash\r\nnpm run build\r\n```\r\n\r\n### 发布\r\n\r\n发布前自动执行 `prepublishOnly`（构建与测试）。按环境发布为 **`@app-public` scope 下的独立包**（自动 `--access public`）：\r\n\r\n| 脚本 | npm 包名 | 全局命令 |\r\n|------|----------|----------|\r\n| `npm run publish:release` | `@app-public/weaverbird` | `weaverbird` |\r\n| `npm run publish:debug` | `@app-public/weaverbird-debug` | `weaverbird-debug` |\r\n| `npm run publish:test` | `@app-public/weaverbird-test` | `weaverbird-test` |\r\n| `npm run publish:all` | 依次发布以上三个包 | — |\r\n\r\n```bash\r\nnpm run publish:release              # 发布线上包\r\nnpm run publish:debug              # 发布调试包\r\nnpm run publish:test               # 发布测试包\r\nnpm run publish:all                # 一次性发布三个包（版本号需一致）\r\n\r\n# 透传 npm publish 参数\r\nnpm run publish:release -- --dry-run\r\nnpm run publish:test -- --tag beta\r\n```\r\n\r\n> 本地 `package.json` 为私有工作区包（`weaverbird-cli`，`private: true`），**不可直接 `npm publish`**。`publish:*` 脚本会临时改写为对应环境的独立包名与 bin，发布完成后自动还原。\r\n\r\n三个环境对应**三个不同的 scoped 包名**，互不影响、可独立发版：\r\n\r\n| 环境 | npm 包名 |\r\n|------|----------|\r\n| 线上 | `@app-public/weaverbird` |\r\n| 调试 | `@app-public/weaverbird-debug` |\r\n| 测试 | `@app-public/weaverbird-test` |\r\n\r\n## 运行时环境变量覆盖\r\n\r\n生成 `config.js` 在运行时按命令名加载 profile；仍可通过环境变量覆盖部分配置（便于本地调试）：\r\n\r\n| 变量 | 说明 |\r\n|------|------|\r\n| `WEAVERBIRD_ENV` | 环境标识（debug / test / release） |\r\n| `WEAVERBIRD_CI_URL` | CI API 根地址 |\r\n| `WEAVERBIRD_URL` | WeaverBird 前端地址 |\r\n| `WEAVERBIRD_CLIENT_ID` | OAuth 应用 ID |\r\n| `WEAVERBIRD_CLIENT_SECRET` | OAuth 应用密钥 |\r\n| `WEAVERBIRD_REDIRECT_URI` | OAuth 回调地址，默认 `http://localhost:3333/callback` |\r\n| `WEAVERBIRD_CALLBACK_PORT` | 本地回调端口，默认 `3333` |\r\n| `WEAVERBIRD_SCOPE` | OAuth scope，默认 `read write` |\r\n\r\n## OAuth 凭证配置\r\n\r\n凭证由服务端 **`oauth_client` 表**统一生成与管理（每个 PocketBase 库各一条应用记录）。环境不写在表里，而是通过 `core/config` 连接不同数据库（debug / test / release）自然隔离。\r\n\r\nCLI 侧在 `config/profiles/{debug,test,release}.js` 中配置各环境 API 地址，并从**对应环境数据库**初始化脚本输出的 `CLIENT_ID` / `CLIENT_SECRET` 填入 profile。\r\n\r\n### 初始化 WeaverBird CLI 应用\r\n\r\n在目标环境的 PocketBase 建好 `oauth_client` 表后，用对应环境启动脚本执行（与后端一致，如 `python main.py test` 或 `release`）：\r\n\r\n```bash\r\npython util/data_server_update/maintain_260615/main.py\r\n```\r\n\r\n每个数据库只会创建一条 `name=WeaverBird CLI` 的记录；首次创建时打印明文 `clientSecret`，写入该环境对应的 `cli/config/profiles/*.js`。\r\n\r\n**注意：** 不要将真实 `CLIENT_SECRET` 提交到公开仓库；CI/CD 发布时建议通过环境变量注入。\r\n\r\n## OAuth 客户端应用表（`oauth_client`）\r\n\r\nPocketBase 集合名：`oauth_client`\r\n\r\n| 字段 | 类型 | 必填 | 说明 |\r\n|------|------|------|------|\r\n| `name` | text | 是 | 应用名称，如 `WeaverBird CLI` |\r\n| `clientId` | text | 是 | OAuth `CLIENT_ID`，格式 `cli_` + 32 位 hex，**全局唯一** |\r\n| `clientSecret` | text | 是 | AES 加密存储的 `CLIENT_SECRET`，**不可明文回显** |\r\n| `redirectUris` | json | 是 | 允许的回调地址列表，默认 `[\"http://localhost:3333/callback\"]` |\r\n| `scopes` | text | 是 | 授权范围，默认 `read write` |\r\n| `grantTypes` | json | 是 | 默认 `[\"authorization_code\",\"refresh_token\"]` |\r\n| `isInvalid` | bool | 否 | 是否禁用，默认 `false` |\r\n| `description` | text | 否 | 备注 |\r\n\r\n> **环境隔离**：不在表中存 `env` / `key`。debug、test、release 各自连独立 PocketBase（见 `core/config/config.py` 的 `POCKETBASE_BASE_URL`），各库维护各自的 `oauth_client` 记录。\r\n\r\n### 凭证生成规则\r\n\r\n后端 `OAuthClientExtLogic`（`logic/ext/oauth_client.py`）：\r\n\r\n| 方法 | 说明 |\r\n|------|------|\r\n| `generate_client_id()` | `cli_{secrets.token_hex(16)}` |\r\n| `generate_client_secret()` | `secrets.token_urlsafe(32)` |\r\n| `create_oauth_client(name, ...)` | 创建应用，**仅返回时展示一次**明文 secret |\r\n| `create_or_get_weaverbird_cli()` | 若已有 `WeaverBird CLI` 则返回首条，否则新建 |\r\n| `get_by_client_id` | 按 clientId 查询（唯一键） |\r\n| `verify_client(client_id, secret)` | OAuth token 端校验客户端 |\r\n\r\n`clientSecret` 使用 `Encrypt.encrypt_with_salt` 加密，`salt` 为 `clientId`。\r\n\r\n### 与 CLI profile 的关系\r\n\r\n| 后端环境（core/config） | PocketBase | CLI profile |\r\n|-------------------------|------------|-------------|\r\n| debug | `10.25.72.141:17760` | `config/profiles/debug.js` |\r\n| test | `192.168.10.191:17760` | `config/profiles/test.js` |\r\n| release | `apphost.vesync.cn` | `config/profiles/release.js` |\r\n\r\n各环境分别在对应库执行初始化脚本，将输出的凭证填入同名 profile 即可。\r\n\r\n### 关联表（OAuth 完整链路，后续实现）\r\n\r\n| 表名 | 用途 |\r\n|------|------|\r\n| `oauth_authorization_code` | 授权码：`code`、`clientId`、`userId`、`redirectUri`、`scope`、`expiresTime`（兑换后删除） |\r\n| `oauth_access_token` | 访问令牌：可复用现有 `token` 表扩展，或独立存储 `accessToken`、`refreshToken`、`clientId`、`userId`、`expiresAt` |\r\n\r\n## 目录结构\r\n\r\n```\r\ncli/\r\n├── index.js              # CLI 入口（bin: weaverbird / weaverbird-debug / weaverbird-test）\r\n├── config.js             # 运行时按命令名加载 profile\r\n├── config/\r\n│   └── profiles/         # 各环境 profile（与 core/config 对齐）\r\n├── login.js              # OAuth 登录逻辑\r\n├── tokenStore.js         # ~/.weaverbird/token.json 读写\r\n├── api.js                # Bearer 请求与 refresh_token 刷新\r\n├── scripts/\r\n│   ├── build-config.js   # 校验 profile\r\n│   ├── run-build.js      # build 入口\r\n│   └── run-publish.js    # 按环境发布 npm 包\r\n├── test/\r\n└── package.json\r\n```\r\n\r\n## 开发\r\n\r\n```bash\r\nnpm run build             # 完整构建\r\nnpm run publish:release   # 发布线上包（示例）\r\n```\r\n\r\n## 后端依赖\r\n\r\nCLI 依赖服务端 OAuth 接口（均为 POST JSON，响应格式与项目统一 `{ code, msg, data }`）：\r\n\r\n| 接口 | 说明 |\r\n|------|------|\r\n| `/v1/api/ci/oauth/createOauthClient` | 管理员创建 OAuth 应用（header `token`） |\r\n| `/v1/api/ci/oauth/createAuthorizeSession` | CLI 创建授权页一次性会话（需 client_secret） |\r\n| `/v1/api/ci/oauth/getAuthorizeSession` | Web 查询授权会话状态（pending/expired/invalid） |\r\n| `/v1/api/ci/oauth/authorize` | 签发 authorization code（需 session_token + header `token`） |\r\n| `/v1/api/ci/oauth/token` | 换取/刷新 access_token |\r\n| `/v1/api/ci/open/setToken` | CLI 设置第三方 token（如 figma），需 `cli_token` header |\r\n\r\n授权页由前端 `{WEAVERBIRD_URL}/weaverbird/oauth/authorize?session=...` 实现；会话 **5 分钟过期、一次性使用**，过期或已用后 Web 会拦截并提示重新执行 `weaverbird cli login`。\r\n\r\n业务 API 请求头：`cli_token` header（OAuth 换取的 access_token，与网页 `token` 独立）。\r\n\r\n### PocketBase 需创建的表\r\n\r\n- `oauth_client` — OAuth 应用\r\n- `oauth_authorize_session` — 授权页会话（字段：sessionToken, clientId, redirectUri, responseType, scope, state, expiresTime；使用后删除）\r\n- `oauth_authorization_code` — 授权码（字段：code, clientId, userId, redirectUri, scope, expiresTime；兑换后删除）\r\n- `oauth_refresh_token` — 刷新令牌（字段：refreshToken, clientId, userId, scope, expiresTime, isInvalid）\r\n","readmeFilename":"README.md"}