Skip to content

Instantly share code, notes, and snippets.

@balloonlimb
Created March 31, 2026 02:04
Show Gist options
  • Select an option

  • Save balloonlimb/aceb4e4af20c8e43fa7e134fdfca691f to your computer and use it in GitHub Desktop.

Select an option

Save balloonlimb/aceb4e4af20c8e43fa7e134fdfca691f to your computer and use it in GitHub Desktop.
#!/usr/bin/env tsx
import {
existsSync,
mkdirSync,
rmSync,
cpSync,
unlinkSync,
readFileSync,
writeFileSync,
} from "fs";
import { tmpdir } from "os";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const repoDir = join(__dirname, "..");
const claudeCurrDir = join(repoDir, ".claude");
const claudeAltsDir = join(repoDir, ".claude.alts");
const currClaudeMD = join(repoDir, "CLAUDE.md");
const archivedDir = join(claudeAltsDir, ".archived");
type ParsedArgs = {
name: string;
targetClaudeAltsDir: string;
targetClaudeMD: string;
};
function printUsage(exitCode = 0): never {
console.error(`
Usage:
npx tsx .claude.alts/load.ts <name>
Purpose:
Load a Claude config from .claude.alts/<name> into the active repo state,
with archive + rollback protection.
Expected repo structure:
<repo>/
.claude/
.claude.alts/
<name>/
CLAUDE.md
...
.archived/
CLAUDE.md
Behavior:
1. Validate the requested alternative config.
2. Archive the current active config into:
.claude.alts/.archived/<timestamp>/
3. Stage the requested alternative config in a temp directory.
4. Replace the active config.
5. If anything fails after replacement begins, automatically restore the
previously active config from the archive.
Examples:
npx tsx .claude.alts/load.ts planning
npx tsx .claude.alts/load.ts research-mode
`.trim());
process.exit(exitCode);
}
function timestampForPath(): string {
return new Date().toISOString().replace(/[:.]/g, "-");
}
function ensureDir(path: string): void {
mkdirSync(path, { recursive: true });
}
function pathExists(path: string): boolean {
return existsSync(path);
}
function assertExists(path: string, label: string): void {
if (!pathExists(path)) {
throw new Error(`${label} does not exist: ${path}`);
}
}
function warn(message: string): void {
console.error(`WARN: ${message}`);
}
function info(message: string): void {
console.error(`INFO: ${message}`);
}
function parseArgs(): ParsedArgs {
const args = process.argv.slice(2);
const name = args[0]?.trim() ?? "";
if (!name || ["help", "--help", "-h", "h"].includes(name)) {
printUsage(name ? 0 : 1);
}
const targetClaudeAltsRoot = join(claudeAltsDir, name);
const targetClaudeAltsDir = join(targetClaudeAltsRoot, '.claude');
const targetClaudeMD = join(targetClaudeAltsRoot, "CLAUDE.md");
assertExists(claudeAltsDir, "Alternatives directory");
assertExists(targetClaudeAltsDir, "Selected alternative config");
assertExists(targetClaudeMD, "Selected alternative CLAUDE.md");
return { name, targetClaudeAltsDir, targetClaudeMD };
}
function removeIfExists(path: string): void {
if (pathExists(path)) {
rmSync(path, { recursive: true, force: true });
}
}
function archiveCurrent(archiveDir: string): void {
ensureDir(archiveDir);
if (pathExists(claudeCurrDir)) {
cpSync(claudeCurrDir, join(archiveDir, ".claude"), {
recursive: true,
force: true,
});
} else {
warn(`No active .claude directory found at ${claudeCurrDir}; nothing to archive there.`);
}
if (pathExists(currClaudeMD)) {
cpSync(currClaudeMD, join(archiveDir, "CLAUDE.md"), {
force: true,
});
} else {
warn(`No active root CLAUDE.md found at ${currClaudeMD}; nothing to archive there.`);
}
}
function stageTarget(targetClaudeAltsDir: string, targetClaudeMD: string, stageDir: string): void {
ensureDir(stageDir);
const stagedClaudeDir = join(stageDir, ".claude");
const stagedRootClaudeMD = join(stageDir, "CLAUDE.md");
cpSync(targetClaudeAltsDir, stagedClaudeDir, {
recursive: true,
force: true,
});
const targetClaudeMDContents = readFileSync(targetClaudeMD, "utf8");
writeFileSync(stagedRootClaudeMD, targetClaudeMDContents, "utf8");
const stagedInnerClaudeMD = join(stagedClaudeDir, "CLAUDE.md");
if (pathExists(stagedInnerClaudeMD)) {
unlinkSync(stagedInnerClaudeMD);
}
}
function clearActiveState(): void {
if (pathExists(claudeCurrDir)) {
rmSync(claudeCurrDir, { recursive: true, force: true });
}
if (pathExists(currClaudeMD)) {
rmSync(currClaudeMD, { force: true });
}
}
function applyStaged(stageDir: string): void {
const stagedClaudeDir = join(stageDir, ".claude");
const stagedRootClaudeMD = join(stageDir, "CLAUDE.md");
assertExists(stagedClaudeDir, "Staged .claude directory");
assertExists(stagedRootClaudeMD, "Staged root CLAUDE.md");
clearActiveState();
cpSync(stagedClaudeDir, claudeCurrDir, {
recursive: true,
force: true,
});
cpSync(stagedRootClaudeMD, currClaudeMD, {
force: true,
});
}
function restoreFromArchive(archiveDir: string): void {
const archivedClaudeDir = join(archiveDir, ".claude");
const archivedClaudeMD = join(archiveDir, "CLAUDE.md");
info(`Attempting rollback from archive: ${archiveDir}`);
clearActiveState();
if (pathExists(archivedClaudeDir)) {
cpSync(archivedClaudeDir, claudeCurrDir, {
recursive: true,
force: true,
});
} else {
warn(`Archive does not contain .claude at ${archivedClaudeDir}; active .claude will remain absent.`);
}
if (pathExists(archivedClaudeMD)) {
cpSync(archivedClaudeMD, currClaudeMD, {
force: true,
});
} else {
warn(`Archive does not contain root CLAUDE.md at ${archivedClaudeMD}; active root CLAUDE.md will remain absent.`);
}
}
function cleanupStage(stageDir: string): void {
removeIfExists(stageDir);
}
function main(): void {
const { name, targetClaudeAltsDir, targetClaudeMD } = parseArgs();
ensureDir(archivedDir);
const runId = timestampForPath();
// create a unique temp directory like:
// /tmp/claude-switch-<pid>-<timestamp>
const stageDir = join(
tmpdir(),
`claude-switch-${process.pid}-${runId}`
);
const archiveDir = join(archivedDir, runId);
let switchStarted = false;
try {
info(`Validating alternative config "${name}"`);
assertExists(targetClaudeAltsDir, "Selected alternative config");
assertExists(targetClaudeMD, "Selected alternative CLAUDE.md");
info(`Archiving current active config to ${archiveDir}`);
archiveCurrent(archiveDir);
info(`Staging new config in ${stageDir}`);
stageTarget(targetClaudeAltsDir, targetClaudeMD, stageDir);
info(`Applying staged config`);
switchStarted = true;
applyStaged(stageDir);
cleanupStage(stageDir);
console.log(`SUCCESS: loaded alternative config "${name}"`);
console.log(`SOURCE: ${targetClaudeAltsDir}`);
console.log(`ARCHIVE: previous active config saved to ${archiveDir}`);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.error("");
console.error("ERROR: Claude config switch failed.");
console.error(`ERROR: ${message}`);
if (switchStarted) {
console.error("");
console.error("WARN: The active config may be partially updated.");
console.error("WARN: Automatic rollback will now be attempted.");
try {
restoreFromArchive(archiveDir);
cleanupStage(stageDir);
console.error("ROLLBACK: success.");
console.error(`ROLLBACK: restored previous active config from ${archiveDir}`);
} catch (rollbackError) {
const rollbackMessage =
rollbackError instanceof Error ? rollbackError.message : String(rollbackError);
console.error("ROLLBACK: failed.");
console.error(`ROLLBACK ERROR: ${rollbackMessage}`);
console.error("");
console.error("MANUAL ACTION REQUIRED:");
console.error(`1. Inspect archive: ${archiveDir}`);
console.error(`2. Restore ${join(archiveDir, ".claude")} -> ${claudeCurrDir} if present`);
console.error(`3. Restore ${join(archiveDir, "CLAUDE.md")} -> ${currClaudeMD} if present`);
process.exit(1);
}
} else {
console.error("");
console.error("WARN: No active config changes were applied.");
cleanupStage(stageDir);
}
process.exit(1);
}
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment