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.
- package/README.md +22 -3
- package/dist/cli/app.js +23 -8
- package/dist/cli/app.js.map +1 -1
- package/dist/cli/grokOAuth.js +215 -216
- package/dist/cli/grokOAuth.js.map +1 -1
- package/dist/cli/main.bundled.js +181 -330
- package/dist/cli/main.bundled.js.map +4 -4
- package/dist/cli/main.js +2 -2
- package/dist/cli/main.js.map +1 -1
- package/dist/cli/modelDiscovery.js +1 -1
- package/dist/cli/modelDiscovery.js.map +1 -1
- package/dist/cli/slashCommands.js +4 -4
- package/dist/cli/slashCommands.js.map +1 -1
- package/dist/cli/updater.js +12 -6
- package/dist/cli/updater.js.map +1 -1
- package/package.json +2 -2
package/dist/cli/main.bundled.js
CHANGED
|
@@ -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
|
-
|
|
184
|
-
async function fetchGrokDiscovery(options = {}) {
|
|
14
|
+
async function requestDeviceCode(options) {
|
|
185
15
|
const fetchImpl = options.fetchImpl ?? fetch;
|
|
186
|
-
const
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
`
|
|
263
|
-
`
|
|
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(
|
|
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("
|
|
53
|
+
throw new GrokOAuthError("Device code response is not an object");
|
|
274
54
|
}
|
|
275
55
|
const obj = body;
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
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
|
|
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
|
|
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 ??
|
|
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
|
|
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
|
|
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
|
-
|
|
465
|
-
|
|
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-
|
|
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, {
|
|
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-
|
|
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-
|
|
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-
|
|
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", "
|
|
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-
|
|
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-
|
|
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-
|
|
20702
|
-
(exit with /exit or Ctrl+C, then run \`zelari-
|
|
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]
|
|
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
|
-
|
|
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-
|
|
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 {
|