teleton 0.8.1 → 0.8.2

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.
Files changed (36) hide show
  1. package/dist/{chunk-5FNWBZ5K.js → chunk-2IZU3REP.js} +147 -82
  2. package/dist/chunk-3UFPFWYP.js +12 -0
  3. package/dist/{chunk-KVXV7EF7.js → chunk-55SKE6YH.js} +2 -2
  4. package/dist/{setup-server-32XGDPE6.js → chunk-57URFK6M.js} +7 -206
  5. package/dist/chunk-5SEMA47R.js +75 -0
  6. package/dist/{chunk-S6PHGKOC.js → chunk-7YKSXOQQ.js} +10 -2
  7. package/dist/{chunk-UP55PXFH.js → chunk-C4NKJT2Z.js} +8 -0
  8. package/dist/{chunk-CGOXE4WP.js → chunk-GGXJLMOH.js} +404 -730
  9. package/dist/{chunk-QBHRXLZS.js → chunk-H7MFXJZK.js} +2 -2
  10. package/dist/{chunk-3S4GGLLR.js → chunk-HEDJCLA6.js} +58 -19
  11. package/dist/{chunk-AYWEJCDB.js → chunk-J73TA3UM.js} +7 -7
  12. package/dist/{chunk-QV2GLOTK.js → chunk-LC4TV3KL.js} +1 -1
  13. package/dist/{chunk-RCMD3U65.js → chunk-NQ6FZKCE.js} +13 -0
  14. package/dist/{chunk-7U7BOHCL.js → chunk-VYKW7FMV.js} +147 -63
  15. package/dist/chunk-W25Z7CM6.js +487 -0
  16. package/dist/{chunk-OJCLKU5Z.js → chunk-WFTC3JJW.js} +16 -0
  17. package/dist/{server-3FHI2SEB.js → chunk-XBSCYMKM.js} +23 -369
  18. package/dist/{chunk-PHSAHTK4.js → chunk-YOSUPUAJ.js} +75 -7
  19. package/dist/cli/index.js +61 -17
  20. package/dist/{client-MPHPIZB6.js → client-YOOHI776.js} +4 -4
  21. package/dist/{get-my-gifts-CC6HAVWB.js → get-my-gifts-Y7EN7RK4.js} +3 -3
  22. package/dist/index.js +14 -13
  23. package/dist/{memory-UBHM7ILG.js → memory-Q6EWGK2S.js} +6 -4
  24. package/dist/memory-hook-WUXJNVT5.js +18 -0
  25. package/dist/{migrate-UBBEJ5BL.js → migrate-WFU6COBN.js} +4 -4
  26. package/dist/server-GYZXKIKU.js +787 -0
  27. package/dist/server-YODFBZKG.js +392 -0
  28. package/dist/setup-server-IZBUOJRU.js +215 -0
  29. package/dist/{store-M5IMUQCL.js → store-7M4XV6M5.js} +5 -5
  30. package/dist/{task-dependency-resolver-RR2O5S7B.js → task-dependency-resolver-L6UUMTHK.js} +2 -2
  31. package/dist/{task-executor-6W5HRX5C.js → task-executor-XBNJLUCS.js} +2 -2
  32. package/dist/{tool-adapter-IH5VGBOO.js → tool-adapter-IVX2XQJE.js} +1 -1
  33. package/dist/{tool-index-PMAOXWUA.js → tool-index-NYH57UWP.js} +3 -3
  34. package/dist/{transcript-NGDPSNIH.js → transcript-IM7G25OS.js} +2 -2
  35. package/package.json +4 -2
  36. package/dist/chunk-XBE4JB7C.js +0 -8
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  getModelsForProvider
3
- } from "./chunk-OJCLKU5Z.js";
3
+ } from "./chunk-WFTC3JJW.js";
4
4
  import {
5
5
  CONFIGURABLE_KEYS,
6
6
  TonProxyManager,
@@ -30,31 +30,22 @@ import {
30
30
  validateWritePath,
31
31
  writePluginSecret,
32
32
  writeRawConfig
33
- } from "./chunk-CGOXE4WP.js";
33
+ } from "./chunk-GGXJLMOH.js";
34
34
  import {
35
35
  invalidateEndpointCache,
36
36
  invalidateTonClientCache,
37
37
  setToncenterApiKey
38
- } from "./chunk-S6PHGKOC.js";
39
- import "./chunk-KVXV7EF7.js";
40
- import "./chunk-7TECSLJ4.js";
38
+ } from "./chunk-7YKSXOQQ.js";
41
39
  import {
42
40
  getErrorMessage
43
- } from "./chunk-XBE4JB7C.js";
44
- import "./chunk-AYWEJCDB.js";
41
+ } from "./chunk-3UFPFWYP.js";
45
42
  import {
46
43
  getProviderMetadata,
47
44
  validateApiKeyFormat
48
- } from "./chunk-PHSAHTK4.js";
49
- import "./chunk-QV2GLOTK.js";
50
- import "./chunk-7U7BOHCL.js";
51
- import "./chunk-3S4GGLLR.js";
45
+ } from "./chunk-YOSUPUAJ.js";
52
46
  import {
53
47
  setTonapiKey
54
48
  } from "./chunk-VFA7QMCZ.js";
