@shulex/cxclawctl 0.1.8 → 0.1.9
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 +3 -6
- package/capabilities.lock.json +3 -2
- package/dist/cli.js +56 -8
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -33,15 +33,12 @@ codex plugin marketplace add SolveaCX/cx_claw_codex --ref marketplace/stable
|
|
|
33
33
|
codex plugin add cxclaw@cxclaw
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
安装后不需要单独执行登录命令。首次使用任何需要服务端访问的 CXClaw 能力时,CLI 会自动打开钉钉授权页并持续监听授权结果;授权成功后自动继续原命令,不需要再对 Codex 回复“已授权”。
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
npx --yes @shulex/cxclawctl@latest auth login
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
CLI 固定连接生产授权服务 `https://ai.solvea.cx/api_v2/ticket`,不接受用户指定服务地址或登录环境。CLI 会打开钉钉授权页;一次登录即可访问获准的测试和生产租户。授权完成后,检查登录状态和本地版本:
|
|
38
|
+
CLI 固定连接生产授权服务 `https://ai.solvea.cx/api_v2/ticket`,不接受用户指定服务地址或登录环境。一次登录即可访问获准的测试和生产租户。需要人工诊断授权时,可以执行:
|
|
43
39
|
|
|
44
40
|
```bash
|
|
41
|
+
npx --yes @shulex/cxclawctl@latest auth ensure
|
|
45
42
|
npx --yes @shulex/cxclawctl@latest auth status
|
|
46
43
|
npx --yes @shulex/cxclawctl@latest doctor
|
|
47
44
|
```
|
package/capabilities.lock.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"plugin": {
|
|
4
|
-
"pluginVersion": "0.1.
|
|
5
|
-
"cliVersion": "0.1.
|
|
4
|
+
"pluginVersion": "0.1.9",
|
|
5
|
+
"cliVersion": "0.1.9",
|
|
6
6
|
"toolSchemaVersion": "1",
|
|
7
7
|
"serverApiVersion": "1"
|
|
8
8
|
},
|
|
@@ -158,6 +158,7 @@
|
|
|
158
158
|
}
|
|
159
159
|
],
|
|
160
160
|
"localCommands": [
|
|
161
|
+
"node \"<PLUGIN_ROOT>/plugin-cli.mjs\" auth ensure",
|
|
161
162
|
"node \"<PLUGIN_ROOT>/plugin-cli.mjs\" auth status",
|
|
162
163
|
"node \"<PLUGIN_ROOT>/plugin-cli.mjs\" tenant current",
|
|
163
164
|
"node \"<PLUGIN_ROOT>/plugin-cli.mjs\" tenant search",
|
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ var AUTH_SERVER_URL = "https://ai.solvea.cx/api_v2/ticket";
|
|
|
12
12
|
// package.json
|
|
13
13
|
var package_default = {
|
|
14
14
|
name: "@shulex/cxclawctl",
|
|
15
|
-
version: "0.1.
|
|
15
|
+
version: "0.1.9",
|
|
16
16
|
description: "Solvea CXClaw CLI and Codex plugin runtime",
|
|
17
17
|
type: "module",
|
|
18
18
|
bin: {
|
|
@@ -67,6 +67,16 @@ var TOOL_SCHEMA_VERSION = "1";
|
|
|
67
67
|
var SERVER_API_VERSION = "1";
|
|
68
68
|
|
|
69
69
|
// src/http.ts
|
|
70
|
+
var HttpError = class extends Error {
|
|
71
|
+
constructor(status, responseBody) {
|
|
72
|
+
super(`server returned ${status}: ${responseBody}`);
|
|
73
|
+
this.status = status;
|
|
74
|
+
this.responseBody = responseBody;
|
|
75
|
+
this.name = "HttpError";
|
|
76
|
+
}
|
|
77
|
+
status;
|
|
78
|
+
responseBody;
|
|
79
|
+
};
|
|
70
80
|
function unwrap(value) {
|
|
71
81
|
if (value && typeof value === "object" && Object.keys(value).length === 1 && "data" in value) return value.data;
|
|
72
82
|
return value;
|
|
@@ -86,10 +96,11 @@ async function request(session, path, init = {}, tenant = false) {
|
|
|
86
96
|
const response = await fetch(`${AUTH_SERVER_URL}${path}`, { ...init, headers, signal: AbortSignal.timeout(3e4) });
|
|
87
97
|
const text = await response.text();
|
|
88
98
|
const value = text ? JSON.parse(text) : void 0;
|
|
89
|
-
if (!response.ok) throw new
|
|
99
|
+
if (!response.ok) throw new HttpError(response.status, text);
|
|
90
100
|
return unwrap(value);
|
|
91
101
|
} catch (error) {
|
|
92
102
|
last = error;
|
|
103
|
+
if (error instanceof HttpError && error.status < 500) break;
|
|
93
104
|
if (attempt < 2) await new Promise((r) => setTimeout(r, 200 * 2 ** attempt));
|
|
94
105
|
}
|
|
95
106
|
}
|
|
@@ -101,9 +112,15 @@ var post = (session, path, body, tenant = false) => request(session, path, { met
|
|
|
101
112
|
import { AsyncEntry } from "@napi-rs/keyring";
|
|
102
113
|
var options = process.platform === "linux" ? { linux: { store: "secret-service" } } : void 0;
|
|
103
114
|
var entry = () => new AsyncEntry("com.solvea.cxclaw", "session", options);
|
|
115
|
+
var SessionNotFoundError = class extends Error {
|
|
116
|
+
constructor() {
|
|
117
|
+
super("\u672A\u767B\u5F55");
|
|
118
|
+
this.name = "SessionNotFoundError";
|
|
119
|
+
}
|
|
120
|
+
};
|
|
104
121
|
async function loadSession() {
|
|
105
122
|
const raw = await entry().getPassword();
|
|
106
|
-
if (!raw) throw new
|
|
123
|
+
if (!raw) throw new SessionNotFoundError();
|
|
107
124
|
return JSON.parse(raw);
|
|
108
125
|
}
|
|
109
126
|
async function saveSession(session) {
|
|
@@ -116,10 +133,11 @@ async function deleteSession() {
|
|
|
116
133
|
|
|
117
134
|
// src/auth.ts
|
|
118
135
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
136
|
+
var loginInFlight;
|
|
119
137
|
function publicAuthStatus(session) {
|
|
120
138
|
return { ok: true, loggedIn: true, email: session.email, channel: session.channel, accessExpiresAt: session.accessExpiry };
|
|
121
139
|
}
|
|
122
|
-
async function
|
|
140
|
+
async function runLogin(channel) {
|
|
123
141
|
const session = { channel, accessToken: "", refreshToken: "", accessExpiry: 0, employeeId: "", email: "" };
|
|
124
142
|
const start = await post(session, "/auth/device/start", { clientVersion: VERSION });
|
|
125
143
|
console.error(`\u8BF7\u5728\u6D4F\u89C8\u5668\u6253\u5F00\u4EE5\u4E0B\u5730\u5740\u5B8C\u6210\u9489\u9489\u6388\u6743\uFF1A
|
|
@@ -127,19 +145,32 @@ ${start.verificationUriComplete}
|
|
|
127
145
|
\u8BBE\u5907\u7801\uFF1A${start.userCode}`);
|
|
128
146
|
await open(start.verificationUriComplete).catch(() => void 0);
|
|
129
147
|
const deadline = Date.now() + start.expiresIn * 1e3;
|
|
148
|
+
console.error("\u6B63\u5728\u7B49\u5F85\u9489\u9489\u6388\u6743\uFF0C\u6388\u6743\u5B8C\u6210\u540E\u5F53\u524D\u547D\u4EE4\u4F1A\u81EA\u52A8\u7EE7\u7EED\uFF0C\u65E0\u9700\u56DE\u590D Codex\u3002");
|
|
149
|
+
let nextProgressAt = Date.now() + 15e3;
|
|
130
150
|
while (Date.now() < deadline) {
|
|
131
151
|
await sleep(Math.max(2, start.interval) * 1e3);
|
|
132
152
|
try {
|
|
133
153
|
const token = await post(session, "/auth/device/token", { deviceCode: start.deviceCode, clientVersion: VERSION });
|
|
134
154
|
Object.assign(session, { accessToken: token.accessToken, refreshToken: token.refreshToken, accessExpiry: Math.floor(Date.now() / 1e3) + token.expiresIn, employeeId: token.employee.id, email: token.employee.email });
|
|
135
155
|
await saveSession(session);
|
|
156
|
+
console.error("\u9489\u9489\u6388\u6743\u6210\u529F\uFF0C\u7EE7\u7EED\u6267\u884C\u5F53\u524D\u547D\u4EE4\u3002");
|
|
136
157
|
return session;
|
|
137
158
|
} catch (error) {
|
|
138
|
-
if (!
|
|
159
|
+
if (!(error instanceof HttpError) || error.status !== 428) throw error;
|
|
160
|
+
if (Date.now() >= nextProgressAt) {
|
|
161
|
+
console.error("\u4ECD\u5728\u7B49\u5F85\u9489\u9489\u6388\u6743\u2026\u2026");
|
|
162
|
+
nextProgressAt = Date.now() + 15e3;
|
|
163
|
+
}
|
|
139
164
|
}
|
|
140
165
|
}
|
|
141
166
|
throw new Error("device authorization expired");
|
|
142
167
|
}
|
|
168
|
+
function login(channel) {
|
|
169
|
+
if (!loginInFlight) loginInFlight = runLogin(channel).finally(() => {
|
|
170
|
+
loginInFlight = void 0;
|
|
171
|
+
});
|
|
172
|
+
return loginInFlight;
|
|
173
|
+
}
|
|
143
174
|
async function ensureAccess(session) {
|
|
144
175
|
if (session.accessToken && Date.now() / 1e3 + 30 < session.accessExpiry) return session;
|
|
145
176
|
const token = await post(session, "/auth/refresh", { refreshToken: session.refreshToken, clientVersion: VERSION });
|
|
@@ -147,8 +178,24 @@ async function ensureAccess(session) {
|
|
|
147
178
|
await saveSession(session);
|
|
148
179
|
return session;
|
|
149
180
|
}
|
|
150
|
-
async function
|
|
151
|
-
|
|
181
|
+
async function resolveAuthenticated(runtime) {
|
|
182
|
+
try {
|
|
183
|
+
return await runtime.refresh(await runtime.load());
|
|
184
|
+
} catch (error) {
|
|
185
|
+
if (error instanceof SessionNotFoundError) {
|
|
186
|
+
console.error("CXClaw \u5C1A\u672A\u6388\u6743\uFF0C\u6B63\u5728\u81EA\u52A8\u53D1\u8D77\u9489\u9489\u6388\u6743\u3002");
|
|
187
|
+
return runtime.authorize();
|
|
188
|
+
}
|
|
189
|
+
if (error instanceof HttpError && error.status === 401) {
|
|
190
|
+
console.error("CXClaw \u767B\u5F55\u5DF2\u5931\u6548\uFF0C\u6B63\u5728\u91CD\u65B0\u53D1\u8D77\u9489\u9489\u6388\u6743\u3002");
|
|
191
|
+
await runtime.remove().catch(() => void 0);
|
|
192
|
+
return runtime.authorize();
|
|
193
|
+
}
|
|
194
|
+
throw error;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function authenticated() {
|
|
198
|
+
return resolveAuthenticated({ load: loadSession, refresh: ensureAccess, remove: deleteSession, authorize: () => login("stable") });
|
|
152
199
|
}
|
|
153
200
|
async function logout() {
|
|
154
201
|
let session;
|
|
@@ -2835,6 +2882,7 @@ async function runAuth(action2, args) {
|
|
|
2835
2882
|
const session = await login(option(args, "--channel") ?? "stable");
|
|
2836
2883
|
return print(publicAuthStatus(session));
|
|
2837
2884
|
}
|
|
2885
|
+
if (action2 === "ensure") return print(publicAuthStatus(await authenticated()));
|
|
2838
2886
|
if (action2 === "logout") {
|
|
2839
2887
|
await logout();
|
|
2840
2888
|
return print({ ok: true, loggedOut: true });
|
|
@@ -2850,7 +2898,7 @@ async function runAuth(action2, args) {
|
|
|
2850
2898
|
if (action2 === "refresh") {
|
|
2851
2899
|
return print(publicAuthStatus(await authenticated()));
|
|
2852
2900
|
}
|
|
2853
|
-
throw new Error("auth action must be login, logout, status, or refresh");
|
|
2901
|
+
throw new Error("auth action must be login, ensure, logout, status, or refresh");
|
|
2854
2902
|
}
|
|
2855
2903
|
async function runTenant(action2, args) {
|
|
2856
2904
|
const session = await authenticated();
|