feat(channels): auto-enable official Claude channels

This commit is contained in:
Tam Nhu Tran
2026-03-25 16:31:55 -04:00
parent 0e2f47802b
commit a97fc42b10
18 changed files with 2653 additions and 337 deletions
+127 -2
View File
@@ -1,7 +1,18 @@
import * as fs from 'fs';
import { execSync } from 'child_process';
import { execFileSync, execSync } from 'child_process';
import { expandPath } from './helpers';
import { ClaudeCliInfo } from '../types';
import type { ClaudeCliInfo } from '../types';
import { escapeShellArg, stripAnthropicEnv, stripClaudeCodeEnv } from './shell-executor';
export interface ClaudeAuthStatus {
loggedIn: boolean;
authMethod?: string | null;
apiProvider?: string | null;
email?: string | null;
orgId?: string | null;
orgName?: string | null;
subscriptionType?: string | null;
}
/**
* Windows installation paths for Claude CLI
@@ -127,6 +138,120 @@ export function getClaudeCliInfo(): ClaudeCliInfo | null {
};
}
function runClaudeCliCommand(args: string[], envOverrides?: NodeJS.ProcessEnv): string | null {
const cliInfo = getClaudeCliInfo();
if (!cliInfo) {
return null;
}
const env = stripClaudeCodeEnv(stripAnthropicEnv({ ...process.env, ...envOverrides }));
const { path: claudePath, needsShell } = cliInfo;
const isWindows = process.platform === 'win32';
const isPowerShellScript = isWindows && /\.ps1$/i.test(claudePath);
try {
if (isPowerShellScript) {
return execFileSync(
'powershell.exe',
['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', claudePath, ...args],
{
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 5000,
env,
}
).trim();
}
if (needsShell) {
return execSync([claudePath, ...args].map(escapeShellArg).join(' '), {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 5000,
shell: process.env.ComSpec || 'cmd.exe',
env,
}).trim();
}
return execFileSync(claudePath, args, {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
timeout: 5000,
env,
}).trim();
} catch (error) {
if (typeof error === 'object' && error !== null && 'stdout' in error) {
const stdout = (error as { stdout?: string | Buffer | null }).stdout;
if (typeof stdout === 'string') {
const trimmed = stdout.trim();
return trimmed.length > 0 ? trimmed : null;
}
if (Buffer.isBuffer(stdout)) {
const trimmed = stdout.toString('utf8').trim();
return trimmed.length > 0 ? trimmed : null;
}
}
return null;
}
}
export function getClaudeCliVersion(): string | null {
const output = runClaudeCliCommand(['--version']);
const versionMatch = output?.match(/(\d+\.\d+\.\d+)/);
return versionMatch ? versionMatch[1] : null;
}
export function compareClaudeCliVersions(left: string, right: string): number {
const leftParts = left.split('.').map((value) => Number.parseInt(value, 10) || 0);
const rightParts = right.split('.').map((value) => Number.parseInt(value, 10) || 0);
const maxLength = Math.max(leftParts.length, rightParts.length);
for (let index = 0; index < maxLength; index += 1) {
const leftValue = leftParts[index] ?? 0;
const rightValue = rightParts[index] ?? 0;
if (leftValue !== rightValue) {
return leftValue > rightValue ? 1 : -1;
}
}
return 0;
}
export function isClaudeCliVersionAtLeast(
currentVersion: string | null,
minimumVersion: string
): boolean {
return currentVersion !== null && compareClaudeCliVersions(currentVersion, minimumVersion) >= 0;
}
export function getClaudeAuthStatus(envOverrides?: NodeJS.ProcessEnv): ClaudeAuthStatus | null {
const output = runClaudeCliCommand(['auth', 'status'], envOverrides);
if (!output) {
return null;
}
try {
const parsed = JSON.parse(output) as Partial<ClaudeAuthStatus>;
if (typeof parsed.loggedIn !== 'boolean') {
return null;
}
return {
loggedIn: parsed.loggedIn,
authMethod: parsed.authMethod ?? null,
apiProvider: parsed.apiProvider ?? null,
email: parsed.email ?? null,
orgId: parsed.orgId ?? null,
orgName: parsed.orgName ?? null,
subscriptionType: parsed.subscriptionType ?? null,
};
} catch {
return null;
}
}
/**
* Show Claude not found error
*/