55
- import "./chunk-UP55PXFH.js";
56
- import "./chunk-XQUHC3JZ.js";
57
- import "./chunk-R4YSJ4EY.js";
58
49
  import {
59
50
  WORKSPACE_PATHS,
60
51
  WORKSPACE_ROOT
@@ -63,41 +54,10 @@ import {
63
54
  addLogListener,
64
55
  clearLogListeners,
65
56
  createLogger
66
- } from "./chunk-RCMD3U65.js";
57
+ } from "./chunk-NQ6FZKCE.js";
67
58
  import {
68
59
  getTaskStore
69
60
  } from "./chunk-4L66JHQE.js";
70
- import "./chunk-3RG5ZIWI.js";
71
-
72
- // src/webui/server.ts
73
- import { Hono as Hono14 } from "hono";
74
- import { serve } from "@hono/node-server";
75
- import { cors } from "hono/cors";
76
- import { streamSSE as streamSSE2 } from "hono/streaming";
77
- import { bodyLimit } from "hono/body-limit";
78
- import { setCookie, getCookie, deleteCookie } from "hono/cookie";
79
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
80
- import { join as join4, dirname, resolve as resolve2, relative as relative2 } from "path";
81
- import { fileURLToPath } from "url";
82
-
83
- // src/webui/middleware/auth.ts
84
- import { randomBytes, timingSafeEqual } from "crypto";
85
- var COOKIE_NAME = "teleton_session";
86
- var COOKIE_MAX_AGE = 7 * 24 * 60 * 60;
87
- function generateToken() {
88
- return randomBytes(32).toString("base64url");
89
- }
90
- function maskToken(token) {
91
- if (token.length < 12) return "****";
92
- return `${token.slice(0, 4)}...${token.slice(-4)}`;
93
- }
94
- function safeCompare(a, b) {
95
- if (!a || !b) return false;
96
- const bufA = Buffer.from(a);
97
- const bufB = Buffer.from(b);
98
- if (bufA.length !== bufB.length) return false;
99
- return timingSafeEqual(bufA, bufB);
100
- }
101
61
 
102
62
  // src/webui/log-interceptor.ts
103
63
  var LogInterceptor = class {
@@ -444,9 +404,9 @@ function createLogsRoutes(_deps) {
444
404
  }),
445
405
  event: "log"
446
406
  });
447
- await new Promise((resolve3) => {
448
- if (aborted) return resolve3();
449
- stream.onAbort(() => resolve3());
407
+ await new Promise((resolve2) => {
408
+ if (aborted) return resolve2();
409
+ stream.onAbort(() => resolve2());
450
410
  });
451
411
  if (cleanup) cleanup();
452
412
  });
@@ -2310,325 +2270,19 @@ function createTonProxyRoutes(deps) {
2310
2270
  return app;
2311
2271
  }
