| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- const crypto = require('crypto');
- const fs = require('fs');
- const path = require('path');
- const os = require('os');
- const DEFAULT_API_BASE = 'https://server.fmode.cn/api/qiwei';
- const CREDENTIALS_FILE = path.join(os.homedir(), '.claude', 'qiwei-credentials.json');
- function readJsonMaybe(filePath) {
- try {
- if (!filePath || !fs.existsSync(filePath)) return {};
- return JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, ''));
- } catch {
- return {};
- }
- }
- function firstNonEmpty(values) {
- return values.find(value => typeof value === 'string' && value.trim()) || '';
- }
- function readEnvFileMaybe(filePath) {
- try {
- if (!filePath || !fs.existsSync(filePath)) return {};
- const env = {};
- const content = fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '');
- for (const rawLine of content.split(/\r?\n/)) {
- const line = rawLine.trim();
- if (!line || line.startsWith('#')) continue;
- const match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
- if (!match) continue;
- let value = match[2].trim();
- if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
- value = value.slice(1, -1);
- }
- env[match[1]] = value;
- }
- return env;
- } catch {
- return {};
- }
- }
- function envCandidates() {
- const candidates = [];
- let current = process.cwd();
- while (current) {
- candidates.push(path.join(current, '.env.local'));
- candidates.push(path.join(current, '.env'));
- const parent = path.dirname(current);
- if (parent === current) break;
- current = parent;
- }
- candidates.push(path.resolve(__dirname, '..', '..', '..', '.env.local'));
- candidates.push(path.resolve(__dirname, '..', '..', '..', '.env'));
- return [...new Set(candidates)];
- }
- function readEnvFiles() {
- return envCandidates().reduce((merged, filePath) => {
- const next = readEnvFileMaybe(filePath);
- for (const [key, value] of Object.entries(next)) {
- if (!merged[key]) merged[key] = value;
- }
- return merged;
- }, {});
- }
- function readClaudeSettingsEnv() {
- const files = [
- path.join(os.homedir(), '.claude', 'settings.json'),
- path.join(os.homedir(), '.claude', 'settings.local.json'),
- path.join(process.cwd(), '.claude', 'settings.json'),
- path.join(process.cwd(), '.claude', 'settings.local.json')
- ];
- const merged = {};
- for (const filePath of files) {
- const json = readJsonMaybe(filePath);
- const env = json && typeof json.env === 'object' && json.env ? json.env : null;
- if (!env) continue;
- for (const [key, value] of Object.entries(env)) {
- if (!merged[key] && typeof value === 'string' && value.trim()) merged[key] = value;
- }
- }
- return merged;
- }
- function readFmodeConfig() {
- const files = [
- path.join(os.homedir(), '.fmode', 'config.json'),
- path.join(process.cwd(), '.fmode', 'config.json')
- ];
- const merged = {};
- for (const filePath of files) {
- const json = readJsonMaybe(filePath);
- for (const [key, value] of Object.entries(json || {})) {
- if (merged[key] === undefined) merged[key] = value;
- }
- }
- return merged;
- }
- function pickFmodeAnthropicToken(env) {
- const token = env && typeof env.ANTHROPIC_AUTH_TOKEN === 'string' ? env.ANTHROPIC_AUTH_TOKEN.trim() : '';
- if (!token || !/^sk-/i.test(token) || /^sk-ant-/i.test(token)) return '';
- const base = String((env && (env.ANTHROPIC_BASE_URL || env.ANTHROPIC_API_BASE)) || '').toLowerCase();
- if (base && !base.includes('fmode')) return '';
- return token;
- }
- function normalizeToken(value) {
- return String(value || '').trim().replace(/^Bearer\s+/i, '');
- }
- function readQiweiAuthToken(input = {}) {
- const fileEnv = readEnvFiles();
- const claudeEnv = readClaudeSettingsEnv();
- const fmodeConfig = readFmodeConfig();
- const token = firstNonEmpty([
- input.authToken,
- input.fmodeApiKey,
- input.fmodeApiToken,
- input.sessionToken,
- input.apiToken,
- input.token,
- fileEnv.QIWEI_AUTH_TOKEN,
- fileEnv.QIWE_AUTH_TOKEN,
- fileEnv.FMODE_API_KEY,
- fileEnv.FMODE_API_TOKEN,
- fileEnv.NEWAPI_TOKEN,
- fileEnv.VOC_TOKEN,
- fileEnv.VOC_SOCIAL_TOKEN,
- process.env.QIWEI_AUTH_TOKEN,
- process.env.QIWE_AUTH_TOKEN,
- process.env.FMODE_API_KEY,
- process.env.FMODE_API_TOKEN,
- process.env.NEWAPI_TOKEN,
- process.env.VOC_TOKEN,
- process.env.VOC_SOCIAL_TOKEN,
- fmodeConfig.newapiToken,
- fmodeConfig.newApiToken,
- fmodeConfig.fmodeApiKey,
- fmodeConfig.fmodeApiToken,
- claudeEnv.FMODE_API_KEY,
- claudeEnv.NEWAPI_TOKEN,
- pickFmodeAnthropicToken(process.env),
- pickFmodeAnthropicToken(claudeEnv)
- ]);
- return normalizeToken(token);
- }
- function readCredentialsFile() {
- return readJsonMaybe(CREDENTIALS_FILE);
- }
- function readQiweiUid(input = {}) {
- const fileEnv = readEnvFiles();
- const creds = readCredentialsFile();
- return firstNonEmpty([
- input.uid,
- input.qiweiUid,
- fileEnv.QIWEI_UID,
- fileEnv.QIWE_UID,
- process.env.QIWEI_UID,
- process.env.QIWE_UID,
- creds.uid
- ]);
- }
- function readQiweiApiBase(input = {}) {
- const fileEnv = readEnvFiles();
- const creds = readCredentialsFile();
- return String(firstNonEmpty([
- input.apiBase,
- input.baseUrl,
- fileEnv.QIWEI_API_BASE,
- fileEnv.QIWEI_RELAY_BASE_URL,
- fileEnv.QIWE_API_BASE,
- fileEnv.QIWE_RELAY_BASE_URL,
- process.env.QIWEI_API_BASE,
- process.env.QIWEI_RELAY_BASE_URL,
- process.env.QIWE_API_BASE,
- process.env.QIWE_RELAY_BASE_URL,
- creds.apiBase,
- DEFAULT_API_BASE
- ]) || DEFAULT_API_BASE).replace(/\/$/, '');
- }
- function saveQiweiClientConfig({ uid, apiBase } = {}) {
- const saved = [];
- try {
- const dir = path.dirname(CREDENTIALS_FILE);
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
- const current = readCredentialsFile();
- const next = { ...current };
- if (uid) next.uid = uid;
- if (apiBase) next.apiBase = apiBase;
- next.updatedAt = new Date().toISOString();
- fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(next, null, 2), 'utf8');
- saved.push(CREDENTIALS_FILE);
- } catch {
- // ignore, fall through to env file
- }
- try {
- const envPath = path.join(process.cwd(), '.env.local');
- const pairs = [];
- if (uid) pairs.push(['QIWEI_UID', uid]);
- if (apiBase) pairs.push(['QIWEI_API_BASE', apiBase]);
- if (pairs.length) {
- let content = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf8') : '';
- for (const [key, value] of pairs) {
- const line = `${key}=${value}`;
- const re = new RegExp(`^${key}\\s*=.*$`, 'm');
- content = re.test(content)
- ? content.replace(re, line)
- : `${content.replace(/\n*$/, '')}${content ? '\n' : ''}${line}\n`;
- }
- fs.writeFileSync(envPath, content, 'utf8');
- saved.push(envPath);
- }
- } catch {
- // ignore
- }
- return saved;
- }
- function ensureQiweiUid(input = {}) {
- const existing = readQiweiUid(input);
- if (existing) return existing;
- const uid = `qiwei-${crypto.randomUUID()}`;
- saveQiweiClientConfig({ uid, apiBase: readQiweiApiBase(input) });
- return uid;
- }
- function isConfigured(input = {}) {
- return Boolean(readQiweiAuthToken(input) && readQiweiUid(input));
- }
- module.exports = {
- DEFAULT_API_BASE,
- CREDENTIALS_FILE,
- readQiweiAuthToken,
- readQiweiUid,
- ensureQiweiUid,
- readQiweiApiBase,
- saveQiweiClientConfig,
- isConfigured,
- readEnvFiles,
- readClaudeSettingsEnv,
- readFmodeConfig,
- pickFmodeAnthropicToken
- };
|