Skip to content

Commit 9b6ada5

Browse files
committed
Add unsync command
1 parent 2424576 commit 9b6ada5

File tree

3 files changed

+158
-0
lines changed

3 files changed

+158
-0
lines changed

docs/agents/commands.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@
4040
- Export (Repo -> System): Applies repository configurations to the system.
4141
- Safety: Automatically creates backups of existing system configurations.
4242

43+
### `syncode unsync`
44+
- Purpose: Removes symlink-based configs and copies them back to the local machine.
45+
- Actions:
46+
- Replaces symlinked agent configs with real files in system paths.
47+
- Skips agents that use copy-based syncing.
48+
4349
### `syncode status`
4450
- Purpose: Displays the current synchronization state.
4551
- Output:

src/commands/unsync.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* Unsync agent configs by removing symlinks
3+
*/
4+
5+
import { copyFileSync, readdirSync, unlinkSync } from "node:fs";
6+
import { dirname, join } from "node:path";
7+
import * as p from "@clack/prompts";
8+
import { adapterRegistry } from "../adapters/registry";
9+
import type { Platform } from "../adapters/types";
10+
import { getConfig } from "../config/manager";
11+
import type { GlobalConfig } from "../config/types";
12+
import {
13+
copyDir,
14+
ensureDir,
15+
exists,
16+
isDirectory,
17+
isSymlink,
18+
removeDir,
19+
} from "../utils/fs";
20+
import { expandHome } from "../utils/paths";
21+
22+
function copyDirReplacingSymlinks(src: string, dest: string): void {
23+
ensureDir(dest);
24+
const entries = readdirSync(src, { withFileTypes: true });
25+
26+
for (const entry of entries) {
27+
const srcPath = join(src, entry.name);
28+
const destPath = join(dest, entry.name);
29+
30+
if (entry.isDirectory()) {
31+
if (isSymlink(destPath)) {
32+
unlinkSync(destPath);
33+
} else if (exists(destPath) && !isDirectory(destPath)) {
34+
unlinkSync(destPath);
35+
}
36+
37+
ensureDir(destPath);
38+
copyDirReplacingSymlinks(srcPath, destPath);
39+
continue;
40+
}
41+
42+
if (isSymlink(destPath)) {
43+
unlinkSync(destPath);
44+
} else if (exists(destPath) && isDirectory(destPath)) {
45+
removeDir(destPath);
46+
}
47+
48+
ensureDir(dirname(destPath));
49+
copyFileSync(srcPath, destPath);
50+
}
51+
}
52+
53+
function unsyncSymlinkedConfig(repoPath: string, systemPath: string): boolean {
54+
if (!exists(repoPath) || !isDirectory(repoPath)) {
55+
return false;
56+
}
57+
58+
if (isSymlink(systemPath)) {
59+
unlinkSync(systemPath);
60+
copyDir(repoPath, systemPath);
61+
return true;
62+
}
63+
64+
ensureDir(systemPath);
65+
copyDirReplacingSymlinks(repoPath, systemPath);
66+
return true;
67+
}
68+
69+
export async function unsyncCommand() {
70+
p.intro("Unsync Agent Configs");
71+
72+
let config: GlobalConfig;
73+
try {
74+
config = getConfig();
75+
} catch (_error) {
76+
p.cancel("Configuration not found. Run 'syncode new' first.");
77+
return;
78+
}
79+
80+
if (config.agents.length === 0) {
81+
p.cancel("No agents configured. Run 'syncode new' to set up agents.");
82+
return;
83+
}
84+
85+
const platform: Platform =
86+
process.platform === "darwin"
87+
? "macos"
88+
: process.platform === "win32"
89+
? "windows"
90+
: "linux";
91+
const repoPath = expandHome(config.repoPath);
92+
93+
let successCount = 0;
94+
let skipCount = 0;
95+
let failCount = 0;
96+
97+
const s = p.spinner();
98+
s.start("Removing config symlinks");
99+
100+
for (const agentId of config.agents) {
101+
const adapter = adapterRegistry.get(agentId);
102+
if (!adapter) {
103+
s.message(`Warning: Adapter not found for ${agentId}`);
104+
failCount++;
105+
continue;
106+
}
107+
108+
if (adapter.syncStrategy.export !== "symlink") {
109+
s.message(`↷ ${adapter.name} uses copy strategy - skipped`);
110+
skipCount++;
111+
continue;
112+
}
113+
114+
const systemPath = adapter.getConfigPath(platform);
115+
const agentRepoPath = adapter.getRepoPath(repoPath);
116+
117+
if (!adapter.isLinked(systemPath, agentRepoPath)) {
118+
s.message(`↷ ${adapter.name} is not linked - skipped`);
119+
skipCount++;
120+
continue;
121+
}
122+
123+
try {
124+
const success = unsyncSymlinkedConfig(agentRepoPath, systemPath);
125+
if (success) {
126+
s.message(`✓ Unsynced ${adapter.name}`);
127+
successCount++;
128+
} else {
129+
s.message(`✗ Failed to unsync ${adapter.name}: configs not found in repo`);
130+
failCount++;
131+
}
132+
} catch (error) {
133+
s.message(`✗ Error unsyncing ${adapter.name}: ${error}`);
134+
failCount++;
135+
}
136+
}
137+
138+
s.stop(
139+
`Unsync complete: ${successCount} succeeded, ${skipCount} skipped, ${failCount} failed`,
140+
);
141+
142+
p.outro("Symlinks removed. Configs now live on your machine.");
143+
}

src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { newCommand } from "./commands/new";
88
import { pushCommand } from "./commands/push";
99
import { statusCommand } from "./commands/status";
1010
import { syncCommand } from "./commands/sync";
11+
import { unsyncCommand } from "./commands/unsync";
1112

1213
// Read version from package.json
1314
const __filename = fileURLToPath(import.meta.url);
@@ -20,6 +21,7 @@ const VERSION = packageJson.version;
2021
const commands = {
2122
new: newCommand,
2223
sync: syncCommand,
24+
unsync: unsyncCommand,
2325
status: statusCommand,
2426
push: pushCommand,
2527
};
@@ -69,6 +71,11 @@ async function main() {
6971
label: "Sync configs",
7072
hint: "Import or export agent configs",
7173
},
74+
{
75+
value: "unsync",
76+
label: "Remove symlinks",
77+
hint: "Copy configs back to this machine",
78+
},
7279
{
7380
value: "status",
7481
label: "Check status",
@@ -107,6 +114,7 @@ Setup Commands:
107114
108115
Sync Commands:
109116
sync Sync agent configs (import or export)
117+
unsync Remove symlinks and keep local configs
110118
status Show status of synced agents
111119
push Push config changes to git remote
112120
@@ -119,6 +127,7 @@ Examples:
119127
syncode new # Initialize new agent config repo
120128
syncode status # Show status
121129
syncode sync # Sync agent configs
130+
syncode unsync # Remove symlinks
122131
syncode push # Push changes to remote
123132
124133
Quick Start:

0 commit comments

Comments
 (0)