/** * Step: register — Assign a room via the canonical application service. * * EJClaw is Discord-only, so registrations must target Discord channel IDs. */ import { execFileSync } from 'child_process'; import fs from 'fs'; import path from 'path'; import { GROUPS_DIR } from '../src/config.js'; import { assignRoom, initDatabase } from '../src/db.js'; import { isValidGroupFolder } from '../src/group-folder.js'; import { logger } from '../src/logger.js'; import { emitStatus } from './status.js'; /** * Resolve a usable git-repo work_dir for a new tribunal room. * Order: if .git exists, else /repo if .git exists, * else `git init` (with an empty initial commit) and use it. * Returns the absolute work_dir path. */ function ensureWorkDirForFolder(folderName: string): string { const folderPath = path.join(GROUPS_DIR, folderName); fs.mkdirSync(folderPath, { recursive: true }); if (fs.existsSync(path.join(folderPath, '.git'))) { return folderPath; } const repoSub = path.join(folderPath, 'repo'); if (fs.existsSync(path.join(repoSub, '.git'))) { return repoSub; } // No git repo found anywhere — initialize at the folder root. const gitEnv = { ...process.env, GIT_AUTHOR_NAME: 'ejclaw', GIT_AUTHOR_EMAIL: 'ejclaw@local', GIT_COMMITTER_NAME: 'ejclaw', GIT_COMMITTER_EMAIL: 'ejclaw@local', }; execFileSync('git', ['init', '-q'], { cwd: folderPath, env: gitEnv }); execFileSync( 'git', ['commit', '-q', '--allow-empty', '-m', `init ${folderName} workspace`], { cwd: folderPath, env: gitEnv }, ); logger.info( { folder: folderName, workDir: folderPath }, 'Initialized git repository for tribunal workspace', ); return folderPath; } interface RegisterArgs { jid: string; name: string; folder: string; channel: string; isMain: boolean; assistantNameProvided: boolean; } function parseArgs(args: string[]): RegisterArgs { const result: RegisterArgs = { jid: '', name: '', folder: '', channel: 'discord', isMain: false, assistantNameProvided: false, }; for (let i = 0; i < args.length; i++) { switch (args[i]) { case '--jid': result.jid = args[++i] || ''; break; case '--name': result.name = args[++i] || ''; break; case '--folder': result.folder = args[++i] || ''; break; case '--channel': result.channel = (args[++i] || '').toLowerCase(); break; case '--is-main': result.isMain = true; break; case '--assistant-name': result.assistantNameProvided = true; i += 1; break; } } return result; } export async function run(args: string[]): Promise { const parsed = parseArgs(args); if (!parsed.jid || !parsed.name || !parsed.folder) { emitStatus('REGISTER_CHANNEL', { STATUS: 'failed', ERROR: 'missing_required_args', LOG: 'logs/setup.log', }); process.exit(4); } if (!isValidGroupFolder(parsed.folder)) { emitStatus('REGISTER_CHANNEL', { STATUS: 'failed', ERROR: 'invalid_folder', LOG: 'logs/setup.log', }); process.exit(4); } if (parsed.channel !== 'discord') { emitStatus('REGISTER_CHANNEL', { STATUS: 'failed', ERROR: 'unsupported_channel', LOG: 'logs/setup.log', }); process.exit(4); } if (parsed.assistantNameProvided) { emitStatus('REGISTER_CHANNEL', { STATUS: 'failed', ERROR: 'assistant_name_option_removed', NEXT_STEP: 'Use a dedicated assistant identity configuration command instead', LOG: 'logs/setup.log', }); process.exit(4); } logger.info(parsed, 'Registering channel'); initDatabase(); // Defaults for newly registered rooms: // roomMode = tribunal, owner = claude-code, reviewer = codex // Tribunal rooms also require a git-backed work_dir; auto-init if missing. const workDir = ensureWorkDirForFolder(parsed.folder); assignRoom(parsed.jid, { name: parsed.name, folder: parsed.folder, isMain: parsed.isMain, roomMode: 'tribunal', ownerAgentType: 'claude-code', reviewerAgentType: 'codex', workDir, }); logger.info('Assigned room through canonical room service'); fs.mkdirSync(path.join(GROUPS_DIR, parsed.folder, 'logs'), { recursive: true, }); logger.info({ folder: parsed.folder }, 'Ensured group log directory exists'); emitStatus('REGISTER_CHANNEL', { JID: parsed.jid, NAME: parsed.name, FOLDER: parsed.folder, CHANNEL: parsed.channel, STATUS: 'success', LOG: 'logs/setup.log', }); }