chatccc 0.2.181 → 0.2.182

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/src/feishu-api.ts CHANGED
@@ -302,10 +302,10 @@ const AVATAR_DIR = resolvePath(PROJECT_ROOT, "images", "avatars");
302
302
  const AVATAR_BADGE_DIR = resolvePath(AVATAR_DIR, "badges");
303
303
  const AVATAR_COMBINATIONS_DIR = resolvePath(AVATAR_DIR, "combinations");
304
304
  const AVATAR_KEY_CACHE_FILE = resolvePath(USER_DATA_DIR, "state", "avatar-image-keys.json");
305
- const CODEX_AUTH_FILE = resolvePath(homedir(), ".codex", "auth.json");
306
- const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
307
- const CODEX_RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
308
- const CODEX_RESET_CONSUME_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits/consume";
305
+ const CODEX_AUTH_FILE = resolvePath(homedir(), ".codex", "auth.json");
306
+ const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
307
+ const CODEX_RESET_CREDITS_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits";
308
+ const CODEX_RESET_CONSUME_URL = "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits/consume";
309
309
  const AVATAR_SOURCES: Record<string, string> = {
310
310
  new: resolvePath(AVATAR_DIR, "status_new.png"),
311
311
  busy: resolvePath(AVATAR_DIR, "status_busy.png"),
@@ -322,31 +322,31 @@ const AVATAR_BADGE_MARGIN = 10;
322
322
  const CODEX_AVATAR_USAGE_STYLE_VERSION = "usage-ring-gray-consumed-v13";
323
323
  const CURSOR_AVATAR_USAGE_STYLE_VERSION = "usage-battery-v1";
324
324
 
325
- export interface CodexUsageBalance {
326
- usedPercent: number;
327
- remainingPercent: number;
328
- resetAtEpochSeconds: number | null;
329
- resetAfterSeconds: number | null;
330
- }
331
-
332
- export interface CodexRateLimitResetCredit {
333
- grantedAt: string | null;
334
- expiresAt: string;
335
- }
336
-
337
- export interface CodexUsageSummary {
338
- fiveHour: CodexUsageBalance;
339
- weekly: CodexUsageBalance | null;
340
- rateLimitResetCreditsAvailable: number | null;
341
- rateLimitResetCredits: CodexRateLimitResetCredit[] | null;
342
- }
343
-
344
- export type CodexResetConsumeCode = "reset" | "nothing_to_reset" | "no_credit" | "already_redeemed";
345
-
346
- export interface CodexResetConsumeResult {
347
- code: CodexResetConsumeCode;
348
- windowsReset: number;
349
- }
325
+ export interface CodexUsageBalance {
326
+ usedPercent: number;
327
+ remainingPercent: number;
328
+ resetAtEpochSeconds: number | null;
329
+ resetAfterSeconds: number | null;
330
+ }
331
+
332
+ export interface CodexRateLimitResetCredit {
333
+ grantedAt: string | null;
334
+ expiresAt: string;
335
+ }
336
+
337
+ export interface CodexUsageSummary {
338
+ fiveHour: CodexUsageBalance;
339
+ weekly: CodexUsageBalance | null;
340
+ rateLimitResetCreditsAvailable: number | null;
341
+ rateLimitResetCredits: CodexRateLimitResetCredit[] | null;
342
+ }
343
+
344
+ export type CodexResetConsumeCode = "reset" | "nothing_to_reset" | "no_credit" | "already_redeemed";
345
+
346
+ export interface CodexResetConsumeResult {
347
+ code: CodexResetConsumeCode;
348
+ windowsReset: number;
349
+ }
350
350
 
351
351
  const avatarKeyCache = new Map<string, string>();
352
352
  let avatarKeyCacheLoaded = false;
@@ -427,167 +427,167 @@ function usageBalanceFromWindow(raw: Record<string, unknown>, fieldName: string)
427
427
  };
428
428
  }
429
429
 
