Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,8 @@ Global configuration is stored at `~/.syncode/config.json`:
├── packages-arch.txt
├── packages-debian.txt
├── README.md
├── .agents/ # Shared skills (symlinked)
│ └── skills/
└── configs/
├── amp/ # Symlinked
├── antigravity/ # Copy sync
Expand Down Expand Up @@ -215,7 +217,7 @@ Global configuration is stored at `~/.syncode/config.json`:
```bash
# Edit your AI agent configs normally
# Example: ~/.config/opencode/opencode.json
# Example: ~/.claude/skills/my-helper.md
# Example: ~/.agents/skills/my-helper.md
# Changes are synced via symlinks automatically

# Check what changed
Expand Down
1 change: 1 addition & 0 deletions docs/agents/project-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
- `src/config/`: Configuration management (manager.ts, types.ts).
- `src/utils/`: Shared helpers (fs, git, paths, shell, platform).
- `configs/`: In the user's repo, stores tracked agent configs.
- `.agents/`: In the user's repo, stores shared skills (symlinked).
144 changes: 144 additions & 0 deletions src/adapters/agents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/**
* Shared Agents adapter
*/

import { unlinkSync } from "node:fs";
import { join } from "node:path";
import {
copyDir,
createSymlink,
ensureDir,
exists,
getSymlinkTarget,
isSymlink,
removeDir,
} from "../utils/fs";
import { contractHome } from "../utils/paths";
import type {
AgentAdapter,
ExportResult,
ImportResult,
Platform,
} from "./types";

export class AgentsAdapter implements AgentAdapter {
readonly id = "agents";
readonly name = "Shared Agents";
readonly version = "1.0.0";
readonly syncStrategy = {
import: "copy" as const,
export: "symlink" as const,
};

getConfigPath(_platform: Platform): string {
return join(process.env.HOME || "", ".agents");
}

getRepoPath(repoRoot: string): string {
return join(repoRoot, ".agents");
}

getSkillsPath(_platform: Platform): string {
return join(this.getConfigPath(_platform), "skills");
}

isInstalled(platform: Platform): boolean {
return exists(this.getConfigPath(platform));
}

detect(): boolean {
const platform =
process.platform === "darwin"
? "macos"
: process.platform === "win32"
? "windows"
: "linux";
return this.isInstalled(platform);
}

isLinked(systemPath: string, repoPath: string): boolean {
if (!exists(systemPath) || !exists(repoPath) || !isSymlink(systemPath)) {
return false;
}
return getSymlinkTarget(systemPath) === repoPath;
}

async import(systemPath: string, repoPath: string): Promise<ImportResult> {
if (!exists(systemPath)) {
return {
success: false,
message: "Shared agents config not found on system",
};
}

if (isSymlink(systemPath)) {
return {
success: true,
message: "Already linked to repo - no import needed",
};
}

if (exists(repoPath)) {
return {
success: true,
message: "Configs already in repo - no import needed",
};
}

ensureDir(repoPath);
copyDir(systemPath, repoPath);

return {
success: true,
message: "Imported shared agents configs to repo",
};
}

async export(repoPath: string, systemPath: string): Promise<ExportResult> {
if (!exists(repoPath)) {
return {
success: false,
message: "Shared agents configs not found in repo",
};
}

ensureDir(join(repoPath, "skills"));

if (isSymlink(systemPath)) {
const target = getSymlinkTarget(systemPath);
if (target === repoPath) {
return {
success: true,
message: "Already linked to repo - no export needed",
linkedTo: repoPath,
};
}
}

if (exists(systemPath)) {
if (isSymlink(systemPath)) {
unlinkSync(systemPath);
} else {
const backupPath = `${systemPath}.backup`;
if (exists(backupPath)) {
if (isSymlink(backupPath)) {
unlinkSync(backupPath);
} else {
removeDir(backupPath);
}
}
require("node:fs").renameSync(systemPath, backupPath);
}
}

createSymlink(repoPath, systemPath);

return {
success: true,
message: `Linked shared agents configs to ${contractHome(systemPath)}`,
linkedTo: repoPath,
};
}
}

export const agentsAdapter = new AgentsAdapter();
11 changes: 11 additions & 0 deletions src/adapters/amp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import {
removeDir,
} from "../utils/fs";
import { contractHome } from "../utils/paths";
import {
getSharedSkillsPath,
getSharedSkillsRepoPath,
linkSharedSkillsInRepo,
} from "./shared-skills";
import type {
AgentAdapter,
ExportResult,
Expand All @@ -34,6 +39,10 @@ export class AmpAdapter implements AgentAdapter {
return join(process.env.HOME || "", ".config/amp");
}

getSkillsPath(_platform: Platform): string {
return getSharedSkillsPath();
}

getRepoPath(repoRoot: string): string {
return join(repoRoot, "configs", "amp");
}
Expand Down Expand Up @@ -130,6 +139,8 @@ export class AmpAdapter implements AgentAdapter {
// Create symlink
createSymlink(repoPath, systemPath);

linkSharedSkillsInRepo(repoPath, getSharedSkillsRepoPath(repoPath));

return {
success: true,
message: `Linked Amp configs to ${contractHome(systemPath)}`,
Expand Down
11 changes: 7 additions & 4 deletions src/adapters/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
isSymlink,
} from "../utils/fs";
import { contractHome } from "../utils/paths";
import { getSharedSkillsPath, linkSharedSkillsOnSystem } from "./shared-skills";
import type {
AgentAdapter,
CanonicalSkill,
Expand All @@ -30,7 +31,7 @@ export class ClaudeAdapter implements AgentAdapter {
};

// Files/folders to sync (exclude cache, history, etc.)
private syncPatterns = ["settings.json", "CLAUDE.md", "commands", "skills"];
private syncPatterns = ["settings.json", "CLAUDE.md", "commands"];

getConfigPath(platform: Platform): string {
if (platform === "windows") {
Expand All @@ -39,8 +40,8 @@ export class ClaudeAdapter implements AgentAdapter {
return join(process.env.HOME || "", ".claude");
}

getSkillsPath(platform: Platform): string {
return join(this.getConfigPath(platform), "skills");
getSkillsPath(_platform: Platform): string {
return getSharedSkillsPath();
}

getRepoPath(repoRoot: string): string {
Expand Down Expand Up @@ -107,7 +108,7 @@ export class ClaudeAdapter implements AgentAdapter {
return {
success: true,
message:
"No Claude configs found to import (settings.json, CLAUDE.md, commands/, skills/)",
"No Claude configs found to import (settings.json, CLAUDE.md, commands/)",
};
}

Expand Down Expand Up @@ -150,6 +151,8 @@ export class ClaudeAdapter implements AgentAdapter {
}
}

linkSharedSkillsOnSystem(join(systemPath, "skills"));

return {
success: true,
message: `Copied Claude configs to ${contractHome(systemPath)}`,
Expand Down
11 changes: 11 additions & 0 deletions src/adapters/codex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import {
removeDir,
} from "../utils/fs";
import { contractHome } from "../utils/paths";
import {
getSharedSkillsPath,
getSharedSkillsRepoPath,
linkSharedSkillsInRepo,
} from "./shared-skills";
import type {
AgentAdapter,
ExportResult,
Expand All @@ -34,6 +39,10 @@ export class CodexAdapter implements AgentAdapter {
return join(process.env.HOME || "", ".codex");
}

getSkillsPath(_platform: Platform): string {
return getSharedSkillsPath();
}

getRepoPath(repoRoot: string): string {
return join(repoRoot, "configs", "codex");
}
Expand Down Expand Up @@ -130,6 +139,8 @@ export class CodexAdapter implements AgentAdapter {
// Create symlink
createSymlink(repoPath, systemPath);

linkSharedSkillsInRepo(repoPath, getSharedSkillsRepoPath(repoPath));

return {
success: true,
message: `Linked Codex configs to ${contractHome(systemPath)}`,
Expand Down
7 changes: 7 additions & 0 deletions src/adapters/cursor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
removeDir,
} from "../utils/fs";
import { contractHome } from "../utils/paths";
import { getSharedSkillsPath, linkSharedSkillsOnSystem } from "./shared-skills";
import type {
AgentAdapter,
CanonicalSkill,
Expand Down Expand Up @@ -63,6 +64,10 @@ export class CursorAdapter implements AgentAdapter {
}
}

getSkillsPath(_platform: Platform): string {
return getSharedSkillsPath();
}

getRepoPath(repoRoot: string): string {
return join(repoRoot, "configs", "cursor");
}
Expand Down Expand Up @@ -137,6 +142,8 @@ export class CursorAdapter implements AgentAdapter {
};
}

linkSharedSkillsOnSystem(join(systemPath, "skills"));

return {
success: true,
message: "Imported Cursor configs to repo",
Expand Down
Loading
Loading