zelari-code 0.1.1 → 0.1.3

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.
@@ -10,276 +10,71 @@ var __export = (target, all) => {
10
10
  __defProp(target, name, { get: all[name], enumerable: true });
11
11
  };
12
12
 
13
- // src/cli/oauthCallbackServer.ts
14
- import { createServer } from "node:http";
15
- function startOAuthCallbackServer(options = {}) {
16
- const host = options.host ?? "127.0.0.1";
17
- const preferredPort = options.port ?? 14523;
18
- const timeoutMs = options.timeoutMs ?? 6e4;
19
- const expectedPath = options.expectedPath ?? "/oauth/callback";
20
- const successHtml = options.successHtml ?? DEFAULT_SUCCESS_HTML;
21
- return new Promise((resolveStart, rejectStart) => {
22
- let resolved = false;
23
- let resolveCode = null;
24
- let rejectCode = null;
25
- let timeoutHandle = null;
26
- const server = createServer((req, res) => {
27
- try {
28
- if (!req.url) {
29
- res.writeHead(400, { "Content-Type": "text/plain" });
30
- res.end("Bad request");
31
- return;
32
- }
33
- const url2 = new URL(req.url, `http://${host}:${preferredPort}`);
34
- if (url2.pathname !== expectedPath) {
35
- res.writeHead(404, { "Content-Type": "text/plain" });
36
- res.end("Not found");
37
- return;
38
- }
39
- const code = url2.searchParams.get("code");
40
- const state = url2.searchParams.get("state");
41
- const errorParam = url2.searchParams.get("error");
42
- const errorDescription = url2.searchParams.get("error_description");
43
- const extras = {};
44
- for (const [key, value] of url2.searchParams.entries()) {
45
- if (key !== "code" && key !== "state" && key !== "error" && key !== "error_description") {
46
- extras[key] = value;
47
- }
48
- }
49
- const params = {
50
- ...code ? { code } : {},
51
- ...state ? { state } : {},
52
- ...errorParam ? { error: errorParam } : {},
53
- ...errorDescription ? { errorDescription } : {},
54
- extras
55
- };
56
- if (errorParam) {
57
- res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
58
- res.end(`<h1>OAuth error: ${errorParam}</h1>`);
59
- if (rejectCode && !resolved) {
60
- resolved = true;
61
- if (timeoutHandle) clearTimeout(timeoutHandle);
62
- rejectCode(new OAuthCallbackError(`OAuth provider returned error: ${errorParam}`));
63
- }
64
- return;
65
- }
66
- if (!code) {
67
- res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
68
- res.end("<h1>Missing <code>code</code> query parameter</h1>");
69
- return;
70
- }
71
- res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
72
- res.end(successHtml);
73
- if (resolveCode && !resolved) {
74
- resolved = true;
75
- if (timeoutHandle) clearTimeout(timeoutHandle);
76
- resolveCode(params);
77
- }
78
- setTimeout(() => {
79
- try {
80
- server.close();
81
- } catch {
82
- }
83
- }, 100);
84
- } catch (err) {
85
- try {
86
- res.writeHead(500, { "Content-Type": "text/plain" });
87
- res.end("Internal error");
88
- } catch {
89
- }
90
- if (!resolved && rejectCode) {
91
- resolved = true;
92
- if (timeoutHandle) clearTimeout(timeoutHandle);
93
- rejectCode(err instanceof Error ? err : new Error(String(err)));
94
- }
95
- }
96
- });
97
- const onError = (err) => {
98
- if (!resolved) {
99
- resolved = true;
100
- if (timeoutHandle) clearTimeout(timeoutHandle);
101
- rejectStart(err);
102
- }
103
- };
104
- server.once("error", onError);
105
- server.listen(preferredPort, host, () => {
106
- const addr = server.address();
107
- const actualPort = addr ? addr.port : preferredPort;
108
- const waitForCode = () => {
109
- return new Promise((res, rej) => {
110
- if (resolved) {
111
- rej(new OAuthCallbackClosedError());
112
- return;
113
- }
114
- resolveCode = res;
115
- rejectCode = rej;
116
- timeoutHandle = setTimeout(() => {
117
- if (!resolved) {
118
- resolved = true;
119
- try {
120
- server.close();
121
- } catch {
122
- }
123
- rej(new OAuthCallbackTimeoutError(timeoutMs));
124
- }
125
- }, timeoutMs);
126
- if (typeof timeoutHandle?.unref === "function") timeoutHandle.unref();
127
- });
128
- };
129
- const close = () => {
130
- if (timeoutHandle) clearTimeout(timeoutHandle);
131
- try {
132
- server.close();
133
- } catch {
134
- }
135
- if (!resolved && rejectCode) {
136
- resolved = true;
137
- rejectCode(new OAuthCallbackClosedError());
138
- }
139
- };
140
- resolveStart({ port: actualPort, waitForCode, close });
141
- });
142
- });
143
- }
144
- var DEFAULT_SUCCESS_HTML, OAuthCallbackTimeoutError, OAuthCallbackClosedError, OAuthCallbackError;
145
- var init_oauthCallbackServer = __esm({
146
- "src/cli/oauthCallbackServer.ts"() {
147
- "use strict";
148
- DEFAULT_SUCCESS_HTML = `
149
- <!DOCTYPE html>
150
- <html lang="en">
151
- <head>
152
- <meta charset="UTF-8">
153
- <title>zelari-code OAuth</title>
154
- </head>
155
- <body style="font-family: -apple-system, BlinkMacSystemFont, sans-serif; padding: 40px; text-align: center; color: #1a1a1a;">
156
- <h1 style="color: #00aa33;">\u2713 Authentication complete</h1>
157
- <p>You can close this tab and return to zelari-code.</p>
158
- </body>
159
- </html>
160
- `.trim();
161
- OAuthCallbackTimeoutError = class extends Error {
162
- constructor(timeoutMs) {
163
- super(`OAuth callback not received within ${timeoutMs}ms`);
164
- this.name = "OAuthCallbackTimeoutError";
165
- }
166
- };
167
- OAuthCallbackClosedError = class extends Error {
168
- constructor() {
169
- super("OAuth callback server closed before receiving a callback");
170
- this.name = "OAuthCallbackClosedError";
171
- }
172
- };
173
- OAuthCallbackError = class extends Error {
174
- constructor(message) {
175
- super(message);
176
- this.name = "OAuthCallbackError";
177
- }
178
- };
179
- }
180
- });
181
-
182
13
  // src/cli/grokOAuth.ts