2312
2272
 
2313
- // src/webui/server.ts
2314
- var log3 = createLogger("WebUI");
2315
- function findWebDist() {
2316
- const candidates = [
2317
- resolve2("dist/web"),
2318
- // npm start / teleton start (from project root)
2319
- resolve2("web")
2320
- // fallback
2321
- ];
2322
- const __dirname = dirname(fileURLToPath(import.meta.url));
2323
- candidates.push(
2324
- resolve2(__dirname, "web"),
2325
- // dist/web when __dirname = dist/
2326
- resolve2(__dirname, "../dist/web")
2327
- // when running with tsx from src/
2328
- );
2329
- for (const candidate of candidates) {
2330
- if (existsSync3(join4(candidate, "index.html"))) {
2331
- return candidate;
2332
- }
2333
- }
2334
- return null;
2335
- }
2336
- var WebUIServer = class {
2337
- app;
2338
- server = null;
2339
- deps;
2340
- authToken;
2341
- constructor(deps) {
2342
- this.deps = deps;
2343
- this.app = new Hono14();
2344
- this.authToken = deps.config.auth_token || generateToken();
2345
- this.setupMiddleware();
2346
- this.setupRoutes();
2347
- }
2348
- /** Set an HttpOnly session cookie */
2349
- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Hono context type
2350
- setSessionCookie(c) {
2351
- setCookie(c, COOKIE_NAME, this.authToken, {
2352
- path: "/",
2353
- httpOnly: true,
2354
- sameSite: "Strict",
2355
- secure: false,
2356
- // localhost is HTTP
2357
- maxAge: COOKIE_MAX_AGE
2358
- });
2359
- }
2360
- setupMiddleware() {
2361
- this.app.use(
2362
- "*",
2363
- cors({
2364
- origin: this.deps.config.cors_origins,
2365
- credentials: true,
2366
- allowMethods: ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH"],
2367
- allowHeaders: ["Content-Type", "Authorization"],
2368
- maxAge: 3600
2369
- })
2370
- );
2371
- if (this.deps.config.log_requests) {
2372
- this.app.use("*", async (c, next) => {
2373
- const start = Date.now();
2374
- await next();
2375
- const duration = Date.now() - start;
2376
- log3.info(`${c.req.method} ${c.req.path} \u2192 ${c.res.status} (${duration}ms)`);
2377
- });
2378
- }
2379
- this.app.use(
2380
- "*",
2381
- bodyLimit({
2382
- maxSize: 2 * 1024 * 1024,
2383
- // 2MB
2384
- onError: (c) => c.json({ success: false, error: "Request body too large (max 2MB)" }, 413)
2385
- })
2386
- );
2387
- this.app.use("*", async (c, next) => {
2388
- await next();
2389
- c.res.headers.set("X-Content-Type-Options", "nosniff");
2390
- c.res.headers.set("X-Frame-Options", "DENY");
2391
- c.res.headers.set("Referrer-Policy", "strict-origin-when-cross-origin");
2392
- });
2393
- this.app.use("/api/*", async (c, next) => {
2394
- const cookieToken = getCookie(c, COOKIE_NAME);
2395
- if (cookieToken && safeCompare(cookieToken, this.authToken)) {
2396
- return next();
2397
- }
2398
- const authHeader = c.req.header("Authorization");
2399
- if (authHeader) {
2400
- const match = authHeader.match(/^Bearer\s+(.+)$/i);
2401
- if (match && safeCompare(match[1], this.authToken)) {
2402
- return next();
2403
- }
2404
- }
2405
- const queryToken = c.req.query("token");
2406
- if (queryToken && safeCompare(queryToken, this.authToken)) {
2407
- return next();
2408
- }
2409
- return c.json({ success: false, error: "Unauthorized" }, 401);
2410
- });
2411
- }
2412
- setupRoutes() {
2413
- this.app.get("/health", (c) => c.json({ status: "ok" }));
2414
- this.app.get("/auth/exchange", (c) => {
2415
- const token = c.req.query("token");
2416
- if (!token || !safeCompare(token, this.authToken)) {
2417
- return c.json({ success: false, error: "Invalid token" }, 401);
2418
- }
2419
- this.setSessionCookie(c);
2420
- return c.redirect("/");
2421
- });
2422
- this.app.post("/auth/login", async (c) => {
2423
- try {
2424
- const body = await c.req.json();
2425
- if (!body.token || !safeCompare(body.token, this.authToken)) {
2426
- return c.json({ success: false, error: "Invalid token" }, 401);
2427
- }
2428
- this.setSessionCookie(c);
2429
- return c.json({ success: true });
2430
- } catch {
2431
- return c.json({ success: false, error: "Invalid request body" }, 400);
2432
- }
2433
- });
2434
- this.app.post("/auth/logout", (c) => {
2435
- deleteCookie(c, COOKIE_NAME, { path: "/" });
2436
- return c.json({ success: true });
2437
- });
2438
- this.app.get("/auth/check", (c) => {
2439
- const cookieToken = getCookie(c, COOKIE_NAME);
2440
- const authenticated = !!(cookieToken && safeCompare(cookieToken, this.authToken));
2441
- return c.json({ success: true, data: { authenticated } });
2442
- });
2443
- this.app.route("/api/status", createStatusRoutes(this.deps));
2444
- this.app.route("/api/tools", createToolsRoutes(this.deps));
2445
- this.app.route("/api/logs", createLogsRoutes(this.deps));
2446
- this.app.route("/api/memory", createMemoryRoutes(this.deps));
2447
- this.app.route("/api/soul", createSoulRoutes(this.deps));
2448
- this.app.route("/api/plugins", createPluginsRoutes(this.deps));
2449
- this.app.route("/api/mcp", createMcpRoutes(this.deps));
2450
- this.app.route("/api/workspace", createWorkspaceRoutes(this.deps));
2451
- this.app.route("/api/tasks", createTasksRoutes(this.deps));
2452
- this.app.route("/api/config", createConfigRoutes(this.deps));
2453
- this.app.route("/api/marketplace", createMarketplaceRoutes(this.deps));
2454
- this.app.route("/api/hooks", createHooksRoutes(this.deps));
2455
- this.app.route("/api/ton-proxy", createTonProxyRoutes(this.deps));
2456
- this.app.post("/api/agent/start", async (c) => {
2457
- const lifecycle = this.deps.lifecycle;
2458
- if (!lifecycle) {
2459
- return c.json({ error: "Agent lifecycle not available" }, 503);
2460
- }
2461
- const state = lifecycle.getState();
2462
- if (state === "running") {
2463
- return c.json({ state: "running" }, 409);
2464
- }
2465
- if (state === "stopping") {
2466
- return c.json({ error: "Agent is currently stopping, please wait" }, 409);
2467
- }
2468
- lifecycle.start().catch((err) => {
2469
- log3.error({ err }, "Agent start failed");
2470
- });
2471
- return c.json({ state: "starting" });
2472
- });
2473
- this.app.post("/api/agent/stop", async (c) => {
2474
- const lifecycle = this.deps.lifecycle;
2475
- if (!lifecycle) {
2476
- return c.json({ error: "Agent lifecycle not available" }, 503);
2477
- }
2478
- const state = lifecycle.getState();
2479
- if (state === "stopped") {
2480
- return c.json({ state: "stopped" }, 409);
2481
- }
2482
- if (state === "starting") {
2483
- return c.json({ error: "Agent is currently starting, please wait" }, 409);
2484
- }
2485
- lifecycle.stop().catch((err) => {
2486
- log3.error({ err }, "Agent stop failed");
2487
- });
2488
- return c.json({ state: "stopping" });
2489
- });
2490
- this.app.get("/api/agent/status", (c) => {
2491
- const lifecycle = this.deps.lifecycle;
2492
- if (!lifecycle) {
2493
- return c.json({ error: "Agent lifecycle not available" }, 503);
2494
- }
2495
- return c.json({
2496
- state: lifecycle.getState(),
2497
- uptime: lifecycle.getUptime(),
2498
- error: lifecycle.getError() ?? null
2499
- });
2500
- });
2501
- this.app.get("/api/agent/events", (c) => {
2502
- const lifecycle = this.deps.lifecycle;
2503
- if (!lifecycle) {
2504
- return c.json({ error: "Agent lifecycle not available" }, 503);
2505
- }
2506
- return streamSSE2(c, async (stream) => {
2507
- let aborted = false;
2508
- stream.onAbort(() => {
2509
- aborted = true;
2510
- });
2511
- const now = Date.now();
2512
- await stream.writeSSE({
2513
- event: "status",
2514
- id: String(now),
2515
- data: JSON.stringify({
2516
- state: lifecycle.getState(),
2517
- error: lifecycle.getError() ?? null,
2518
- timestamp: now
2519
- }),
2520
- retry: 3e3
2521
- });
2522
- const onStateChange = (event) => {
2523
- if (aborted) return;
2524
- void stream.writeSSE({
2525
- event: "status",
2526
- id: String(event.timestamp),
2527
- data: JSON.stringify({
2528
- state: event.state,
2529
- error: event.error ?? null,
2530
- timestamp: event.timestamp
2531
- })
2532
- });
2533
- };
2534
- lifecycle.on("stateChange", onStateChange);
2535
- while (!aborted) {
2536
- await stream.sleep(3e4);
2537
- if (aborted) break;
2538
- await stream.writeSSE({
2539
- event: "ping",
2540
- data: ""
2541
- });
2542
- }
2543
- lifecycle.off("stateChange", onStateChange);
2544
- });
2545
- });
2546
- const webDist = findWebDist();
2547
- if (webDist) {
2548
- const indexHtml = readFileSync3(join4(webDist, "index.html"), "utf-8");
2549
- const mimeTypes = {
2550
- js: "application/javascript",
2551
- css: "text/css",
2552
- svg: "image/svg+xml",
2553
- png: "image/png",
2554
- jpg: "image/jpeg",
2555
- jpeg: "image/jpeg",
2556
- ico: "image/x-icon",
2557
- json: "application/json",
2558
- woff2: "font/woff2",
2559
- woff: "font/woff"
2560
- };
2561
- this.app.get("*", (c) => {
2562
- const filePath = resolve2(join4(webDist, c.req.path));
2563
- const rel = relative2(webDist, filePath);
2564
- if (rel.startsWith("..") || resolve2(filePath) !== filePath) {
2565
- return c.html(indexHtml);
2566
- }
2567
- try {
2568
- const content = readFileSync3(filePath);
2569
- const ext = filePath.split(".").pop() || "";
2570
- if (mimeTypes[ext]) {
2571
- const immutable = c.req.path.startsWith("/assets/");
2572
- return c.body(content, 200, {
2573
- "Content-Type": mimeTypes[ext],
2574
- "Cache-Control": immutable ? "public, max-age=31536000, immutable" : "public, max-age=3600"
2575
- });
2576
- }
2577
- } catch {
2578
- }
2579
- return c.html(indexHtml);
2580
- });
2581
- }
2582
- this.app.onError((err, c) => {
2583
- log3.error({ err }, "WebUI error");
2584
- return c.json(
2585
- {
2586
- success: false,
2587
- error: err.message || "Internal server error"
2588
- },
2589
- 500
2590
- );
2591
- });
2592
- }
2593
- async start() {
2594
- return new Promise((resolve3, reject) => {
2595
- try {
2596
- logInterceptor.install();
2597
- this.server = serve(
2598
- {
2599
- fetch: this.app.fetch,
2600
- hostname: this.deps.config.host,
2601
- port: this.deps.config.port
2602
- },
2603
- (info) => {
2604
- const url = `http://${info.address}:${info.port}`;
2605
- log3.info(`WebUI server running`);
2606
- log3.info(`URL: ${url}/auth/exchange?token=${this.authToken}`);
2607
- log3.info(`Token: ${maskToken(this.authToken)} (use Bearer header for API access)`);
2608
- resolve3();
2609
- }
2610
- );
2611
- } catch (error) {
2612
- logInterceptor.uninstall();
2613
- reject(error);
2614
- }
2615
- });
2616
- }
2617
- async stop() {
2618
- if (this.server) {
2619
- return new Promise((resolve3) => {
2620
- this.server?.close(() => {
2621
- logInterceptor.uninstall();
2622
- log3.info("WebUI server stopped");
2623
- resolve3();
2624
- });
2625
- });
2626
- }
2627
- }
2628
- getToken() {
2629
- return this.authToken;
2630
- }
2631
- };
2632
2273
  export {
2633
- WebUIServer
2274
+ logInterceptor,
2275
+ createStatusRoutes,
2276
+ createToolsRoutes,
2277
+ createLogsRoutes,
2278
+ createMemoryRoutes,
2279
+ createSoulRoutes,
2280
+ createPluginsRoutes,
2281
+ createMcpRoutes,
2282
+ createWorkspaceRoutes,
2283
+ createTasksRoutes,
2284
+ createConfigRoutes,
2285
+ createMarketplaceRoutes,
2286
+ createHooksRoutes,
2287
+ createTonProxyRoutes
2634
2288
  };
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createLogger
3
- } from "./chunk-RCMD3U65.js";
3
+ } from "./chunk-NQ6FZKCE.js";
4
4
 
