grokbot-control 0.0.0-stage → 0.1.0
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 +160 -2
- package/dist/auth/lock.d.ts +14 -0
- package/dist/auth/lock.js +71 -0
- package/dist/auth/provider.d.ts +89 -0
- package/dist/auth/provider.js +884 -0
- package/dist/auth/store.d.ts +25 -0
- package/dist/auth/store.js +106 -0
- package/dist/auth/types.d.ts +138 -0
- package/dist/auth/types.js +1 -0
- package/dist/cli/config.d.ts +26 -0
- package/dist/cli/config.js +98 -0
- package/dist/cli/help.d.ts +1 -0
- package/dist/cli/help.js +83 -0
- package/dist/cli/io.d.ts +6 -0
- package/dist/cli/io.js +61 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +275 -0
- package/dist/cli/sync.d.ts +4 -0
- package/dist/cli/sync.js +105 -0
- package/dist/connect/client.d.ts +32 -0
- package/dist/connect/client.js +257 -0
- package/dist/dto/result.d.ts +33 -0
- package/dist/dto/result.js +29 -0
- package/dist/dto/types.d.ts +63 -0
- package/dist/dto/types.js +2 -0
- package/dist/errors.d.ts +31 -0
- package/dist/errors.js +64 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +11 -0
- package/dist/mcp/main.d.ts +2 -0
- package/dist/mcp/main.js +82 -0
- package/dist/mcp/tools.d.ts +24 -0
- package/dist/mcp/tools.js +408 -0
- package/dist/mcp/zod-json.d.ts +2 -0
- package/dist/mcp/zod-json.js +4 -0
- package/dist/ops/auth.d.ts +48 -0
- package/dist/ops/auth.js +89 -0
- package/dist/ops/bot.d.ts +52 -0
- package/dist/ops/bot.js +211 -0
- package/dist/ops/limits.d.ts +10 -0
- package/dist/ops/limits.js +31 -0
- package/dist/ops/map.d.ts +7 -0
- package/dist/ops/map.js +57 -0
- package/dist/ops/message.d.ts +33 -0
- package/dist/ops/message.js +118 -0
- package/dist/ops/plane.d.ts +26 -0
- package/dist/ops/plane.js +33 -0
- package/dist/ops/progress.d.ts +34 -0
- package/dist/ops/progress.js +92 -0
- package/dist/ops/session.d.ts +13 -0
- package/dist/ops/session.js +34 -0
- package/dist/ops/sidebar.d.ts +70 -0
- package/dist/ops/sidebar.js +205 -0
- package/dist/sync/attachments.d.ts +76 -0
- package/dist/sync/attachments.js +274 -0
- package/dist/sync/core.d.ts +92 -0
- package/dist/sync/core.js +1027 -0
- package/dist/sync/decode.d.ts +83 -0
- package/dist/sync/decode.js +229 -0
- package/dist/sync/download-policy.d.ts +3 -0
- package/dist/sync/download-policy.js +103 -0
- package/dist/sync/fs.d.ts +24 -0
- package/dist/sync/fs.js +252 -0
- package/dist/sync/hydrate.d.ts +43 -0
- package/dist/sync/hydrate.js +472 -0
- package/dist/sync/render.d.ts +5 -0
- package/dist/sync/render.js +100 -0
- package/dist/testing/mock-persist.d.ts +3 -0
- package/dist/testing/mock-persist.js +45 -0
- package/dist/testing/mock-transport.d.ts +18 -0
- package/dist/testing/mock-transport.js +202 -0
- package/docs/acceptance.md +19 -0
- package/docs/architecture.md +128 -0
- package/docs/auth-lock.md +87 -0
- package/docs/implementation-plan.md +14 -0
- package/docs/implementation-status.md +92 -0
- package/docs/proto-drift.md +9 -0
- package/docs/protocol/README.md +34 -0
- package/docs/protocol/attachment-pagination-0.79.3.md +32 -0
- package/docs/protocol/auth-flow.md +51 -0
- package/docs/protocol/baseline-manifest.md +72 -0
- package/docs/protocol/baselines/full-schema-diff-0.78.4-to-0.79.2.txt +239 -0
- package/docs/protocol/baselines/full-schema-diff-0.78.4-to-0.79.3.txt +240 -0
- package/docs/protocol/baselines/full-schema-diff-0.79.2-to-0.79.3.txt +23 -0
- package/docs/protocol/baselines/schema-0.79.3/README.md +7 -0
- package/docs/protocol/baselines/schema-0.79.3/agent.v1.proto +5184 -0
- package/docs/protocol/baselines/schema-0.79.3/aiserver.v1.proto +18182 -0
- package/docs/protocol/baselines/schema-0.79.3/aiserver.v1_2.proto +54 -0
- package/docs/protocol/baselines/schema-0.79.3/anyrun.v1.proto +37 -0
- package/docs/protocol/baselines/schema-0.79.3/internapi.v1.proto +35 -0
- package/docs/protocol/baselines/schema-0.79.3/snapshot.json.gz +0 -0
- package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.AgentStoreService.proto +34 -0
- package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.AiService.proto +17 -0
- package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.AnalyticsService.proto +17 -0
- package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.BackgroundComposerService.proto +19 -0
- package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.DashboardService.proto +101 -0
- package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.GrokBotService.proto +473 -0
- package/docs/protocol/baselines/schema-0.79.3/svc_aiserver.v1.SandBoxService.proto +64 -0
- package/docs/protocol/baselines/used-surface-0.78.4.json +3694 -0
- package/docs/protocol/baselines/used-surface-0.79.2.json +3835 -0
- package/docs/protocol/baselines/used-surface-0.79.3.json +3835 -0
- package/docs/protocol/baselines/used-surface-diff-0.78.4-to-0.79.2.md +34 -0
- package/docs/protocol/baselines/used-surface-diff-0.78.4-to-0.79.3.md +33 -0
- package/docs/protocol/compat-matrix.md +20 -0
- package/docs/protocol/static-restore.md +64 -0
- package/docs/protocol/surface-map.md +39 -0
- package/docs/protocol/transcript-body-0.79.3.md +98 -0
- package/docs/protocol/update-runbook.md +30 -0
- package/docs/protocol-map.md +106 -0
- package/docs/references.md +55 -0
- package/docs/requirements.md +26 -0
- package/docs/review.md +36 -0
- package/docs/sync-format.md +98 -0
- package/package.json +60 -4
- package/vendor/proto-gen/NOTICE.md +23 -0
- package/vendor/proto-gen/agent.v1_pb.js +9503 -0
- package/vendor/proto-gen/aiserver.v1_2_pb.js +105 -0
- package/vendor/proto-gen/aiserver.v1_pb.js +33163 -0
- package/vendor/proto-gen/anyrun.v1_pb.js +64 -0
- package/vendor/proto-gen/internapi.v1_pb.js +60 -0
- package/vendor/proto-gen/package.json +10 -0
- package/vendor/proto-gen/svc_aiserver.v1.GrokBotService_connect.js +4193 -0
package/README.md
CHANGED
|
@@ -1,3 +1,161 @@
|
|
|
1
|
-
#
|
|
1
|
+
# grokbot-control
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**人通过 Grok Bot App 驱动 → agent CLI 经 MCP/CLI 驱动同一 Bot。**
|
|
4
|
+
可编程控制面(原生 Connect RPC),非自建任务系统。
|
|
5
|
+
|
|
6
|
+
CLI 暂称 **`gbc`**;MCP stdio 入口 **`gbc-mcp`**。
|
|
7
|
+
|
|
8
|
+
> Live auth / production RPC **未在本轮验证**。默认 `--mock` 仅用于显式仿真(结果带 `simulated: true`),不是在线成功。
|
|
9
|
+
|
|
10
|
+
## 快速开始
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm ci
|
|
14
|
+
npm run build
|
|
15
|
+
npm test
|
|
16
|
+
|
|
17
|
+
# 显式 mock 仿真(跨进程状态:GBT_MOCK_STATE 或 ~/.gbc/mock-state.json)
|
|
18
|
+
node dist/cli/main.js --mock --json --allow-writes bot create --name demo --description "test"
|
|
19
|
+
node dist/cli/main.js --mock --json bot list
|
|
20
|
+
node dist/cli/main.js --mock --json --allow-writes message send \
|
|
21
|
+
--agent-id <id> --session-id <sid> --text "hello"
|
|
22
|
+
node dist/cli/main.js --mock --json bot status --agent-id <id>
|
|
23
|
+
node dist/cli/main.js --mock --json --allow-writes sidebar create --name Work
|
|
24
|
+
node dist/cli/main.js --mock --json --allow-writes sidebar move --agent-id <id> --section-id <sid>
|
|
25
|
+
node dist/cli/main.js --mock --json --allow-writes bot delete --agent-id <id> --confirm <id>
|
|
26
|
+
|
|
27
|
+
# MCP(stdio;stdout 仅协议帧)
|
|
28
|
+
GBT_MOCK=1 node dist/mcp/main.js
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Live Connect(真实登录需用户本人在浏览器授权;本仓库测试只用本地 fake 后端):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# 不要设置 GBT_MOCK / GBT_ALLOW_WRITES;默认 backend https://api2.cursor.sh,网站 https://cursor.com
|
|
35
|
+
node dist/cli/main.js --json auth login-begin # stdout: authorization_url(不含 verifier)
|
|
36
|
+
# → 用户在浏览器打开该 URL 并登录
|
|
37
|
+
node dist/cli/main.js --json auth complete --timeout 300 # 轮询 GET /auth/poll;成功后凭据写入 ~/.gbc(0600)
|
|
38
|
+
# 或单命令:auth login --timeout 300(URL 走 stderr 的 open_browser 事件,stdout 只放结果)
|
|
39
|
+
node dist/cli/main.js --json auth whoami # 本地状态,不联网、不打印 token
|
|
40
|
+
node dist/cli/main.js --json auth refresh # 临期才联网;足够新则 refreshed:false
|
|
41
|
+
node dist/cli/main.js --json auth refresh --force # 强制刷新(验证/401恢复)
|
|
42
|
+
node dist/cli/main.js --json bot list --limit 20 # 只读
|
|
43
|
+
node dist/cli/main.js --json sidebar list
|
|
44
|
+
node dist/cli/main.js --json bot status --agent-id <agent_id>
|
|
45
|
+
node dist/cli/main.js --json auth logout # 只删本工具自己的本地凭据
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
鉴权限制(详见 docs/protocol-map.md「Auth」):
|
|
49
|
+
- 协议来自 Grok Bot Linux App **静态逆向**,非官方公开 API。`vendor/proto-gen` 现为 **0.79.3**(本仓库从App恢复生成,离线校验)。较早人工登录验证使用旧0.78.4 vendor;0.79.3 的选定正常RPC已进行隔离临时bot验收,范围/skip见 implementation-status。不能由此推定所有RPC或OAuth刷新已验证。
|
|
50
|
+
- 仅支持 Cursor token;xAI identity 登录结果返回 `unsupported`。
|
|
51
|
+
- 刷新需显式配置公开 OAuth client_id:`GBT_OAUTH_CLIENT_ID`(本工具不内置,也不从 JWT 猜测),或登录时已持久化的绑定;旧凭据可用 `GBT_AUTH_CONFIRM_MIGRATE=1` 确认迁移。临期(<5min)/缺非法 exp / `--force` 才刷新;进程内 singleflight + 跨进程锁;`shouldLogout` 不回退已撤销 token。
|
|
52
|
+
- 凭据独立于 App(不读取、不复用 App 的 sand-secrets.json)。logout 只删本地文件,不吊销服务端会话。
|
|
53
|
+
- MCP:`GBT_MCP_READONLY=1 node dist/mcp/main.js` 仅提供 `auth_whoami` + 只读业务工具(不含 login/logout/cancel/refresh 凭据写工具)。非 READONLY 另有 `auth_refresh`(仅非 secret 元数据)。
|
|
54
|
+
|
|
55
|
+
## Publish / npx
|
|
56
|
+
|
|
57
|
+
本包可发布到 [npm](https://www.npmjs.com/)(包名 `grokbot-control`)。**未声明 SPDX `license`**:`vendor/proto-gen` 来自逆向恢复的 schema,许可不清,见该目录 `NOTICE.md`;请自行评估分发风险。
|
|
58
|
+
|
|
59
|
+
### 一次性准备(首发)
|
|
60
|
+
|
|
61
|
+
1. 在 [npmjs.com](https://www.npmjs.com/) 创建账号并开启 2FA。
|
|
62
|
+
2. 首个版本手动发布一次(包不存在时无法配置 Trusted Publisher):
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm login --auth-type=web --registry=https://registry.npmjs.org
|
|
66
|
+
npm ci && npm test
|
|
67
|
+
npm publish --access public --registry=https://registry.npmjs.org
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
3. 在 npmjs.com → `grokbot-control` → **Settings → Trusted Publisher** 选 GitHub Actions,填写:
|
|
71
|
+
- Organization or user:`younglark`
|
|
72
|
+
- Repository:`grokbot-control`
|
|
73
|
+
- Workflow filename:`publish.yml`
|
|
74
|
+
|
|
75
|
+
新建配置需在 2 天内完成第一次成功发布,否则过期。配置后可在 **Publishing access** 选择禁止 token 发布。
|
|
76
|
+
|
|
77
|
+
### 后续发版(GitHub Actions + OIDC)
|
|
78
|
+
|
|
79
|
+
`.github/workflows/publish.yml` 仅在推送 tag `v*` 时运行(Node 24、npm ≥ 11.5.1、`id-token: write`):`npm ci` → `npm test` → `npm publish --access public`。无需 npm token 或仓库 secret。
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# 工作区干净、已通过本地 build/test
|
|
83
|
+
npm version patch # 或 minor / major;会改 package.json 并打 vX.Y.Z tag
|
|
84
|
+
git push origin main --follow-tags
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
GitHub Release 不会触发发布,可在 tag 推送后基于该 tag 创建 Release,不会重复 publish。首版 0.1.0 已手动发布,不要再推 `v0.1.0` tag(会因版本已存在而失败)。
|
|
88
|
+
|
|
89
|
+
### 安装 / 运行
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx --yes --package=grokbot-control@latest gbc --help
|
|
93
|
+
npx --yes --package=grokbot-control@latest gbc --mock --json bot list
|
|
94
|
+
npx --yes --package=grokbot-control@latest gbc-mcp
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
MCP 建议钉版本,例如:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"command": "npx",
|
|
102
|
+
"args": ["--yes", "--package=grokbot-control@0.1.0", "gbc-mcp"]
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
OAuth 刷新仍需自行配置公开的 `GBT_OAUTH_CLIENT_ID`(本工具不内置 client_id)。
|
|
107
|
+
|
|
108
|
+
## 文件优先同步(无 SQLite)
|
|
109
|
+
|
|
110
|
+
CLI:`gbc sync pull --agent-id BOT [--session-id SESSION] --dir /absolute/private/root
|
|
111
|
+
--account-id STABLE_ID --team-id none --ack-unverified-scope`;`gbc sync status|render|attachments --dir /absolute/private/root`。
|
|
112
|
+
pull/render/attachments 明确授权本地写,不需要远端 `--allow-writes`;`--dry-run` 纯本地计划、无 RPC/文件。
|
|
113
|
+
身份是操作者显式声明而非服务端验证,必须核对稳定 ID,不能用邮箱/token 哈希。
|
|
114
|
+
**render 已可直接 rg 已确认 JSON UI entry 的正文**:message、send-message(text/attachment)、notice、
|
|
115
|
+
tool-call 名称/状态/summary、附件文件引用;其余类型/未知 body 仍 opaque,不猜解密。
|
|
116
|
+
正文补读显式开启 `sync pull ... --hydrate missing`(默认 `off`,不默默增加网络请求)。inline 优先;
|
|
117
|
+
omitted 精确重读同版本;blob 仅通过 store presign 读取正文,不是用户附件。原始 wire/protojson 不覆盖,
|
|
118
|
+
补读 bytes/本地 SHA-256/来源/版本上下文另存,JSONL 与离线 render 均可 grep。请求失败/无法解码返回 `partial`(exit 9),已归档行保留。
|
|
119
|
+
默认额外 RPC 32、正文传输/响应总 8MiB、单正文 4MiB,与 pull 总字节/截止时间共享;参数见格式文档。
|
|
120
|
+
静态解码证据和 synthetic genuine-schema fixture 见 [transcript-body-0.79.3.md](docs/protocol/transcript-body-0.79.3.md)。
|
|
121
|
+
例如 `rg '关键词' /absolute/private/root/derived -g '*.md'`;旧 opaque 归档可离线重新 render。
|
|
122
|
+
head刷新与持久化backfill交替,不删缺失行,不承诺完整性;附件单独按需下载;无Watch/`--all`。
|
|
123
|
+
MCP 设 `GBT_SYNC_ROOT` 才提供 `sync_status`,另需 `GBT_SYNC_ALLOW_LOCAL_WRITES=1` 才有
|
|
124
|
+
`sync_pull/sync_render/sync_attachments`(READONLY 排除写工具;scope 在启动配置;attachments 可选远端 file_path 过滤)。
|
|
125
|
+
命令示例、scope 限制、格式与恢复步骤见 [docs/sync-format.md](docs/sync-format.md)。
|
|
126
|
+
|
|
127
|
+
## 文档
|
|
128
|
+
|
|
129
|
+
| 文件 | 内容 |
|
|
130
|
+
|---|---|
|
|
131
|
+
| [docs/requirements.md](docs/requirements.md) | 需求 |
|
|
132
|
+
| [docs/protocol-map.md](docs/protocol-map.md) | 覆盖矩阵 / RPC |
|
|
133
|
+
| [docs/protocol/](docs/protocol/README.md) | 协议基线、静态还原方法、App 更新 runbook、兼容矩阵、`tools/protocol` 脚本 |
|
|
134
|
+
| [docs/architecture.md](docs/architecture.md) | 模块 / 错误 / 写安全 |
|
|
135
|
+
| [docs/implementation-plan.md](docs/implementation-plan.md) | 分期 |
|
|
136
|
+
| [docs/acceptance.md](docs/acceptance.md) | 验收 |
|
|
137
|
+
| [docs/review.md](docs/review.md) | 审查 |
|
|
138
|
+
| [docs/references.md](docs/references.md) | 一手来源 |
|
|
139
|
+
| [docs/sync-format.md](docs/sync-format.md) | 文件同步格式、身份限制、预算、崩溃恢复 |
|
|
140
|
+
| [docs/implementation-status.md](docs/implementation-status.md) | 实现状态 |
|
|
141
|
+
| [/workspace/grokbot-control-implementation.md](../grokbot-control-implementation.md) | 本轮实现报告 |
|
|
142
|
+
|
|
143
|
+
## 退出码
|
|
144
|
+
|
|
145
|
+
`0` success · `2` usage · `4` auth · `5` not_found · `6` conflict · `7` denied · `8` retryable · `9` failure · `10` unknown · `1` other
|
|
146
|
+
|
|
147
|
+
## 协议 vendor
|
|
148
|
+
|
|
149
|
+
正式协议源见 `proto/`;生成客户端见 `vendor/proto-gen/NOTICE.md`(App `0.79.3` commit `4739412`,本仓库从App恢复生成;生成器 Buf Connect Apache-2.0;schema 本身无单独官方许可声明)。
|
|
150
|
+
|
|
151
|
+
### 同步修订
|
|
152
|
+
支持默认session、generation内持久化backfill与跨调用session轮转;归档是retained observations,不是删除同步/完整镜像。
|
|
153
|
+
附件仅按需App store aliases,用户上传和send-message attachment均支持;每次Resolve+条件GET,流式预算与scope绑定。
|
|
154
|
+
attachments需与pull一致的`--account-id --team-id --ack-unverified-scope`。
|
|
155
|
+
`--page-size 1 --max-pages 1`可做小量分页验收。详细边界见[格式文档](docs/sync-format.md)。
|
|
156
|
+
|
|
157
|
+
### Native auth lock installation
|
|
158
|
+
跨进程 refresh 使用 `fs-native-extensions@1.5.3` 原生 OS advisory lock(非外部 flock 命令、非 mtime lease)。
|
|
159
|
+
Node20+,Linux/macOS/Windows x64/arm64 npm包预编译;缺binding fail closed。升级前停止全部旧MCP进程,旧Linux flock与新OFD锁不互通。
|
|
160
|
+
Linux x64已实测;macOS/Windows仅源码检查+CI矩阵,未在本机执行。Windows需另核查用户目录ACL;只支持本地文件系统。
|
|
161
|
+
详见 [native lock构建/平台/证据](docs/auth-lock.md)。
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Backend-neutral contract. Only nonblocking native attempts run on the event loop. */
|
|
2
|
+
export interface AdvisoryLockBinding {
|
|
3
|
+
tryLock(fd: number): boolean;
|
|
4
|
+
unlock(fd: number): void;
|
|
5
|
+
}
|
|
6
|
+
export type LockWaitOptions = {
|
|
7
|
+
waitMs?: number;
|
|
8
|
+
signal?: AbortSignal;
|
|
9
|
+
};
|
|
10
|
+
/** Permanent independent file. Never truncate, replace or unlink; OS releases on process death.
|
|
11
|
+
* Cancellation is honored only BEFORE exchange starts, never while fn owns the lock.
|
|
12
|
+
* Only local filesystems supported; all credential writers must cooperate.
|
|
13
|
+
*/
|
|
14
|
+
export declare function withAdvisoryLock<T>(file: string, fn: () => Promise<T>, opts?: LockWaitOptions): Promise<T>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
4
|
+
import { GbcError } from '../errors.js';
|
|
5
|
+
let binding;
|
|
6
|
+
function nativeBinding() {
|
|
7
|
+
if (binding)
|
|
8
|
+
return binding;
|
|
9
|
+
try {
|
|
10
|
+
if (!['linux', 'darwin', 'win32'].includes(process.platform))
|
|
11
|
+
throw new Error('unsupported platform');
|
|
12
|
+
const loaded = createRequire(import.meta.url)('fs-native-extensions');
|
|
13
|
+
if (typeof loaded.tryLock !== 'function' || typeof loaded.unlock !== 'function')
|
|
14
|
+
throw new Error('invalid binding');
|
|
15
|
+
binding = loaded;
|
|
16
|
+
return loaded;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
throw new GbcError('auth_lock_unavailable', 'Native advisory lock unavailable; run npm ci with fs-native-extensions@1.5.3 prebuilds for your OS/architecture. See docs/auth-lock.md for source build instructions. No lease fallback.', { exitCode: 4 });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Permanent independent file. Never truncate, replace or unlink; OS releases on process death.
|
|
23
|
+
* Cancellation is honored only BEFORE exchange starts, never while fn owns the lock.
|
|
24
|
+
* Only local filesystems supported; all credential writers must cooperate.
|
|
25
|
+
*/
|
|
26
|
+
export async function withAdvisoryLock(file, fn, opts = {}) {
|
|
27
|
+
const native = nativeBinding();
|
|
28
|
+
const waitMs = opts.waitMs ?? 30000;
|
|
29
|
+
if (!Number.isFinite(waitMs) || waitMs < 0)
|
|
30
|
+
throw new GbcError('invalid_argument', 'invalid lock wait deadline');
|
|
31
|
+
const fd = fs.openSync(file, fs.constants.O_CREAT | fs.constants.O_RDWR | (fs.constants.O_NOFOLLOW ?? 0), 0o600);
|
|
32
|
+
let held = false;
|
|
33
|
+
try {
|
|
34
|
+
const original = fs.fstatSync(fd);
|
|
35
|
+
if (fs.lstatSync(file).isSymbolicLink() || !original.isFile() || original.nlink !== 1)
|
|
36
|
+
throw new GbcError('auth_lock_path', 'lock must be an independent regular file');
|
|
37
|
+
if (process.platform !== 'win32')
|
|
38
|
+
fs.fchmodSync(fd, 0o600);
|
|
39
|
+
const deadline = performance.now() + waitMs;
|
|
40
|
+
for (;;) {
|
|
41
|
+
if (opts.signal?.aborted)
|
|
42
|
+
throw new GbcError('auth_lock_canceled', 'lock wait canceled; no exchange started', { exitCode: 8, retryable: true, details: { rpc_dispatched: false } });
|
|
43
|
+
try {
|
|
44
|
+
held = native.tryLock(fd);
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
// libuv Windows ERROR_LOCK_VIOLATION may surface as EBUSY; other failures fail closed.
|
|
48
|
+
if (!['EAGAIN', 'EBUSY'].includes(e.code ?? ''))
|
|
49
|
+
throw new GbcError('auth_lock_unavailable', 'native lock failed; credentials kept', { exitCode: 4 });
|
|
50
|
+
}
|
|
51
|
+
if (held) {
|
|
52
|
+
const current = fs.lstatSync(file);
|
|
53
|
+
if (current.isSymbolicLink() || original.ino !== current.ino || original.dev !== current.dev)
|
|
54
|
+
throw new GbcError('auth_lock_path', 'lock path replaced while waiting; refusing exchange');
|
|
55
|
+
return await fn();
|
|
56
|
+
}
|
|
57
|
+
if (performance.now() >= deadline)
|
|
58
|
+
throw new GbcError('auth_refresh_lock_timeout', 'lock wait deadline exceeded; owner left intact', { exitCode: 8, retryable: true, details: { rpc_dispatched: false } });
|
|
59
|
+
await delay(Math.min(25, Math.max(1, deadline - performance.now())));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
try {
|
|
64
|
+
if (held)
|
|
65
|
+
native.unlock(fd);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
fs.closeSync(fd);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { FileCredentialStore } from "./store.js";
|
|
2
|
+
import type { AuthKind, AuthProvider } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Independent Cursor browser login (product-custom challenge + poll; NOT RFC 8628).
|
|
5
|
+
*
|
|
6
|
+
* Protocol source: STATIC reverse engineering of Grok Bot Linux 0.79.2 / 0.79.3
|
|
7
|
+
* (/workspace/grokbot-linux-auth-analysis.md; evidence/main-app.pretty.cjs = A,
|
|
8
|
+
* main-core.pretty.cjs = C). Not official public API documentation.
|
|
9
|
+
* - A:38644-38657 Rdn: verifier = base64url(32 random bytes); challenge =
|
|
10
|
+
* base64url(SHA256(verifier string)); uuid = randomUUID();
|
|
11
|
+
* `${website}/loginDeepControl?challenge&uuid&mode=login&redirectTarget[&supportsSelectedTeamLogin=true]`
|
|
12
|
+
* - A:38720-38816 Aze/Odn: GET `${api}/auth/poll?uuid=..&verifier=..`; 404 = pending;
|
|
13
|
+
* max 150 tries, 1s * 1.2^i capped 10s; 3 consecutive non-404 failures stop;
|
|
14
|
+
* 403 {error:"sign_in_policy_violation"} = policy denial.
|
|
15
|
+
* - A:38584-38593 Cdn: success body {accessToken, refreshToken}; A:38605-38622 mze:
|
|
16
|
+
* xAI identity body {xaiAccessToken,...} -> UNSUPPORTED here (different refresh path).
|
|
17
|
+
* - A:56108-56125: refresh POST new URL("/oauth/token", backend) JSON
|
|
18
|
+
* {client_id, grant_type:"refresh_token", refresh_token}; A:54459 response
|
|
19
|
+
* {access_token?, refresh_token?, shouldLogout?, error?}; refresh_token defaults to old.
|
|
20
|
+
* - C:35811 KLe: refresh 300s before JWT exp.
|
|
21
|
+
* client_id: the App selects a public client identifier per backend (C:35866-35869).
|
|
22
|
+
* This tool does NOT embed it; refresh requires explicit GBT_OAUTH_CLIENT_ID config
|
|
23
|
+
* or a client_id persisted at login / confirmed migration.
|
|
24
|
+
*/
|
|
25
|
+
export declare const DEFAULT_BACKENDS: string[];
|
|
26
|
+
export declare const DEFAULT_WEBSITE = "https://cursor.com";
|
|
27
|
+
export declare const POLICY_VIOLATION = "sign_in_policy_violation";
|
|
28
|
+
export declare const REFRESH_EARLY_MS = 300000;
|
|
29
|
+
export declare const REFRESH_DEADLINE_MS = 20000;
|
|
30
|
+
export declare const PENDING_TTL_MS: number;
|
|
31
|
+
export type AuthHttp = {
|
|
32
|
+
fetch: typeof fetch;
|
|
33
|
+
};
|
|
34
|
+
export type AuthProviderOptions = {
|
|
35
|
+
kind: AuthKind;
|
|
36
|
+
backend: string;
|
|
37
|
+
/** Browser login site. Default https://cursor.com; custom requires trust. */
|
|
38
|
+
website?: string;
|
|
39
|
+
http?: AuthHttp;
|
|
40
|
+
store?: FileCredentialStore;
|
|
41
|
+
trustCustomBackend?: boolean;
|
|
42
|
+
/** Public OAuth client_id for refresh. Default: GBT_OAUTH_CLIENT_ID (no built-in value). */
|
|
43
|
+
clientId?: string;
|
|
44
|
+
/** redirectTarget login param. Default GBT_LOGIN_REDIRECT_TARGET || "cli" (library default, A:38644 / A:38845; App uses "sand", A:49834). */
|
|
45
|
+
redirectTarget?: string;
|
|
46
|
+
/** Test hooks. */
|
|
47
|
+
now?: () => number;
|
|
48
|
+
sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
49
|
+
/** Override refresh HTTP deadline (default 20s). */
|
|
50
|
+
refreshDeadlineMs?: number;
|
|
51
|
+
};
|
|
52
|
+
export declare function generateLoginChallenge(): {
|
|
53
|
+
verifier: string;
|
|
54
|
+
challenge: string;
|
|
55
|
+
uuid: string;
|
|
56
|
+
};
|
|
57
|
+
export declare function buildLoginUrl(website: string, p: {
|
|
58
|
+
challenge: string;
|
|
59
|
+
uuid: string;
|
|
60
|
+
redirectTarget: string;
|
|
61
|
+
}): string;
|
|
62
|
+
/** JWT payload (unverified decode — only for exp/email display; never trust for auth / client_id). */
|
|
63
|
+
export declare function decodeJwtPayload(token: string): {
|
|
64
|
+
exp?: number;
|
|
65
|
+
email?: string;
|
|
66
|
+
sub?: string;
|
|
67
|
+
iss?: string;
|
|
68
|
+
} | null;
|
|
69
|
+
export declare function refreshEndpointFor(backend: string): string;
|
|
70
|
+
export type ExpStatus = {
|
|
71
|
+
ok: true;
|
|
72
|
+
expires_at_ms: number;
|
|
73
|
+
} | {
|
|
74
|
+
ok: false;
|
|
75
|
+
reason: "missing_exp" | "illegal_exp";
|
|
76
|
+
};
|
|
77
|
+
/** Classify stored / JWT exp. Never invents an expiry. */
|
|
78
|
+
export declare function classifyExpiry(expires_at_ms: number | undefined, nowMs: number): {
|
|
79
|
+
status: ExpStatus;
|
|
80
|
+
needsRefresh: boolean;
|
|
81
|
+
stillValid: boolean;
|
|
82
|
+
};
|
|
83
|
+
export declare function createAuthProvider(opts: AuthProviderOptions): AuthProvider;
|
|
84
|
+
/** Injected token provider for tests / mock — never logs the token; never disk-refreshes. */
|
|
85
|
+
export declare function staticTokenProvider(opts: {
|
|
86
|
+
kind: AuthKind;
|
|
87
|
+
backend: string;
|
|
88
|
+
accessToken: string;
|
|
89
|
+
}): AuthProvider;
|