183
- import nodeCrypto from "node:crypto";
184
- async function fetchGrokDiscovery(options = {}) {
14
+ async function requestDeviceCode(options) {
185
15
  const fetchImpl = options.fetchImpl ?? fetch;
186
- const discoveryUrl = options.discoveryUrl ?? `${options.issuer ?? DEFAULT_GROK_OAUTH_ISSUER}/.well-known/openid-configuration`;
187
- let response;
188
- try {
189
- response = await fetchImpl(discoveryUrl, { method: "GET", headers: { Accept: "application/json" } });
190
- } catch (err) {
191
- throw new GrokOAuthError(`OAuth discovery network error: ${err instanceof Error ? err.message : String(err)}`, "discovery_network_error");
192
- }
193
- if (!response.ok) {
194
- const text = await response.text().catch(() => "");
195
- throw new GrokOAuthError(
196
- `OAuth discovery HTTP ${response.status}: ${text.slice(0, 200)}`,
197
- `discovery_http_${response.status}`
198
- );
199
- }
200
- let body;
201
- try {
202
- body = await response.json();
203
- } catch (err) {
204
- throw new GrokOAuthError(`OAuth discovery returned invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
205
- }
206
- if (!body || typeof body !== "object") {
207
- throw new GrokOAuthError("OAuth discovery returned non-object body");
208
- }
209
- const obj = body;
210
- if (typeof obj.authorization_endpoint !== "string" || typeof obj.token_endpoint !== "string") {
211
- throw new GrokOAuthError("OAuth discovery missing authorization_endpoint or token_endpoint");
212
- }
213
- return {
214
- authorizationEndpoint: obj.authorization_endpoint,
215
- tokenEndpoint: obj.token_endpoint
216
- };
217
- }
218
- function generatePkce() {
219
- const random = getRandomValues(new Uint8Array(64));
220
- const verifier = Buffer.from(random).toString("base64url").slice(0, 128);
221
- const hash2 = createHash("sha256").update(verifier).digest();
222
- const challenge = hash2.toString("base64url").replace(/=+$/, "");
223
- return { verifier, challenge };
224
- }
225
- function buildGrokAuthorizeUrl(options) {
16
+ const endpoint = options.deviceCodeEndpoint ?? DEFAULT_DEVICE_CODE_ENDPOINT;
226
17
  const scopes = options.scopes ?? DEFAULT_GROK_OAUTH_SCOPES;
227
- const params = new URLSearchParams({
228
- client_id: options.clientId,
229
- redirect_uri: options.redirectUri,
230
- response_type: "code",
231
- scope: scopes.join(" "),
232
- state: options.state ?? crypto.randomUUID(),
233
- code_challenge: options.codeChallenge,
234
- code_challenge_method: "S256"
235
- });
236
- return `${options.authorizeEndpoint}?${params.toString()}`;
237
- }
238
- async function exchangeGrokCode(options) {
239
- const fetchImpl = options.fetchImpl ?? fetch;
240
18
  let response;
241
19
  try {
242
- response = await fetchImpl(options.tokenEndpoint, {
20
+ response = await fetchImpl(endpoint, {
243
21
  method: "POST",
244
22
  headers: {
245
23
  "Content-Type": "application/x-www-form-urlencoded",
246
24
  Accept: "application/json"
247
25
  },
248
26
  body: new URLSearchParams({
249
- grant_type: "authorization_code",
250
27
  client_id: options.clientId,
251
- code: options.code,
252
- redirect_uri: options.redirectUri,
253
- code_verifier: options.codeVerifier
28
+ scope: scopes.join(" ")
254
29
  }).toString()
255
30
  });
256
31
  } catch (err) {
257
- throw new GrokOAuthError(`Token exchange network error: ${err instanceof Error ? err.message : String(err)}`);
32
+ throw new GrokOAuthError(
33
+ `Device code request network error: ${err instanceof Error ? err.message : String(err)}`,
34
+ "device_code_network_error"
35
+ );
258
36
  }
259
37
  if (!response.ok) {
260
38
  const errText = await response.text().catch(() => "");
261
39
  throw new GrokOAuthError(
262
- `Token exchange HTTP ${response.status}: ${errText.slice(0, 200)}`,
263
- `http_${response.status}`
40
+ `Device code request HTTP ${response.status}: ${errText.slice(0, 200)}`,
41
+ `device_code_http_${response.status}`
264
42
  );
265
43
  }
266
44
  let body;
267
45
  try {
268
46
  body = await response.json();
269
47
  } catch (err) {
270
- throw new GrokOAuthError(`Token exchange returned invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
48
+ throw new GrokOAuthError(
49
+ `Device code response invalid JSON: ${err instanceof Error ? err.message : String(err)}`
50
+ );
271
51
  }
272
52
  if (!body || typeof body !== "object") {
273
- throw new GrokOAuthError("Token exchange returned non-object body");
53
+ throw new GrokOAuthError("Device code response is not an object");
274
54
  }
275
55
  const obj = body;
276
- const accessToken = obj.access_token;
277
- if (typeof accessToken !== "string" || accessToken.length === 0) {
278
- throw new GrokOAuthError("Token exchange response missing access_token", "no_access_token");
56
+ const deviceCode = obj.device_code;
57
+ const userCode = obj.user_code;
58
+ const verificationUri = obj.verification_uri;
59
+ if (typeof deviceCode !== "string" || deviceCode.length === 0) {
60
+ throw new GrokOAuthError("Device code response missing device_code", "no_device_code");
61
+ }
62
+ if (typeof userCode !== "string" || userCode.length === 0) {
63
+ throw new GrokOAuthError("Device code response missing user_code", "no_user_code");
64
+ }
65
+ if (typeof verificationUri !== "string" || verificationUri.length === 0) {
66
+ throw new GrokOAuthError("Device code response missing verification_uri", "no_verification_uri");
279
67
  }
280
- return parseTokenResponseBody(obj, accessToken, "Token exchange");
68
+ return {
69
+ deviceCode,
70
+ userCode,
71
+ verificationUri,
72
+ ...typeof obj.verification_uri_complete === "string" && obj.verification_uri_complete.length > 0 ? { verificationUriComplete: obj.verification_uri_complete } : {},
73
+ expiresIn: typeof obj.expires_in === "number" && Number.isFinite(obj.expires_in) ? obj.expires_in : 1800,
74
+ interval: typeof obj.interval === "number" && Number.isFinite(obj.interval) ? obj.interval : 5
75
+ };
281
76
  }
282
- function parseTokenResponseBody(obj, accessToken, label) {
77
+ function parseTokenResponseBody(obj, accessToken) {
283
78
  const result = { accessToken };
284
79
  if (typeof obj.expires_in === "number" && Number.isFinite(obj.expires_in)) {
285
80
  result.expiresAt = Date.now() + obj.expires_in * 1e3;
@@ -292,6 +87,116 @@ function parseTokenResponseBody(obj, accessToken, label) {
292
87
  }
293
88
  return result;
294
89
  }
90
+ async function pollForDeviceToken(options) {
91
+ const fetchImpl = options.fetchImpl ?? fetch;
92
+ const sleep = options.sleepImpl ?? defaultSleep;
93
+ const endpoint = options.tokenEndpoint ?? DEFAULT_TOKEN_ENDPOINT;
94
+ const deadline = Date.now() + options.timeoutMs;
95
+ let interval = Math.max(options.interval, 1);
96
+ while (true) {
97
+ if (Date.now() >= deadline) {
98
+ throw new GrokOAuthError("Device authorization timed out waiting for user approval", "timeout");
99
+ }
100
+ let response;
101
+ try {
102
+ response = await fetchImpl(endpoint, {
103
+ method: "POST",
104
+ headers: {
105
+ "Content-Type": "application/x-www-form-urlencoded",
106
+ Accept: "application/json"
107
+ },
108
+ body: new URLSearchParams({
109
+ grant_type: DEVICE_GRANT_TYPE,
110
+ client_id: options.clientId,
111
+ device_code: options.deviceCode
112
+ }).toString()
113
+ });
114
+ } catch (err) {
115
+ throw new GrokOAuthError(
116
+ `Token poll network error: ${err instanceof Error ? err.message : String(err)}`,
117
+ "poll_network_error"
118
+ );
119
+ }
120
+ if (response.ok) {
121
+ let body;
122
+ try {
123
+ body = await response.json();
124
+ } catch (err) {
125
+ throw new GrokOAuthError(
126
+ `Token response invalid JSON: ${err instanceof Error ? err.message : String(err)}`
127
+ );
128
+ }
129
+ if (!body || typeof body !== "object") {
130
+ throw new GrokOAuthError("Token response is not an object");
131
+ }
132
+ const obj = body;
133
+ const accessToken = obj.access_token;
134
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
135
+ throw new GrokOAuthError("Token response missing access_token", "no_access_token");
136
+ }
137
+ return parseTokenResponseBody(obj, accessToken);
138
+ }
139
+ let errBody = {};
140
+ try {
141
+ const parsed = await response.json();
142
+ if (parsed && typeof parsed === "object") errBody = parsed;
143
+ } catch {
144
+ }
145
+ const errorCode = typeof errBody.error === "string" ? errBody.error : `http_${response.status}`;
146
+ switch (errorCode) {
147
+ case "authorization_pending":
148
+ await sleep(interval * 1e3);
149
+ continue;
150
+ case "slow_down":
151
+ interval += 5;
152
+ await sleep(interval * 1e3);
153
+ continue;
154
+ case "expired_token":
155
+ case "expired":
156
+ throw new GrokOAuthError("Device code expired before user authorized", "expired");
157
+ case "access_denied":
158
+ case "denied":
159
+ throw new GrokOAuthError("User denied the authorization request", "denied");
160
+ case "invalid_grant":
161
+ throw new GrokOAuthError("Device code rejected (invalid_grant)", "invalid_grant");
162
+ default:
163
+ throw new GrokOAuthError(
164
+ `Token poll error: ${errorCode}${typeof errBody.error_description === "string" ? ` \u2014 ${errBody.error_description}` : ""}`,
165
+ errorCode
166
+ );
167
+ }
168
+ }
169
+ }
170
+ async function runGrokOAuthFlow(options = {}) {
171
+ const clientId = options.clientId || process.env.GROK_OAUTH_CLIENT_ID || DEFAULT_GROK_OAUTH_CLIENT_ID;
172
+ if (!clientId || clientId.trim().length === 0) {
173
+ throw new GrokOAuthError("Missing clientId", "no_client_id");
174
+ }
175
+ const deviceAuth = await requestDeviceCode({
176
+ clientId,
177
+ scopes: options.scopes,
178
+ deviceCodeEndpoint: options.deviceCodeEndpoint,
179
+ fetchImpl: options.fetchImpl
180
+ });
181
+ if (options.onUserCode) {
182
+ await options.onUserCode(deviceAuth);
183
+ }
184
+ try {
185
+ await openBrowser(deviceAuth.verificationUriComplete ?? deviceAuth.verificationUri);
186
+ } catch {
187
+ }
188
+ const timeoutMs = options.timeoutMs ?? DEFAULT_OAUTH_TIMEOUT_MS;
189
+ const effectiveTimeout = Math.min(timeoutMs, deviceAuth.expiresIn * 1e3);
190
+ return pollForDeviceToken({
191
+ clientId,
192
+ deviceCode: deviceAuth.deviceCode,
193
+ interval: deviceAuth.interval,
194
+ timeoutMs: effectiveTimeout,
195
+ tokenEndpoint: options.tokenEndpoint,
196
+ fetchImpl: options.fetchImpl,
197
+ sleepImpl: options.sleepImpl
198
+ });
199
+ }
295
200
  async function refreshGrokToken(options) {
296
201
  if (!options.clientId || options.clientId.trim().length === 0) {
297
202
  throw new GrokOAuthError("Missing clientId", "no_client_id");
@@ -299,7 +204,7 @@ async function refreshGrokToken(options) {
299
204
  if (!options.refreshToken || options.refreshToken.trim().length === 0) {
300
205
  throw new GrokOAuthError("Missing refreshToken", "no_refresh_token");
301
206
  }
302
- const endpoint = options.tokenEndpoint ?? "https://oauth.x.ai/token";
207
+ const endpoint = options.tokenEndpoint ?? DEFAULT_TOKEN_ENDPOINT;
303
208
  const fetchImpl = options.fetchImpl ?? fetch;
304
209
  let response;
305
210
  try {
@@ -340,77 +245,7 @@ async function refreshGrokToken(options) {
340
245
  if (typeof accessToken !== "string" || accessToken.length === 0) {
341
246
  throw new GrokOAuthError("Token refresh response missing access_token", "no_access_token");
342
247
  }
343
- return parseTokenResponseBody(obj, accessToken, "Token refresh");
344
- }
345
- async function runGrokOAuthFlow(options) {
346
- const clientId = options.clientId || process.env.GROK_OAUTH_CLIENT_ID || DEFAULT_GROK_OAUTH_CLIENT_ID;
347
- if (!clientId || clientId.trim().length === 0) {
348
- throw new GrokOAuthError("Missing clientId", "no_client_id");
349
- }
350
- const callbackPort = options.callbackPort ?? DEFAULT_GROK_OAUTH_REDIRECT_PORT;
351
- const callbackPath = options.callbackPath ?? DEFAULT_GROK_OAUTH_REDIRECT_PATH;
352
- const redirectUri = `http://127.0.0.1:${callbackPort}${callbackPath}`;
353
- const discovery = await fetchGrokDiscovery({
354
- issuer: options.issuer,
355
- discoveryUrl: options.discoveryUrl,
356
- fetchImpl: options.fetchImpl
357
- });
358
- const { verifier: codeVerifier, challenge: codeChallenge } = generatePkce();
359
- const state = crypto.randomUUID();
360
- const authorizeUrl = buildGrokAuthorizeUrl({
361
- clientId,
362
- redirectUri,
363
- scopes: options.scopes,
364
- state,
365
- codeChallenge,
366
- authorizeEndpoint: discovery.authorizationEndpoint
367
- });
368
- let handle;
369
- try {
370
- handle = await startOAuthCallbackServer({
371
- port: callbackPort,
372
- timeoutMs: options.callbackTimeoutMs ?? 12e4,
373
- expectedPath: callbackPath
374
- });
375
- } catch (err) {
376
- throw new GrokOAuthError(
377
- `Failed to start OAuth callback server on port ${callbackPort}: ${err instanceof Error ? err.message : String(err)}`,
378
- "callback_bind_failed"
379
- );
380
- }
381
- try {
382
- if (options.onBrowserOpen) {
383
- await options.onBrowserOpen(authorizeUrl);
384
- } else {
385
- await openBrowser(authorizeUrl);
386
- }
387
- const callbackResult = await handle.waitForCode();
388
- if (callbackResult.state !== state) {
389
- throw new GrokOAuthError(
390
- `OAuth state mismatch \u2014 possible CSRF. expected=${state} got=${callbackResult.state ?? "(none)"}`,
391
- "state_mismatch"
392
- );
393
- }
394
- if (callbackResult.error) {
395
- throw new GrokOAuthError(
396
- `OAuth provider returned error: ${callbackResult.error}${callbackResult.errorDescription ? ` \u2014 ${callbackResult.errorDescription}` : ""}`,
397
- callbackResult.error
398
- );
399
- }
400
- if (!callbackResult.code) {
401
- throw new GrokOAuthError("OAuth callback did not include authorization code", "no_code");
402
- }
403
- return await exchangeGrokCode({
404
- clientId,
405
- code: callbackResult.code,
406
- codeVerifier,
407
- redirectUri,
408
- tokenEndpoint: discovery.tokenEndpoint,
409
- fetchImpl: options.fetchImpl
410
- });
411
- } finally {
412
- handle.close();
413
- }
248
+ return parseTokenResponseBody(obj, accessToken);
414
249
  }
415
250
  async function openBrowser(url2) {
416
251
  const { spawn: spawn3 } = await import("node:child_process");
@@ -438,12 +273,10 @@ async function openBrowser(url2) {
438
273
  }
439
274
  });
440
275
  }
441
- var getRandomValues, createHash, GrokOAuthError, DEFAULT_GROK_OAUTH_CLIENT_ID, DEFAULT_GROK_OAUTH_ISSUER, DEFAULT_GROK_OAUTH_SCOPES, DEFAULT_GROK_OAUTH_REDIRECT_PORT, DEFAULT_GROK_OAUTH_REDIRECT_PATH;
276
+ var GrokOAuthError, DEFAULT_GROK_OAUTH_CLIENT_ID, DEFAULT_GROK_OAUTH_SCOPES, DEFAULT_DEVICE_CODE_ENDPOINT, DEFAULT_TOKEN_ENDPOINT, DEVICE_GRANT_TYPE, DEFAULT_OAUTH_TIMEOUT_MS, defaultSleep;
442
277
  var init_grokOAuth = __esm({
443
278
  "src/cli/grokOAuth.ts"() {
444
279
  "use strict";
445
- init_oauthCallbackServer();
446
- ({ getRandomValues, createHash } = nodeCrypto);
447
280
  GrokOAuthError = class extends Error {
448
281
  constructor(message, code) {
449
282
  super(message);
@@ -452,7 +285,6 @@ var init_grokOAuth = __esm({
452
285
  }
453
286
  };
454
287
  DEFAULT_GROK_OAUTH_CLIENT_ID = "b1a00492-073a-47ea-816f-4c329264a828";
455
- DEFAULT_GROK_OAUTH_ISSUER = "https://auth.x.ai";
456
288
  DEFAULT_GROK_OAUTH_SCOPES = [
457
289
  "openid",
458
290
  "profile",
@@ -461,8 +293,11 @@ var init_grokOAuth = __esm({
461
293
  "grok-cli:access",
462
294
  "api:access"
463
295
  ];
464
- DEFAULT_GROK_OAUTH_REDIRECT_PORT = 56121;
465
- DEFAULT_GROK_OAUTH_REDIRECT_PATH = "/callback";
296
+ DEFAULT_DEVICE_CODE_ENDPOINT = "https://auth.x.ai/oauth2/device/code";
297
+ DEFAULT_TOKEN_ENDPOINT = "https://auth.x.ai/oauth2/token";
298
+ DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
299
+ DEFAULT_OAUTH_TIMEOUT_MS = 3e5;
300
+ defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
466
301
  }
467
302
  });
468
303
 
@@ -752,12 +587,15 @@ async function checkForUpdate(fetcher = fetch, registryUrl) {
752
587
  updateAvailable: cmp < 0
753
588
  };
754
589
  }
755
- async function performUpdate(packageName = "zelari-coder", executor = spawn2) {
590
+ async function performUpdate(packageName = "zelari-code", executor = spawn2) {
756
591
  return new Promise((resolve) => {
757
592
  const args = ["install", "-g", `${packageName}@latest`];
758
593
  let stdout = "";
759
594
  let stderr = "";
760
- const child = executor("npm", args, { stdio: ["ignore", "pipe", "pipe"] });
595
+ const child = executor("npm", args, {
596
+ stdio: ["ignore", "pipe", "pipe"],
597
+ shell: process.platform === "win32"
598
+ });
761
599
  child.stdout?.on("data", (chunk) => {
762
600
  stdout += chunk.toString();
763
601
  });
@@ -789,7 +627,7 @@ var init_updater = __esm({
789
627
  "use strict";
790
628
  require2 = createRequire(import.meta.url);
791
629
  __dirname2 = path15.dirname(fileURLToPath(import.meta.url));
792
- REGISTRY_URL = "https://registry.npmjs.org/zelari-coder/latest";
630
+ REGISTRY_URL = "https://registry.npmjs.org/zelari-code/latest";
793
631
  }
794
632
  });
795
633
 
@@ -1021,7 +859,7 @@ function handleSlashCommand(text, availableSkills) {
1021
859
  /council <input> \u2014 invoke the multi-agent council on input
1022
860
  /council-feedback <memberId> <1-5> [note] \u2014 rate a council member for future ranking (Task I.2)
1023
861
  /promote-member <memberId> \u2014 promote a council member to a standalone skill (v3-K)
1024
- /update [--yes|-y] \u2014 check for zelari-coder updates; --yes performs the update (v3-N)
862
+ /update [--yes|-y] \u2014 check for zelari-code updates; --yes performs the update (v3-N)
1025
863
  /steer <text> \u2014 enqueue a follow-up prompt on the active run (Task 18.2)
1026
864
  /steer --interrupt <text> \u2014 cancel current run + enqueue <text> for next dispatch (Task C.3.2)
1027
865
  /compact \u2014 compact the session transcript
@@ -1327,7 +1165,7 @@ ${formatSkillList(availableSkills)}`
1327
1165
  return {
1328
1166
  handled: true,
1329
1167
  kind: "update_usage",
1330
- message: "Usage: /update \u2014 check for the latest zelari-coder version\n /update --yes (or -y) \u2014 install the latest version (will ask you to restart manually)"
1168
+ message: "Usage: /update \u2014 check for the latest zelari-code version\n /update --yes (or -y) \u2014 install the latest version (will ask you to restart manually)"
1331
1169
  };
1332
1170
  }
1333
1171
  return {
@@ -4060,7 +3898,7 @@ var PROVIDER_BASE_URLS = {
4060
3898
  "openai-compatible": "https://api.openai.com/v1"
4061
3899
  };
4062
3900
  function defaultModelsFilePath() {
4063
- return process.env.ANATHEMA_MODELS_FILE ?? path6.join(homedir(), ".tmp", "anathema-coder", "models.json");
3901
+ return process.env.ANATHEMA_MODELS_FILE ?? path6.join(homedir(), ".tmp", "zelari-code", "models.json");
4064
3902
  }
4065
3903
  function getModelsFilePath() {
4066
3904
  return defaultModelsFilePath();
@@ -20646,7 +20484,7 @@ ${lines.join("\n")}`;
20646
20484
  {
20647
20485
  id: crypto.randomUUID(),
20648
20486
  role: "system",
20649
- content: `[update] \u{1F195} zelari-coder ${info.latestVersion} available (current: ${info.currentVersion})
20487
+ content: `[update] \u{1F195} zelari-code ${info.latestVersion} available (current: ${info.currentVersion})
20650
20488
  Run \`/update --yes\` to install. You'll need to restart manually after.`,
20651
20489
  ts: Date.now()
20652
20490
  }
@@ -20684,7 +20522,7 @@ ${lines.join("\n")}`;
20684
20522
  {
20685
20523
  id: crypto.randomUUID(),
20686
20524
  role: "system",
20687
- content: `[update] running \`npm install -g zelari-coder@latest\`...`,
20525
+ content: `[update] running \`npm install -g zelari-code@latest\`...`,
20688
20526
  ts: Date.now()
20689
20527
  }
20690
20528
  ]);
@@ -20698,8 +20536,8 @@ ${lines.join("\n")}`;
20698
20536
  role: "system",
20699
20537
  content: `[update] \u2705 installed successfully
20700
20538
 
20701
- Please restart zelari-coder manually to use the new version.
20702
- (exit with /exit or Ctrl+C, then run \`zelari-coder\` again)`,
20539
+ Please restart zelari-code manually to use the new version.
20540
+ (exit with /exit or Ctrl+C, then run \`zelari-code\` again)`,
20703
20541
  ts: Date.now()
20704
20542
  }
20705
20543
  ]);
@@ -21216,14 +21054,27 @@ ${lines.join("\n")}`,
21216
21054
  {
21217
21055
  id: crypto.randomUUID(),
21218
21056
  role: "system",
21219
- content: "[login oauth] opening browser for SuperGrok authentication (PKCE)...",
21057
+ content: "[login oauth] requesting device code from xAI...",
21220
21058
  ts: Date.now()
21221
21059
  }
21222
21060
  ]);
21223
21061
  setBusy(true);
21224
21062
  try {
21225
21063
  const resultOAuth = await runGrokOAuthFlow({
21226
- callbackTimeoutMs: 12e4
21064
+ // Show the user_code + verification_uri as soon as xAI returns them.
21065
+ onUserCode: (info) => {
21066
+ setMessages((prev) => [
21067
+ ...prev,
21068
+ {
21069
+ id: crypto.randomUUID(),
21070
+ role: "system",
21071
+ content: `[login oauth] Open ${info.verificationUri} in your browser and enter the code:
21072
+ ${info.userCode}
21073
+ (Opening your browser automatically...)`,
21074
+ ts: Date.now()
21075
+ }
21076
+ ]);
21077
+ }
21227
21078
  });
21228
21079
  setOAuthToken("grok", {
21229
21080
  apiKey: resultOAuth.accessToken,
@@ -21393,7 +21244,7 @@ async function backgroundUpdateCheck() {
21393
21244
  const info = await checkForUpdate2();
21394
21245
  if (info.updateAvailable && !info.error) {
21395
21246
  console.error(
21396
- `[zelari-coder] \u{1F195} v${info.latestVersion} available (current: v${info.currentVersion}). Run \`zelari-coder\` then \`/update --yes\` to upgrade.`
21247
+ `[zelari-code] \u{1F195} v${info.latestVersion} available (current: v${info.currentVersion}). Run \`zelari-code\` then \`/update --yes\` to upgrade.`
21397
21248
  );
21398
21249
  }
21399
21250
  } catch {