5
5
  // src/config/providers.ts
6
6
  var PROVIDER_REGISTRY = {
@@ -30,12 +30,12 @@ var PROVIDER_REGISTRY = {
30
30
  },
31
31
  openai: {
32
32
  id: "openai",
33
- displayName: "OpenAI (GPT-4o)",
33
+ displayName: "OpenAI (GPT-5.4)",
34
34
  envVar: "OPENAI_API_KEY",
35
35
  keyPrefix: "sk-",
36
36
  keyHint: "sk-proj-...",
37
37
  consoleUrl: "https://platform.openai.com/api-keys",
38
- defaultModel: "gpt-4o",
38
+ defaultModel: "gpt-5.4",
39
39
  utilityModel: "gpt-4o-mini",
40
40
  toolLimit: 128,
41
41
  piAiProvider: "openai"
@@ -207,13 +207,17 @@ function validateApiKeyFormat(provider, key) {
207
207
  }
208
208
 
209
209
  // src/providers/claude-code-credentials.ts
210
- import { readFileSync, existsSync } from "fs";
210
+ import { readFileSync, writeFileSync, existsSync } from "fs";
211
211
  import { execSync } from "child_process";
212
212
  import { homedir } from "os";
213
213
  import { join } from "path";
214
214
  var log = createLogger("ClaudeCodeCreds");
215
+ var OAUTH_TOKEN_URL = "https://platform.claude.com/v1/oauth/token";
216
+ var OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
217
+ var OAUTH_SCOPES = "user:profile user:inference user:sessions:claude_code user:mcp_servers";
215
218
  var cachedToken = null;
216
219
  var cachedExpiresAt = 0;
220
+ var cachedRefreshToken = null;
217
221
  function getClaudeConfigDir() {
218
222
  return process.env.CLAUDE_CONFIG_DIR || join(homedir(), ".claude");
219
223
  }
@@ -261,7 +265,8 @@ function extractToken(creds) {
261
265
  }
262
266
  return {
263
267
  token: oauth.accessToken,
264
- expiresAt: oauth.expiresAt ?? 0
268
+ expiresAt: oauth.expiresAt ?? 0,
269
+ refreshToken: oauth.refreshToken
265
270
  };
266
271
  }
267
272
  function getClaudeCodeApiKey(fallbackKey) {
@@ -274,6 +279,7 @@ function getClaudeCodeApiKey(fallbackKey) {
274
279
  if (extracted) {
275
280
  cachedToken = extracted.token;
276
281
  cachedExpiresAt = extracted.expiresAt;
282
+ cachedRefreshToken = extracted.refreshToken ?? null;
277
283
  log.debug("Claude Code credentials loaded successfully");
278
284
  return cachedToken;
279
285
  }
@@ -284,20 +290,82 @@ function getClaudeCodeApiKey(fallbackKey) {
284
290
  }
285
291
  throw new Error("No Claude Code credentials found. Run 'claude login' or set api_key in config.");
286
292
  }
287
- function refreshClaudeCodeApiKey() {
293
+ async function performOAuthRefresh(refreshToken) {
294
+ try {
295
+ const res = await fetch(OAUTH_TOKEN_URL, {
296
+ method: "POST",
297
+ headers: { "Content-Type": "application/json" },
298
+ body: JSON.stringify({
299
+ grant_type: "refresh_token",
300
+ refresh_token: refreshToken,
301
+ client_id: OAUTH_CLIENT_ID,
302
+ scope: OAUTH_SCOPES
303
+ })
304
+ });
305
+ if (!res.ok) {
306
+ log.warn(`OAuth token refresh failed: ${res.status} ${res.statusText}`);
307
+ return null;
308
+ }
309
+ const data = await res.json();
310
+ if (!data.access_token || !data.expires_in) {
311
+ log.warn("OAuth token refresh: unexpected response shape");
312
+ return null;
313
+ }
314
+ const newExpiresAt = Date.now() + data.expires_in * 1e3;
315
+ const newRefreshToken = data.refresh_token ?? refreshToken;
316
+ const filePath = getCredentialsFilePath();
317
+ try {
318
+ const existing = existsSync(filePath) ? JSON.parse(readFileSync(filePath, "utf-8")) : {};
319
+ const updated = {
320
+ ...existing,
321
+ claudeAiOauth: {
322
+ ...existing.claudeAiOauth,
323
+ accessToken: data.access_token,
324
+ refreshToken: newRefreshToken,
325
+ expiresAt: newExpiresAt
326
+ }
327
+ };
328
+ writeFileSync(filePath, JSON.stringify(updated, null, 2), { mode: 384 });
329
+ } catch (e) {
330
+ log.warn({ err: e }, "Failed to persist refreshed OAuth credentials to disk");
331
+ }
332
+ cachedToken = data.access_token;
333
+ cachedExpiresAt = newExpiresAt;
334
+ cachedRefreshToken = newRefreshToken;
335
+ log.info("Claude Code OAuth token refreshed successfully");
336
+ return cachedToken;
337
+ } catch (e) {
338
+ log.warn({ err: e }, "OAuth token refresh request failed");
339
+ return null;
340
+ }
341
+ }
342
+ async function refreshClaudeCodeApiKey() {
288
343
  cachedToken = null;
289
344
  cachedExpiresAt = 0;
345
+ if (!cachedRefreshToken) {
346
+ const creds2 = readCredentials();
347
+ if (creds2) {
348
+ const extracted = extractToken(creds2);
349
+ cachedRefreshToken = extracted?.refreshToken ?? null;
350
+ }
351
+ }
352
+ if (cachedRefreshToken) {
353
+ const refreshed = await performOAuthRefresh(cachedRefreshToken);
354
+ if (refreshed) return refreshed;
355
+ log.warn("OAuth refresh failed, falling back to disk read");
356
+ }
290
357
  const creds = readCredentials();
291
358
  if (creds) {
292
359
  const extracted = extractToken(creds);
293
360
  if (extracted) {
294
361
  cachedToken = extracted.token;
295
362
  cachedExpiresAt = extracted.expiresAt;
363
+ cachedRefreshToken = extracted.refreshToken ?? null;
296
364
  log.info("Claude Code credentials refreshed from disk");
297
365
  return cachedToken;
298
366
  }
299
367
  }
300
- log.warn("Failed to refresh Claude Code credentials from disk");
368
+ log.warn("Failed to refresh Claude Code credentials");
301
369
  return null;
302
370
  }
303
371
  function isClaudeCodeTokenValid() {