@shulex/cxclawctl 0.1.7 → 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 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
- ```bash
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
  ```
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "plugin": {
4
- "pluginVersion": "0.1.7",
5
- "cliVersion": "0.1.7",
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.7",
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 Error(`server returned ${response.status}: ${text}`);
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 Error("\u672A\u767B\u5F55\uFF0C\u8BF7\u5148\u8FD0\u884C cxclawctl auth login");
123
+ if (!raw) throw new SessionNotFoundError();
107
124
  return JSON.parse(raw);
108
125
  }
109
126
  async function saveSession(session) {
@@ -116,7 +133,11 @@ async function deleteSession() {
116
133
 
117
134
  // src/auth.ts
118
135
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
119
- async function login(channel) {
136
+ var loginInFlight;
137
+ function publicAuthStatus(session) {
138
+ return { ok: true, loggedIn: true, email: session.email, channel: session.channel, accessExpiresAt: session.accessExpiry };
139
+ }
140
+ async function runLogin(channel) {
120
141
  const session = { channel, accessToken: "", refreshToken: "", accessExpiry: 0, employeeId: "", email: "" };
121
142
  const start = await post(session, "/auth/device/start", { clientVersion: VERSION });
122
143
  console.error(`\u8BF7\u5728\u6D4F\u89C8\u5668\u6253\u5F00\u4EE5\u4E0B\u5730\u5740\u5B8C\u6210\u9489\u9489\u6388\u6743\uFF1A
@@ -124,19 +145,32 @@ ${start.verificationUriComplete}
124
145
  \u8BBE\u5907\u7801\uFF1A${start.userCode}`);
125
146
  await open(start.verificationUriComplete).catch(() => void 0);
126
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;
127
150
  while (Date.now() < deadline) {
128
151
  await sleep(Math.max(2, start.interval) * 1e3);
129
152
  try {
130
153
  const token = await post(session, "/auth/device/token", { deviceCode: start.deviceCode, clientVersion: VERSION });
131
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 });
132
155
  await saveSession(session);
156
+ console.error("\u9489\u9489\u6388\u6743\u6210\u529F\uFF0C\u7EE7\u7EED\u6267\u884C\u5F53\u524D\u547D\u4EE4\u3002");
133
157
  return session;
134
158
  } catch (error) {
135
- if (!String(error).includes("428")) throw error;
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
+ }
136
164
  }
137
165
  }
138
166
  throw new Error("device authorization expired");
139
167
  }
168
+ function login(channel) {
169
+ if (!loginInFlight) loginInFlight = runLogin(channel).finally(() => {
170
+ loginInFlight = void 0;
171
+ });
172
+ return loginInFlight;
173
+ }
140
174
  async function ensureAccess(session) {
141
175
  if (session.accessToken && Date.now() / 1e3 + 30 < session.accessExpiry) return session;
142
176
  const token = await post(session, "/auth/refresh", { refreshToken: session.refreshToken, clientVersion: VERSION });
@@ -144,8 +178,24 @@ async function ensureAccess(session) {
144
178
  await saveSession(session);
145
179
  return session;
146
180
  }
147
- async function authenticated() {
148
- return ensureAccess(await loadSession());
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") });
149
199
  }
150
200
  async function logout() {
151
201
  let session;
@@ -2829,8 +2879,10 @@ var usage = () => [
2829
2879
  async function runAuth(action2, args) {
2830
2880
  if (action2 === "login") {
2831
2881
  if (flag(args, "--server") || flag(args, "--env")) throw new Error("auth login \u56FA\u5B9A\u4F7F\u7528\u751F\u4EA7\u6388\u6743\u670D\u52A1\uFF0C\u4E0D\u652F\u6301 --server \u6216 --env");
2832
- return print(await login(option(args, "--channel") ?? "stable"));
2882
+ const session = await login(option(args, "--channel") ?? "stable");
2883
+ return print(publicAuthStatus(session));
2833
2884
  }
2885
+ if (action2 === "ensure") return print(publicAuthStatus(await authenticated()));
2834
2886
  if (action2 === "logout") {
2835
2887
  await logout();
2836
2888
  return print({ ok: true, loggedOut: true });
@@ -2838,16 +2890,15 @@ async function runAuth(action2, args) {
2838
2890
  if (action2 === "status") {
2839
2891
  try {
2840
2892
  const session = await loadSession();
2841
- return print({ ok: true, loggedIn: true, email: session.email, channel: session.channel, accessExpiresAt: session.accessExpiry });
2893
+ return print(publicAuthStatus(session));
2842
2894
  } catch {
2843
2895
  return print({ ok: true, loggedIn: false });
2844
2896
  }
2845
2897
  }
2846
2898
  if (action2 === "refresh") {
2847
- const session = await authenticated();
2848
- return print({ ok: true, accessExpiresAt: session.accessExpiry });
2899
+ return print(publicAuthStatus(await authenticated()));
2849
2900
  }
2850
- 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");
2851
2902
  }
2852
2903
  async function runTenant(action2, args) {
2853
2904
  const session = await authenticated();