@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 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.8",
5
- "cliVersion": "0.1.8",
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.8",
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,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 login(channel) {
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 (!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
+ }
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 authenticated() {
151
- 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") });
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();