ccremote
Version:
Claude Code Remote: approve prompts from Discord, auto-continue sessions after quota resets, and schedule quota windows around your workday.
108 lines (106 loc) • 4.12 kB
JavaScript
import { existsSync } from "node:fs";
import { resolve } from "node:path";
import { config } from "dotenv";
//#region src/core/config.ts
/**
* Load configuration from environment variables and .env files
*
* Priority (highest to lowest):
* 1. Environment variables (CCREMOTE_*)
* 2. Project .env file (./ccremote.env)
* 3. Project .env file (./.env)
* 4. Global .env file (~/.ccremote.env)
* 5. Default values
*/
function loadConfig() {
loadEnvFiles();
const discordBotToken = getEnvVar("CCREMOTE_DISCORD_BOT_TOKEN");
const discordOwnerId = getEnvVar("CCREMOTE_DISCORD_OWNER_ID");
const discordAuthorizedUsers = parseAuthorizedUsers(getEnvVar("CCREMOTE_DISCORD_AUTHORIZED_USERS") || "");
const monitoringInterval = Number.parseInt(getEnvVar("CCREMOTE_MONITORING_INTERVAL") || "2000", 10);
const maxRetries = Number.parseInt(getEnvVar("CCREMOTE_MAX_RETRIES") || "3", 10);
const autoRestart = getEnvVar("CCREMOTE_AUTO_RESTART") !== "false";
const discordHealthCheckInterval = Number.parseInt(getEnvVar("CCREMOTE_DISCORD_HEALTH_CHECK_INTERVAL") || "3600000", 10);
if (!discordBotToken) throw new Error("Missing required environment variable: CCREMOTE_DISCORD_BOT_TOKEN");
if (!discordOwnerId) throw new Error("Missing required environment variable: CCREMOTE_DISCORD_OWNER_ID");
return {
discordBotToken,
discordOwnerId,
discordAuthorizedUsers,
monitoringInterval,
maxRetries,
autoRestart,
discordHealthCheckInterval
};
}
function loadEnvFiles() {
if (process.env.NODE_ENV === "test" && process.env.SKIP_ENV_FILES) return;
const envFiles = [
resolve(process.cwd(), "ccremote.env"),
resolve(process.cwd(), ".env"),
resolve(process.env.HOME || "~", ".ccremote.env")
];
for (let i = envFiles.length - 1; i >= 0; i--) {
const envFile = envFiles[i];
if (existsSync(envFile)) {
config({
path: envFile,
override: false
});
console.info(`Loaded environment from: ${envFile}`);
}
}
}
function getEnvVar(key) {
return process.env[key];
}
function parseAuthorizedUsers(value) {
if (!value.trim()) return [];
return value.split(",").map((id) => id.trim()).filter((id) => id.length > 0);
}
/**
* Validate that the current configuration is valid
*/
function validateConfig(cfg) {
if (!cfg.discordBotToken) throw new Error("Discord bot token is required");
if (!cfg.discordOwnerId) throw new Error("Discord owner ID is required");
if (cfg.monitoringInterval < 1e3) console.warn("Warning: Monitoring interval less than 1000ms may cause performance issues");
}
if (import.meta.vitest) {
const { beforeEach, describe, it, expect, vi } = await import("./dist-bz1tWxsS.js");
describe("loadConfig", () => {
beforeEach(() => {
vi.resetModules();
for (const key in process.env) if (key.startsWith("CCREMOTE_")) delete process.env[key];
process.env.NODE_ENV = "test";
process.env.SKIP_ENV_FILES = "true";
});
it("should load configuration from CCREMOTE_ prefixed environment variables", () => {
process.env.CCREMOTE_DISCORD_BOT_TOKEN = "test_token";
process.env.CCREMOTE_DISCORD_OWNER_ID = "test_owner";
const config$1 = loadConfig();
expect(config$1.discordBotToken).toBe("test_token");
expect(config$1.discordOwnerId).toBe("test_owner");
});
it("should NOT fall back to non-prefixed environment variables", () => {
process.env.DISCORD_BOT_TOKEN = "should_not_use";
process.env.DISCORD_OWNER_ID = "should_not_use";
expect(() => loadConfig()).toThrow("Missing required environment variable: CCREMOTE_DISCORD_BOT_TOKEN");
});
it("should parse authorized users correctly", () => {
process.env.CCREMOTE_DISCORD_BOT_TOKEN = "test_token";
process.env.CCREMOTE_DISCORD_OWNER_ID = "test_owner";
process.env.CCREMOTE_DISCORD_AUTHORIZED_USERS = "user1,user2, user3 ";
expect(loadConfig().discordAuthorizedUsers).toEqual([
"user1",
"user2",
"user3"
]);
});
it("should throw error for missing required variables", () => {
expect(() => loadConfig()).toThrow("Missing required environment variable: CCREMOTE_DISCORD_BOT_TOKEN");
});
});
}
//#endregion
export { validateConfig as n, loadConfig as t };