430
- function parseOptionalUsageWindow(rateLimit: Record<string, unknown>, keys: string[]): CodexUsageBalance | null {
430
+ function parseOptionalUsageWindow(rateLimit: Record<string, unknown>, keys: string[]): CodexUsageBalance | null {
431
431
  for (const key of keys) {
432
432
  const raw = rateLimit[key];
433
433
  if (!raw || typeof raw !== "object") continue;
434
434
  if ((raw as { used_percent?: unknown }).used_percent === undefined) continue;
435
435
  return usageBalanceFromWindow(raw as Record<string, unknown>, `rate_limit.${key}`);
436
436
  }
437
- return null;
438
- }
439
-
440
- function parseRateLimitResetCredits(data: Record<string, unknown>): number | null {
441
- const raw = data.rate_limit_reset_credits;
442
- if (!raw || typeof raw !== "object") return null;
443
- const availableCount = Number((raw as { available_count?: unknown }).available_count);
444
- if (!Number.isFinite(availableCount)) return null;
445
- return Math.max(0, Math.trunc(availableCount));
446
- }
447
-
448
- interface CodexAuth {
449
- accessToken: string;
450
- accountId?: string;
451
- }
452
-
453
- async function getCodexAuth(): Promise<CodexAuth | null> {
454
- try {
455
- const raw = await readFile(CODEX_AUTH_FILE, "utf-8");
456
- const parsed = JSON.parse(raw) as { tokens?: { access_token?: unknown; account_id?: unknown } };
457
- const token = parsed.tokens?.access_token;
458
- if (typeof token !== "string" || !token.trim()) return null;
459
- const accountId = parsed.tokens?.account_id;
460
- return {
461
- accessToken: token,
462
- accountId: typeof accountId === "string" && accountId.trim() ? accountId : undefined,
463
- };
464
- } catch {
465
- return null;
466
- }
467
- }
468
-
469
- async function getCodexAccessToken(): Promise<string | null> {
470
- return (await getCodexAuth())?.accessToken ?? null;
471
- }
472
-
473
- function codexAuthHeaders(auth: CodexAuth): Record<string, string> {
474
- const headers: Record<string, string> = {
475
- Authorization: `Bearer ${auth.accessToken}`,
476
- "OpenAI-Beta": "codex-1",
477
- originator: "Codex Desktop",
478
- };
479
- if (auth.accountId) headers["ChatGPT-Account-ID"] = auth.accountId;
480
- return headers;
481
- }
482
-
483
- function parseCodexResetCreditDetails(data: Record<string, unknown>): {
484
- availableCount: number | null;
485
- availableCredits: CodexRateLimitResetCredit[];
486
- } {
487
- const availableCount = Number(data.available_count);
488
- const credits = Array.isArray(data.credits) ? data.credits : [];
489
- return {
490
- availableCount: Number.isFinite(availableCount) ? Math.max(0, Math.trunc(availableCount)) : null,
491
- availableCredits: credits.flatMap((raw) => {
492
- if (!raw || typeof raw !== "object") return [];
493
- const credit = raw as { status?: unknown; granted_at?: unknown; expires_at?: unknown };
494
- if (credit.status !== "available") return [];
495
- if (typeof credit.expires_at !== "string" || !credit.expires_at.trim()) return [];
496
- return [{
497
- grantedAt: typeof credit.granted_at === "string" && credit.granted_at.trim() ? credit.granted_at : null,
498
- expiresAt: credit.expires_at,
499
- }];
500
- }),
501
- };
502
- }
503
-
504
- async function fetchCodexRateLimitResetCredits(auth: CodexAuth): Promise<{
505
- availableCount: number | null;
506
- availableCredits: CodexRateLimitResetCredit[];
507
- } | null> {
508
- try {
509
- const resp = await fetch(CODEX_RESET_CREDITS_URL, {
510
- headers: codexAuthHeaders(auth),
511
- });
512
- const text = await resp.text();
513
- if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${text.slice(0, 160)}`);
514
- return parseCodexResetCreditDetails(JSON.parse(text) as Record<string, unknown>);
515
- } catch (err) {
516
- console.warn(`[Codex] reset credits lookup failed: ${(err as Error).message}`);
517
- return null;
518
- }
519
- }
520
-
521
- export async function getCodexUsageSummary(): Promise<CodexUsageSummary> {
522
- const auth = await getCodexAuth();
523
- if (!auth) throw new Error("missing ~/.codex/auth.json access token");
524
-
525
- const resetCreditsPromise = fetchCodexRateLimitResetCredits(auth);
526
-
527
- const resp = await fetch(CODEX_USAGE_URL, {
528
- headers: { Authorization: `Bearer ${auth.accessToken}` },
529
- });
530
- const text = await resp.text();
531
- if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${text.slice(0, 160)}`);
532
-
533
- const data = JSON.parse(text) as {
534
- rate_limit?: Record<string, unknown>;
535
- rate_limit_reset_credits?: unknown;
536
- };
537
- const rateLimit = data.rate_limit;
538
- if (!rateLimit || typeof rateLimit !== "object") throw new Error("missing rate_limit");
539
-
540
- const fiveHour = parseOptionalUsageWindow(rateLimit, ["primary_window"]);
541
- if (!fiveHour) throw new Error("missing rate_limit.primary_window.used_percent");
542
- const resetCredits = await resetCreditsPromise;
543
-
544
- return {
545
- fiveHour,
546
- weekly: parseOptionalUsageWindow(rateLimit, [
547
- "secondary_window",
548
- "weekly_window",
549
- "week_window",
550
- "long_window",
551
- ]),
552
- rateLimitResetCreditsAvailable: resetCredits?.availableCount ?? parseRateLimitResetCredits(data),
553
- rateLimitResetCredits: resetCredits?.availableCredits ?? null,
554
- };
555
- }
556
-
557
- export async function consumeCodexRateLimitResetCredit(redeemRequestId: string): Promise<CodexResetConsumeResult> {
558
- if (!redeemRequestId.trim()) throw new Error("missing redeem_request_id");
559
- const token = await getCodexAccessToken();
560
- if (!token) throw new Error("missing ~/.codex/auth.json access token");
561
-
562
- const resp = await fetch(CODEX_RESET_CONSUME_URL, {
563
- method: "POST",
564
- headers: {
565
- Authorization: `Bearer ${token}`,
566
- "Content-Type": "application/json",
567
- },
568
- body: JSON.stringify({ redeem_request_id: redeemRequestId }),
569
- });
570
- const text = await resp.text();
571
- if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${text.slice(0, 160)}`);
572
-
573
- const data = JSON.parse(text) as { code?: unknown; windows_reset?: unknown };
574
- const code = data.code;
575
- if (
576
- code !== "reset"
577
- && code !== "nothing_to_reset"
578
- && code !== "no_credit"
579
- && code !== "already_redeemed"
580
- ) {
581
- throw new Error("missing or unknown reset result code");
582
- }
583
- const windowsReset = Number(data.windows_reset);
584
- return {
585
- code,
586
- windowsReset: Number.isFinite(windowsReset) ? Math.max(0, Math.trunc(windowsReset)) : 0,
587
- };
588
- }
589
-
590
- async function fetchCodexAvatarUsage(): Promise<CodexUsageSummary | null> {
437
+ return null;
438
+ }
439
+
440
+ function parseRateLimitResetCredits(data: Record<string, unknown>): number | null {
441
+ const raw = data.rate_limit_reset_credits;
442
+ if (!raw || typeof raw !== "object") return null;
443
+ const availableCount = Number((raw as { available_count?: unknown }).available_count);
444
+ if (!Number.isFinite(availableCount)) return null;
445
+ return Math.max(0, Math.trunc(availableCount));
446
+ }
447
+
448
+ interface CodexAuth {
449
+ accessToken: string;
450
+ accountId?: string;
451
+ }
452
+
453
+ async function getCodexAuth(): Promise<CodexAuth | null> {
454
+ try {
455
+ const raw = await readFile(CODEX_AUTH_FILE, "utf-8");
456
+ const parsed = JSON.parse(raw) as { tokens?: { access_token?: unknown; account_id?: unknown } };
457
+ const token = parsed.tokens?.access_token;
458
+ if (typeof token !== "string" || !token.trim()) return null;
459
+ const accountId = parsed.tokens?.account_id;
460
+ return {
461
+ accessToken: token,
462
+ accountId: typeof accountId === "string" && accountId.trim() ? accountId : undefined,
463
+ };
464
+ } catch {
465
+ return null;
466
+ }
467
+ }
468
+
469
+ async function getCodexAccessToken(): Promise<string | null> {
470
+ return (await getCodexAuth())?.accessToken ?? null;
471
+ }
472
+
473
+ function codexAuthHeaders(auth: CodexAuth): Record<string, string> {
474
+ const headers: Record<string, string> = {
475
+ Authorization: `Bearer ${auth.accessToken}`,
476
+ "OpenAI-Beta": "codex-1",
477
+ originator: "Codex Desktop",
478
+ };
479
+ if (auth.accountId) headers["ChatGPT-Account-ID"] = auth.accountId;
480
+ return headers;
481
+ }
482
+
483
+ function parseCodexResetCreditDetails(data: Record<string, unknown>): {
484
+ availableCount: number | null;
485
+ availableCredits: CodexRateLimitResetCredit[];
486
+ } {
487
+ const availableCount = Number(data.available_count);
488
+ const credits = Array.isArray(data.credits) ? data.credits : [];
489
+ return {
490
+ availableCount: Number.isFinite(availableCount) ? Math.max(0, Math.trunc(availableCount)) : null,
491
+ availableCredits: credits.flatMap((raw) => {
492
+ if (!raw || typeof raw !== "object") return [];
493
+ const credit = raw as { status?: unknown; granted_at?: unknown; expires_at?: unknown };
494
+ if (credit.status !== "available") return [];
495
+ if (typeof credit.expires_at !== "string" || !credit.expires_at.trim()) return [];
496
+ return [{
497
+ grantedAt: typeof credit.granted_at === "string" && credit.granted_at.trim() ? credit.granted_at : null,
498
+ expiresAt: credit.expires_at,
499
+ }];
500
+ }),
501
+ };
502
+ }
503
+
504
+ async function fetchCodexRateLimitResetCredits(auth: CodexAuth): Promise<{
505
+ availableCount: number | null;
506
+ availableCredits: CodexRateLimitResetCredit[];
507
+ } | null> {
508
+ try {
509
+ const resp = await fetch(CODEX_RESET_CREDITS_URL, {
510
+ headers: codexAuthHeaders(auth),
511
+ });
512
+ const text = await resp.text();
513
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${text.slice(0, 160)}`);
514
+ return parseCodexResetCreditDetails(JSON.parse(text) as Record<string, unknown>);
515
+ } catch (err) {
516
+ console.warn(`[Codex] reset credits lookup failed: ${(err as Error).message}`);
517
+ return null;
518
+ }
519
+ }
520
+
521
+ export async function getCodexUsageSummary(): Promise<CodexUsageSummary> {
522
+ const auth = await getCodexAuth();
523
+ if (!auth) throw new Error("missing ~/.codex/auth.json access token");
524
+
525
+ const resetCreditsPromise = fetchCodexRateLimitResetCredits(auth);
526
+
527
+ const resp = await fetch(CODEX_USAGE_URL, {
528
+ headers: { Authorization: `Bearer ${auth.accessToken}` },
529
+ });
530
+ const text = await resp.text();
531
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${text.slice(0, 160)}`);
532
+
533
+ const data = JSON.parse(text) as {
534
+ rate_limit?: Record<string, unknown>;
535
+ rate_limit_reset_credits?: unknown;
536
+ };
537
+ const rateLimit = data.rate_limit;
538
+ if (!rateLimit || typeof rateLimit !== "object") throw new Error("missing rate_limit");
539
+
540
+ const fiveHour = parseOptionalUsageWindow(rateLimit, ["primary_window"]);
541
+ if (!fiveHour) throw new Error("missing rate_limit.primary_window.used_percent");
542
+ const resetCredits = await resetCreditsPromise;
543
+
544
+ return {
545
+ fiveHour,
546
+ weekly: parseOptionalUsageWindow(rateLimit, [
547
+ "secondary_window",
548
+ "weekly_window",
549
+ "week_window",
550
+ "long_window",
551
+ ]),
552
+ rateLimitResetCreditsAvailable: resetCredits?.availableCount ?? parseRateLimitResetCredits(data),
553
+ rateLimitResetCredits: resetCredits?.availableCredits ?? null,
554
+ };
555
+ }
556
+
557
+ export async function consumeCodexRateLimitResetCredit(redeemRequestId: string): Promise<CodexResetConsumeResult> {
558
+ if (!redeemRequestId.trim()) throw new Error("missing redeem_request_id");
559
+ const token = await getCodexAccessToken();
560
+ if (!token) throw new Error("missing ~/.codex/auth.json access token");
561
+
562
+ const resp = await fetch(CODEX_RESET_CONSUME_URL, {
563
+ method: "POST",
564
+ headers: {
565
+ Authorization: `Bearer ${token}`,
566
+ "Content-Type": "application/json",
567
+ },
568
+ body: JSON.stringify({ redeem_request_id: redeemRequestId }),
569
+ });
570
+ const text = await resp.text();
571
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${text.slice(0, 160)}`);
572
+
573
+ const data = JSON.parse(text) as { code?: unknown; windows_reset?: unknown };
574
+ const code = data.code;
575
+ if (
576
+ code !== "reset"
577
+ && code !== "nothing_to_reset"
578
+ && code !== "no_credit"
579
+ && code !== "already_redeemed"
580
+ ) {
581
+ throw new Error("missing or unknown reset result code");
582
+ }
583
+ const windowsReset = Number(data.windows_reset);
584
+ return {
585
+ code,
586
+ windowsReset: Number.isFinite(windowsReset) ? Math.max(0, Math.trunc(windowsReset)) : 0,
587
+ };
588
+ }
589
+
590
+ async function fetchCodexAvatarUsage(): Promise<CodexUsageSummary | null> {
591
591
  try {
592
592
  const summary = await getCodexUsageSummary();
593
593
  if (!summary.weekly) throw new Error("missing weekly usage window");
@@ -29,10 +29,10 @@ export interface FeishuPlatform {
29
29
  updateChatInfo: typeof realApi.updateChatInfo;
30
30
  getChatInfo: typeof realApi.getChatInfo;
31
31
  disbandChat: typeof realApi.disbandChat;
32
- setChatAvatar: typeof realApi.setChatAvatar;
33
- getCodexUsageSummary: typeof realApi.getCodexUsageSummary;
34
- consumeCodexRateLimitResetCredit: typeof realApi.consumeCodexRateLimitResetCredit;
35
- getOrDownloadImage: typeof realApi.getOrDownloadImage;
32
+ setChatAvatar: typeof realApi.setChatAvatar;
33
+ getCodexUsageSummary: typeof realApi.getCodexUsageSummary;
34
+ consumeCodexRateLimitResetCredit: typeof realApi.consumeCodexRateLimitResetCredit;
35
+ getOrDownloadImage: typeof realApi.getOrDownloadImage;
36
36
  verifyAllPermissions: typeof realApi.verifyAllPermissions;
37
37
  reportPermissionResults: typeof realApi.reportPermissionResults;
38
38
  extractSessionInfo: typeof realApi.extractSessionInfo;
@@ -110,21 +110,21 @@ export function disbandChat(...args: Parameters<typeof realApi.disbandChat>): Re
110
110
  return _impl.disbandChat(...args);
111
111
  }
112
112
 
113
- export function setChatAvatar(...args: Parameters<typeof realApi.setChatAvatar>): ReturnType<typeof realApi.setChatAvatar> {
114
- return _impl.setChatAvatar(...args);
115
- }
116
-
117
- export function getCodexUsageSummary(...args: Parameters<typeof realApi.getCodexUsageSummary>): ReturnType<typeof realApi.getCodexUsageSummary> {
118
- return _impl.getCodexUsageSummary(...args);
119
- }
120
-
121
- export function consumeCodexRateLimitResetCredit(...args: Parameters<typeof realApi.consumeCodexRateLimitResetCredit>): ReturnType<typeof realApi.consumeCodexRateLimitResetCredit> {
122
- return _impl.consumeCodexRateLimitResetCredit(...args);
123
- }
124
-
125
- export function getOrDownloadImage(...args: Parameters<typeof realApi.getOrDownloadImage>): ReturnType<typeof realApi.getOrDownloadImage> {
126
- return _impl.getOrDownloadImage(...args);
127
- }
113
+ export function setChatAvatar(...args: Parameters<typeof realApi.setChatAvatar>): ReturnType<typeof realApi.setChatAvatar> {
114
+ return _impl.setChatAvatar(...args);
115
+ }
116
+
117
+ export function getCodexUsageSummary(...args: Parameters<typeof realApi.getCodexUsageSummary>): ReturnType<typeof realApi.getCodexUsageSummary> {
118
+ return _impl.getCodexUsageSummary(...args);
119
+ }
120
+
121
+ export function consumeCodexRateLimitResetCredit(...args: Parameters<typeof realApi.consumeCodexRateLimitResetCredit>): ReturnType<typeof realApi.consumeCodexRateLimitResetCredit> {
122
+ return _impl.consumeCodexRateLimitResetCredit(...args);
123
+ }
124
+
125
+ export function getOrDownloadImage(...args: Parameters<typeof realApi.getOrDownloadImage>): ReturnType<typeof realApi.getOrDownloadImage> {
126
+ return _impl.getOrDownloadImage(...args);
127
+ }
128
128
 
129
129
  export function verifyAllPermissions(...args: Parameters<typeof realApi.verifyAllPermissions>): ReturnType<typeof realApi.verifyAllPermissions> {
130
130
  return _impl.verifyAllPermissions(...args);
@@ -156,4 +156,4 @@ export function sendRestartCard(...args: Parameters<typeof realApi.sendRestartCa
156
156
 
157
157
  export function getMergeForwardMessages(...args: Parameters<typeof realApi.getMergeForwardMessages>): ReturnType<typeof realApi.getMergeForwardMessages> {
158
158
  return _impl.getMergeForwardMessages(...args);
159
- }
159
+ }
package/src/index.ts CHANGED
@@ -469,8 +469,10 @@ async function startBotServiceCore(): Promise<void> {
469
469
  const handledCodexReset = await handleCodexResetCardAction(data, {
470
470
  getTenantAccessToken,
471
471
  sendRawCard,
472
+ sendTextReply,
472
473
  sendCardReply,
473
474
  updateCardMessage,
475
+ recallMessage,
474
476
  consumeCodexRateLimitResetCredit,
475
477
  });
476
478
  if (handledCodexReset) return;