consortium
Version:
Remote control and session sharing CLI for AI coding agents
410 lines (404 loc) • 14.7 kB
JavaScript
;
var promises = require('node:fs/promises');
var fs = require('node:fs');
var api = require('./types-XjAAlKci.cjs');
var z = require('zod');
require('axios');
require('chalk');
require('fs');
require('node:os');
require('node:path');
require('node:events');
require('socket.io-client');
require('node:crypto');
require('tweetnacl');
require('child_process');
require('util');
require('fs/promises');
require('crypto');
require('path');
require('url');
require('os');
require('expo-server-sdk');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
Object.keys(e).forEach(function (k) {
if (k !== 'default') {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
}
n.default = e;
return Object.freeze(n);
}
var z__namespace = /*#__PURE__*/_interopNamespaceDefault(z);
const AnthropicConfigSchema = z__namespace.object({
baseUrl: z__namespace.string().url().optional(),
authToken: z__namespace.string().optional(),
model: z__namespace.string().optional()
});
const OpenAIConfigSchema = z__namespace.object({
apiKey: z__namespace.string().optional(),
baseUrl: z__namespace.string().url().optional(),
model: z__namespace.string().optional()
});
const AzureOpenAIConfigSchema = z__namespace.object({
apiKey: z__namespace.string().optional(),
endpoint: z__namespace.string().url().optional(),
apiVersion: z__namespace.string().optional(),
deploymentName: z__namespace.string().optional()
});
const TogetherAIConfigSchema = z__namespace.object({
apiKey: z__namespace.string().optional(),
model: z__namespace.string().optional()
});
const TmuxConfigSchema = z__namespace.object({
sessionName: z__namespace.string().optional(),
tmpDir: z__namespace.string().optional(),
updateEnvironment: z__namespace.boolean().optional()
});
const EnvironmentVariableSchema = z__namespace.object({
name: z__namespace.string().regex(/^[A-Z_][A-Z0-9_]*$/, "Invalid environment variable name"),
value: z__namespace.string()
});
const ProfileCompatibilitySchema = z__namespace.object({
claude: z__namespace.boolean().default(true),
codex: z__namespace.boolean().default(true),
gemini: z__namespace.boolean().default(true)
});
const AIBackendProfileSchema = z__namespace.object({
id: z__namespace.string().uuid(),
name: z__namespace.string().min(1).max(100),
description: z__namespace.string().max(500).optional(),
// Agent-specific configurations
anthropicConfig: AnthropicConfigSchema.optional(),
openaiConfig: OpenAIConfigSchema.optional(),
azureOpenAIConfig: AzureOpenAIConfigSchema.optional(),
togetherAIConfig: TogetherAIConfigSchema.optional(),
// Tmux configuration
tmuxConfig: TmuxConfigSchema.optional(),
// Environment variables (validated)
environmentVariables: z__namespace.array(EnvironmentVariableSchema).default([]),
// Default session type for this profile
defaultSessionType: z__namespace.enum(["simple", "worktree"]).optional(),
// Default permission mode for this profile (supports both Claude and Codex modes)
defaultPermissionMode: z__namespace.enum([
"default",
"acceptEdits",
"bypassPermissions",
"plan",
// Claude modes
"read-only",
"safe-yolo",
"yolo"
// Codex modes
]).optional(),
// Default model mode for this profile
defaultModelMode: z__namespace.string().optional(),
// Compatibility metadata
compatibility: ProfileCompatibilitySchema.default({ claude: true, codex: true, gemini: true }),
// Built-in profile indicator
isBuiltIn: z__namespace.boolean().default(false),
// Metadata
createdAt: z__namespace.number().default(() => Date.now()),
updatedAt: z__namespace.number().default(() => Date.now()),
version: z__namespace.string().default("1.0.0")
});
function validateProfileForAgent(profile, agent) {
return profile.compatibility[agent];
}
function getProfileEnvironmentVariables(profile) {
const envVars = {};
profile.environmentVariables.forEach((envVar) => {
envVars[envVar.name] = envVar.value;
});
if (profile.anthropicConfig) {
if (profile.anthropicConfig.baseUrl) envVars.ANTHROPIC_BASE_URL = profile.anthropicConfig.baseUrl;
if (profile.anthropicConfig.authToken) envVars.ANTHROPIC_AUTH_TOKEN = profile.anthropicConfig.authToken;
if (profile.anthropicConfig.model) envVars.ANTHROPIC_MODEL = profile.anthropicConfig.model;
}
if (profile.openaiConfig) {
if (profile.openaiConfig.apiKey) envVars.OPENAI_API_KEY = profile.openaiConfig.apiKey;
if (profile.openaiConfig.baseUrl) envVars.OPENAI_BASE_URL = profile.openaiConfig.baseUrl;
if (profile.openaiConfig.model) envVars.OPENAI_MODEL = profile.openaiConfig.model;
}
if (profile.azureOpenAIConfig) {
if (profile.azureOpenAIConfig.apiKey) envVars.AZURE_OPENAI_API_KEY = profile.azureOpenAIConfig.apiKey;
if (profile.azureOpenAIConfig.endpoint) envVars.AZURE_OPENAI_ENDPOINT = profile.azureOpenAIConfig.endpoint;
if (profile.azureOpenAIConfig.apiVersion) envVars.AZURE_OPENAI_API_VERSION = profile.azureOpenAIConfig.apiVersion;
if (profile.azureOpenAIConfig.deploymentName) envVars.AZURE_OPENAI_DEPLOYMENT_NAME = profile.azureOpenAIConfig.deploymentName;
}
if (profile.togetherAIConfig) {
if (profile.togetherAIConfig.apiKey) envVars.TOGETHER_API_KEY = profile.togetherAIConfig.apiKey;
if (profile.togetherAIConfig.model) envVars.TOGETHER_MODEL = profile.togetherAIConfig.model;
}
if (profile.tmuxConfig) {
if (profile.tmuxConfig.sessionName !== void 0) envVars.TMUX_SESSION_NAME = profile.tmuxConfig.sessionName;
if (profile.tmuxConfig.tmpDir) envVars.TMUX_TMPDIR = profile.tmuxConfig.tmpDir;
if (profile.tmuxConfig.updateEnvironment !== void 0) {
envVars.TMUX_UPDATE_ENVIRONMENT = profile.tmuxConfig.updateEnvironment.toString();
}
}
return envVars;
}
const SUPPORTED_SCHEMA_VERSION = 2;
const defaultSettings = {
schemaVersion: SUPPORTED_SCHEMA_VERSION,
onboardingCompleted: false,
profiles: [],
localEnvironmentVariables: {}
};
function migrateSettings(raw, fromVersion) {
let migrated = { ...raw };
if (fromVersion < 2) {
if (!migrated.profiles) {
migrated.profiles = [];
}
if (!migrated.localEnvironmentVariables) {
migrated.localEnvironmentVariables = {};
}
migrated.schemaVersion = 2;
}
return migrated;
}
async function readSettings() {
if (!fs.existsSync(api.configuration.settingsFile)) {
return { ...defaultSettings };
}
try {
const content = await promises.readFile(api.configuration.settingsFile, "utf8");
const raw = JSON.parse(content);
const schemaVersion = raw.schemaVersion ?? 1;
if (schemaVersion > SUPPORTED_SCHEMA_VERSION) {
api.logger.warn(
`\u26A0\uFE0F Settings schema v${schemaVersion} > supported v${SUPPORTED_SCHEMA_VERSION}. Update consortium-cli for full functionality.`
);
}
const migrated = migrateSettings(raw, schemaVersion);
if (migrated.profiles && Array.isArray(migrated.profiles)) {
const validProfiles = [];
for (const profile of migrated.profiles) {
try {
const validated = AIBackendProfileSchema.parse(profile);
validProfiles.push(validated);
} catch (error) {
api.logger.warn(
`\u26A0\uFE0F Invalid profile "${profile?.name || profile?.id || "unknown"}" - skipping. Error: ${error.message}`
);
}
}
migrated.profiles = validProfiles;
}
return { ...defaultSettings, ...migrated };
} catch (error) {
api.logger.warn(`Failed to read settings: ${error.message}`);
return { ...defaultSettings };
}
}
async function updateSettings(updater) {
const LOCK_RETRY_INTERVAL_MS = 100;
const MAX_LOCK_ATTEMPTS = 50;
const STALE_LOCK_TIMEOUT_MS = 1e4;
const lockFile = api.configuration.settingsFile + ".lock";
const tmpFile = api.configuration.settingsFile + ".tmp";
let fileHandle;
let attempts = 0;
while (attempts < MAX_LOCK_ATTEMPTS) {
try {
fileHandle = await promises.open(lockFile, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY);
break;
} catch (err) {
if (err.code === "EEXIST") {
attempts++;
await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_INTERVAL_MS));
try {
const stats = await promises.stat(lockFile);
if (Date.now() - stats.mtimeMs > STALE_LOCK_TIMEOUT_MS) {
await promises.unlink(lockFile).catch(() => {
});
}
} catch {
}
} else {
throw err;
}
}
}
if (!fileHandle) {
throw new Error(`Failed to acquire settings lock after ${MAX_LOCK_ATTEMPTS * LOCK_RETRY_INTERVAL_MS / 1e3} seconds`);
}
try {
const current = await readSettings() || { ...defaultSettings };
const updated = await updater(current);
if (!fs.existsSync(api.configuration.consortiumHomeDir)) {
await promises.mkdir(api.configuration.consortiumHomeDir, { recursive: true });
}
await promises.writeFile(tmpFile, JSON.stringify(updated, null, 2));
await promises.rename(tmpFile, api.configuration.settingsFile);
return updated;
} finally {
await fileHandle.close();
await promises.unlink(lockFile).catch(() => {
});
}
}
const credentialsSchema = z__namespace.object({
token: z__namespace.string(),
secret: z__namespace.string().base64().nullish(),
// Legacy
encryption: z__namespace.object({
publicKey: z__namespace.string().base64(),
machineKey: z__namespace.string().base64()
}).nullish()
});
async function readCredentials() {
if (!fs.existsSync(api.configuration.privateKeyFile)) {
return null;
}
try {
const keyBase64 = await promises.readFile(api.configuration.privateKeyFile, "utf8");
const credentials = credentialsSchema.parse(JSON.parse(keyBase64));
if (credentials.secret) {
return {
token: credentials.token,
encryption: {
type: "legacy",
secret: new Uint8Array(Buffer.from(credentials.secret, "base64"))
}
};
} else if (credentials.encryption) {
return {
token: credentials.token,
encryption: {
type: "dataKey",
publicKey: new Uint8Array(Buffer.from(credentials.encryption.publicKey, "base64")),
machineKey: new Uint8Array(Buffer.from(credentials.encryption.machineKey, "base64"))
}
};
}
} catch {
return null;
}
return null;
}
async function writeCredentialsLegacy(credentials) {
if (!fs.existsSync(api.configuration.consortiumHomeDir)) {
await promises.mkdir(api.configuration.consortiumHomeDir, { recursive: true });
}
await promises.writeFile(api.configuration.privateKeyFile, JSON.stringify({
secret: api.encodeBase64(credentials.secret),
token: credentials.token
}, null, 2));
}
async function writeCredentialsDataKey(credentials) {
if (!fs.existsSync(api.configuration.consortiumHomeDir)) {
await promises.mkdir(api.configuration.consortiumHomeDir, { recursive: true });
}
await promises.writeFile(api.configuration.privateKeyFile, JSON.stringify({
encryption: { publicKey: api.encodeBase64(credentials.publicKey), machineKey: api.encodeBase64(credentials.machineKey) },
token: credentials.token
}, null, 2));
}
async function clearCredentials() {
if (fs.existsSync(api.configuration.privateKeyFile)) {
await promises.unlink(api.configuration.privateKeyFile);
}
}
async function clearMachineId() {
await updateSettings((settings) => ({
...settings,
machineId: void 0
}));
}
async function readDaemonState() {
try {
if (!fs.existsSync(api.configuration.daemonStateFile)) {
return null;
}
const content = await promises.readFile(api.configuration.daemonStateFile, "utf-8");
return JSON.parse(content);
} catch (error) {
console.error(`[PERSISTENCE] Daemon state file corrupted: ${api.configuration.daemonStateFile}`, error);
return null;
}
}
function writeDaemonState(state) {
fs.writeFileSync(api.configuration.daemonStateFile, JSON.stringify(state, null, 2), "utf-8");
}
async function clearDaemonState() {
if (fs.existsSync(api.configuration.daemonStateFile)) {
await promises.unlink(api.configuration.daemonStateFile);
}
if (fs.existsSync(api.configuration.daemonLockFile)) {
try {
await promises.unlink(api.configuration.daemonLockFile);
} catch {
}
}
}
async function acquireDaemonLock(maxAttempts = 5, delayIncrementMs = 200) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const fileHandle = await promises.open(
api.configuration.daemonLockFile,
fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_WRONLY
);
await fileHandle.writeFile(String(process.pid));
return fileHandle;
} catch (error) {
if (error.code === "EEXIST") {
try {
const lockPid = fs.readFileSync(api.configuration.daemonLockFile, "utf-8").trim();
if (lockPid && !isNaN(Number(lockPid))) {
try {
process.kill(Number(lockPid), 0);
} catch {
fs.unlinkSync(api.configuration.daemonLockFile);
continue;
}
}
} catch {
}
}
if (attempt === maxAttempts) {
return null;
}
const delayMs = attempt * delayIncrementMs;
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
return null;
}
async function releaseDaemonLock(lockHandle) {
try {
await lockHandle.close();
} catch {
}
try {
if (fs.existsSync(api.configuration.daemonLockFile)) {
fs.unlinkSync(api.configuration.daemonLockFile);
}
} catch {
}
}
exports.AIBackendProfileSchema = AIBackendProfileSchema;
exports.SUPPORTED_SCHEMA_VERSION = SUPPORTED_SCHEMA_VERSION;
exports.acquireDaemonLock = acquireDaemonLock;
exports.clearCredentials = clearCredentials;
exports.clearDaemonState = clearDaemonState;
exports.clearMachineId = clearMachineId;
exports.getProfileEnvironmentVariables = getProfileEnvironmentVariables;
exports.readCredentials = readCredentials;
exports.readDaemonState = readDaemonState;
exports.readSettings = readSettings;
exports.releaseDaemonLock = releaseDaemonLock;
exports.updateSettings = updateSettings;
exports.validateProfileForAgent = validateProfileForAgent;
exports.writeCredentialsDataKey = writeCredentialsDataKey;
exports.writeCredentialsLegacy = writeCredentialsLegacy;
exports.writeDaemonState = writeDaemonState;