mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 05:59:43 +02:00
feat(web): show Codex plan usage in header
This commit is contained in:
@@ -9,7 +9,9 @@
|
||||
|
||||
import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { createCliExecutableResolver, formatCliNotFoundMessage } from './cli-executable-resolver.js';
|
||||
import { parseCodexRateLimitsResponse, type StatusTelemetry } from '../usage-telemetry.js';
|
||||
|
||||
/** Common directories where the Codex CLI binary may be installed */
|
||||
const CODEX_SEARCH_DIRS = [
|
||||
@@ -21,7 +23,8 @@ const CODEX_SEARCH_DIRS = [
|
||||
join(homedir(), 'bin'), // User bin
|
||||
];
|
||||
|
||||
const codexResolver = createCliExecutableResolver({ binary: 'codex', searchDirs: CODEX_SEARCH_DIRS });
|
||||
const CODEX_BINARY = process.platform === 'win32' ? 'codex.exe' : 'codex';
|
||||
const codexResolver = createCliExecutableResolver({ binary: CODEX_BINARY, searchDirs: CODEX_SEARCH_DIRS });
|
||||
const CODEX_NOT_FOUND = 'Codex CLI not found. Install with: npm install -g @openai/codex';
|
||||
|
||||
/**
|
||||
@@ -35,6 +38,11 @@ export function resolveCodexDir(): string | null {
|
||||
return codexResolver.resolve()?.directory ?? null;
|
||||
}
|
||||
|
||||
/** Absolute Codex executable path, for direct app-server requests. */
|
||||
export function resolveCodexBinaryPath(): string | null {
|
||||
return codexResolver.resolve()?.binaryPath ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Codex CLI is available on the system.
|
||||
*/
|
||||
@@ -45,3 +53,80 @@ export function isCodexAvailable(): boolean {
|
||||
export function getCodexNotFoundMessage(): string {
|
||||
return formatCliNotFoundMessage(CODEX_NOT_FOUND, codexResolver.diagnostics());
|
||||
}
|
||||
|
||||
type CodexRateLimitsRequest = (binaryPath: string, clientVersion: string) => Promise<unknown>;
|
||||
|
||||
const APP_SERVER_TIMEOUT_MS = 10_000;
|
||||
const APP_SERVER_MAX_OUTPUT_BYTES = 256 * 1024;
|
||||
|
||||
function requestCodexRateLimits(binaryPath: string, clientVersion: string): Promise<unknown> {
|
||||
return new Promise((resolve) => {
|
||||
let settled = false;
|
||||
let initialized = false;
|
||||
let buffer = '';
|
||||
const child = spawn(binaryPath, ['app-server', '--stdio'], {
|
||||
stdio: ['pipe', 'pipe', 'ignore'],
|
||||
windowsHide: true,
|
||||
});
|
||||
const timeout = setTimeout(() => finish(null), APP_SERVER_TIMEOUT_MS);
|
||||
|
||||
const finish = (value: unknown): void => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
clearTimeout(timeout);
|
||||
child.stdin.end();
|
||||
child.kill();
|
||||
resolve(value);
|
||||
};
|
||||
const send = (message: unknown): void => {
|
||||
if (!settled && child.stdin.writable) child.stdin.write(`${JSON.stringify(message)}\n`);
|
||||
};
|
||||
const handleLine = (line: string): void => {
|
||||
if (!line.trim()) return;
|
||||
let message: { id?: number; result?: unknown; error?: unknown };
|
||||
try {
|
||||
message = JSON.parse(line) as { id?: number; result?: unknown; error?: unknown };
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (message.id === 1) {
|
||||
if (message.error) return finish(null);
|
||||
if (!initialized) {
|
||||
initialized = true;
|
||||
send({ method: 'account/rateLimits/read', id: 2 });
|
||||
}
|
||||
} else if (message.id === 2) {
|
||||
finish(message.error ? null : message.result);
|
||||
}
|
||||
};
|
||||
|
||||
child.on('error', () => finish(null));
|
||||
child.on('close', () => finish(null));
|
||||
child.stdin.on('error', () => finish(null));
|
||||
child.stdout.on('data', (chunk: Buffer) => {
|
||||
buffer += chunk.toString('utf8');
|
||||
if (Buffer.byteLength(buffer) > APP_SERVER_MAX_OUTPUT_BYTES) return finish(null);
|
||||
const lines = buffer.split(/\r?\n/);
|
||||
buffer = lines.pop() ?? '';
|
||||
for (const line of lines) handleLine(line);
|
||||
});
|
||||
|
||||
send({
|
||||
method: 'initialize',
|
||||
id: 1,
|
||||
params: {
|
||||
clientInfo: { name: 'codeman', title: 'Codeman', version: clientVersion },
|
||||
capabilities: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Read the signed-in host account's main Codex limits without exposing credentials. */
|
||||
export async function readCodexPlanUsage(
|
||||
binaryPath: string,
|
||||
clientVersion: string,
|
||||
request: CodexRateLimitsRequest = requestCodexRateLimits
|
||||
): Promise<StatusTelemetry | null> {
|
||||
return parseCodexRateLimitsResponse(await request(binaryPath, clientVersion));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user