🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@syncer/cli

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@syncer/cli - npm Package Compare versions

Comparing version
0.2.0
to
0.3.0
+174
dist/chunk-3PXPG3CQ.js
#!/usr/bin/env node
import {
REGISTRY_MARKER_FILE,
readRegistryMarker
} from "./chunk-YOYA4EIK.js";
// src/core/resolver.ts
import fs from "fs";
import path from "path";
function loadPack(registryPath, packName) {
const markerPath = path.join(registryPath, REGISTRY_MARKER_FILE);
if (!fs.existsSync(markerPath)) {
throw new Error(`Pack "${packName}" not found in registry (no registry marker found)`);
}
const marker = readRegistryMarker(registryPath);
const entry = marker.packs?.[packName];
if (!entry) {
throw new Error(`Pack "${packName}" not found in registry`);
}
return { name: packName, ...entry };
}
function listAvailablePacks(registryPath) {
const markerPath = path.join(registryPath, REGISTRY_MARKER_FILE);
if (!fs.existsSync(markerPath)) return [];
const marker = readRegistryMarker(registryPath);
return Object.keys(marker.packs ?? {});
}
function listAvailableSkills(registryPath) {
const dir = path.join(registryPath, "skills");
if (!fs.existsSync(dir)) return [];
return fs.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
}
function listAvailableAgents(registryPath) {
const dir = path.join(registryPath, "agents");
if (!fs.existsSync(dir)) return [];
return fs.readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
}
function listAvailableCommands(registryPath) {
const dir = path.join(registryPath, "commands");
if (!fs.existsSync(dir)) return [];
return fs.readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
}
function resolvePacks(registryPath, packNames) {
const merged = { skills: [], agents: [], commands: [] };
for (const packName of packNames) {
const resolved = resolveOnePack(registryPath, packName, []);
mergeInto(merged, resolved);
}
return {
skills: dedupe(merged.skills),
agents: dedupe(merged.agents),
commands: dedupe(merged.commands)
};
}
function resolveOnePack(registryPath, packName, chain) {
if (chain.includes(packName)) {
throw new Error(
`Circular pack extends detected: ${[...chain, packName].join(" \u2192 ")}`
);
}
const pack = loadPack(registryPath, packName);
const result = { skills: [], agents: [], commands: [] };
if (pack.extends) {
const parent = resolveOnePack(registryPath, pack.extends, [
...chain,
packName
]);
mergeInto(result, parent);
}
if (pack.skills) result.skills.push(...pack.skills);
if (pack.agents) result.agents.push(...pack.agents);
if (pack.commands) result.commands.push(...pack.commands);
return result;
}
function applyOverrides(base, config) {
return {
skills: applyOverride(
base.skills,
config.skills.include,
config.skills.exclude
),
agents: applyOverride(
base.agents,
config.agents.include,
config.agents.exclude
),
commands: applyOverride(
base.commands,
config.commands.include,
config.commands.exclude
)
};
}
function applyOverride(base, include, exclude) {
const set = /* @__PURE__ */ new Set([...base, ...include]);
for (const item of exclude) set.delete(item);
return Array.from(set);
}
function validateRegistry(registryPath) {
const errors = [];
const skillsDir = path.join(registryPath, "skills");
if (fs.existsSync(skillsDir)) {
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
const skillMd = path.join(skillsDir, entry.name, "SKILL.md");
if (!fs.existsSync(skillMd)) {
errors.push(`Skill "${entry.name}" is missing SKILL.md`);
}
}
}
const packNames = listAvailablePacks(registryPath);
const availableSkills = new Set(listAvailableSkills(registryPath));
const availableAgents = new Set(listAvailableAgents(registryPath));
const availableCommands = new Set(listAvailableCommands(registryPath));
for (const packName of packNames) {
try {
const pack = loadPack(registryPath, packName);
if (pack.extends && !packNames.includes(pack.extends)) {
errors.push(
`Pack "${packName}" extends unknown pack "${pack.extends}"`
);
}
try {
resolveOnePack(registryPath, packName, []);
} catch (err) {
errors.push(String(err));
}
for (const skill of pack.skills ?? []) {
if (!availableSkills.has(skill)) {
errors.push(
`Pack "${packName}" references unknown skill "${skill}"`
);
}
}
for (const agent of pack.agents ?? []) {
if (!availableAgents.has(agent)) {
errors.push(
`Pack "${packName}" references unknown agent "${agent}"`
);
}
}
for (const cmd of pack.commands ?? []) {
if (!availableCommands.has(cmd)) {
errors.push(
`Pack "${packName}" references unknown command "${cmd}"`
);
}
}
} catch (err) {
errors.push(`Pack "${packName}" failed to load: ${err}`);
}
}
return { valid: errors.length === 0, errors };
}
function mergeInto(target, source) {
target.skills.push(...source.skills);
target.agents.push(...source.agents);
target.commands.push(...source.commands);
}
function dedupe(arr) {
return Array.from(new Set(arr));
}
export {
loadPack,
listAvailablePacks,
listAvailableSkills,
listAvailableAgents,
listAvailableCommands,
resolvePacks,
applyOverrides,
validateRegistry
};
//# sourceMappingURL=chunk-3PXPG3CQ.js.map
{"version":3,"sources":["../src/core/resolver.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse } from \"yaml\";\nimport type { PackDef, ResolvedContent } from \"../types.js\";\nimport type { ResolvedConfig } from \"./config.js\";\nimport { readRegistryMarker, REGISTRY_MARKER_FILE } from \"./config.js\";\n\n// ─── Pack loading ─────────────────────────────────────────────────────────────\n\nexport function loadPack(registryPath: string, packName: string): PackDef {\n const markerPath = path.join(registryPath, REGISTRY_MARKER_FILE);\n if (!fs.existsSync(markerPath)) {\n throw new Error(`Pack \"${packName}\" not found in registry (no registry marker found)`);\n }\n const marker = readRegistryMarker(registryPath);\n const entry = marker.packs?.[packName];\n if (!entry) {\n throw new Error(`Pack \"${packName}\" not found in registry`);\n }\n return { name: packName, ...entry };\n}\n\n/** List all available pack names in the registry */\nexport function listAvailablePacks(registryPath: string): string[] {\n const markerPath = path.join(registryPath, REGISTRY_MARKER_FILE);\n if (!fs.existsSync(markerPath)) return [];\n const marker = readRegistryMarker(registryPath);\n return Object.keys(marker.packs ?? {});\n}\n\n/** List all available skills in the registry */\nexport function listAvailableSkills(registryPath: string): string[] {\n const dir = path.join(registryPath, \"skills\");\n if (!fs.existsSync(dir)) return [];\n return fs\n .readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n}\n\n/** List all available agents in the registry */\nexport function listAvailableAgents(registryPath: string): string[] {\n const dir = path.join(registryPath, \"agents\");\n if (!fs.existsSync(dir)) return [];\n return fs\n .readdirSync(dir)\n .filter((f) => f.endsWith(\".md\"))\n .map((f) => f.replace(/\\.md$/, \"\"));\n}\n\n/** List all available commands in the registry */\nexport function listAvailableCommands(registryPath: string): string[] {\n const dir = path.join(registryPath, \"commands\");\n if (!fs.existsSync(dir)) return [];\n return fs\n .readdirSync(dir)\n .filter((f) => f.endsWith(\".md\"))\n .map((f) => f.replace(/\\.md$/, \"\"));\n}\n\n// ─── Pack resolution ─────────────────────────────────────────────────────────\n\n/**\n * Resolve a list of pack names into merged skills/agents/commands.\n * Handles `extends` chains recursively with cycle detection.\n */\nexport function resolvePacks(\n registryPath: string,\n packNames: string[]\n): ResolvedContent {\n const merged: ResolvedContent = { skills: [], agents: [], commands: [] };\n\n for (const packName of packNames) {\n const resolved = resolveOnePack(registryPath, packName, []);\n mergeInto(merged, resolved);\n }\n\n return {\n skills: dedupe(merged.skills),\n agents: dedupe(merged.agents),\n commands: dedupe(merged.commands),\n };\n}\n\nfunction resolveOnePack(\n registryPath: string,\n packName: string,\n chain: string[]\n): ResolvedContent {\n if (chain.includes(packName)) {\n throw new Error(\n `Circular pack extends detected: ${[...chain, packName].join(\" → \")}`\n );\n }\n\n const pack = loadPack(registryPath, packName);\n const result: ResolvedContent = { skills: [], agents: [], commands: [] };\n\n // Resolve parent first (extends)\n if (pack.extends) {\n const parent = resolveOnePack(registryPath, pack.extends, [\n ...chain,\n packName,\n ]);\n mergeInto(result, parent);\n }\n\n // Add this pack's own items\n if (pack.skills) result.skills.push(...pack.skills);\n if (pack.agents) result.agents.push(...pack.agents);\n if (pack.commands) result.commands.push(...pack.commands);\n\n return result;\n}\n\n// ─── Override application ────────────────────────────────────────────────────\n\n/**\n * Apply project-level include/exclude overrides on top of resolved pack content.\n */\nexport function applyOverrides(\n base: ResolvedContent,\n config: ResolvedConfig\n): ResolvedContent {\n return {\n skills: applyOverride(\n base.skills,\n config.skills.include,\n config.skills.exclude\n ),\n agents: applyOverride(\n base.agents,\n config.agents.include,\n config.agents.exclude\n ),\n commands: applyOverride(\n base.commands,\n config.commands.include,\n config.commands.exclude\n ),\n };\n}\n\nfunction applyOverride(\n base: string[],\n include: string[],\n exclude: string[]\n): string[] {\n const set = new Set([...base, ...include]);\n for (const item of exclude) set.delete(item);\n return Array.from(set);\n}\n\n// ─── Validation (registry mode) ──────────────────────────────────────────────\n\nexport interface ValidationResult {\n valid: boolean;\n errors: string[];\n}\n\nexport function validateRegistry(registryPath: string): ValidationResult {\n const errors: string[] = [];\n\n // Validate skills\n const skillsDir = path.join(registryPath, \"skills\");\n if (fs.existsSync(skillsDir)) {\n for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const skillMd = path.join(skillsDir, entry.name, \"SKILL.md\");\n if (!fs.existsSync(skillMd)) {\n errors.push(`Skill \"${entry.name}\" is missing SKILL.md`);\n }\n }\n }\n\n // Validate packs\n const packNames = listAvailablePacks(registryPath);\n const availableSkills = new Set(listAvailableSkills(registryPath));\n const availableAgents = new Set(listAvailableAgents(registryPath));\n const availableCommands = new Set(listAvailableCommands(registryPath));\n\n for (const packName of packNames) {\n try {\n const pack = loadPack(registryPath, packName);\n\n // Validate extends reference\n if (pack.extends && !packNames.includes(pack.extends)) {\n errors.push(\n `Pack \"${packName}\" extends unknown pack \"${pack.extends}\"`\n );\n }\n\n // Validate extends chains for cycles\n try {\n resolveOnePack(registryPath, packName, []);\n } catch (err) {\n errors.push(String(err));\n }\n\n // Validate referenced content exists\n for (const skill of pack.skills ?? []) {\n if (!availableSkills.has(skill)) {\n errors.push(\n `Pack \"${packName}\" references unknown skill \"${skill}\"`\n );\n }\n }\n for (const agent of pack.agents ?? []) {\n if (!availableAgents.has(agent)) {\n errors.push(\n `Pack \"${packName}\" references unknown agent \"${agent}\"`\n );\n }\n }\n for (const cmd of pack.commands ?? []) {\n if (!availableCommands.has(cmd)) {\n errors.push(\n `Pack \"${packName}\" references unknown command \"${cmd}\"`\n );\n }\n }\n } catch (err) {\n errors.push(`Pack \"${packName}\" failed to load: ${err}`);\n }\n }\n\n return { valid: errors.length === 0, errors };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction mergeInto(target: ResolvedContent, source: ResolvedContent): void {\n target.skills.push(...source.skills);\n target.agents.push(...source.agents);\n target.commands.push(...source.commands);\n}\n\nfunction dedupe(arr: string[]): string[] {\n return Array.from(new Set(arr));\n}\n"],"mappings":"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","names":[]}
#!/usr/bin/env node
import {
ensureDir,
urlToKey
} from "./chunk-N6JPW7IT.js";
import {
CACHE_DIR
} from "./chunk-YOYA4EIK.js";
// src/core/registry.ts
import fs from "fs";
import path from "path";
import { simpleGit } from "simple-git";
function registryCachePath(registryUrl) {
return path.join(CACHE_DIR, urlToKey(registryUrl));
}
async function ensureRegistry(registryUrl, version = "latest") {
ensureDir(CACHE_DIR);
const cachePath = registryCachePath(registryUrl);
if (fs.existsSync(cachePath)) {
return await fetchRegistry(cachePath, registryUrl, version);
} else {
return await cloneRegistry(cachePath, registryUrl, version);
}
}
async function cloneRegistry(cachePath, registryUrl, version) {
const git = simpleGit();
const isLatest = version === "latest";
try {
if (isLatest) {
await git.clone(registryUrl, cachePath, ["--depth=1"]);
} else {
await git.clone(registryUrl, cachePath);
}
const repoGit = simpleGit(cachePath);
if (!isLatest) {
await repoGit.checkout(version);
}
const commit = await getCommitHash(repoGit);
return { cachePath, commit, fromCache: false };
} catch (err) {
if (!fs.existsSync(cachePath)) throw err;
fs.rmSync(cachePath, { recursive: true, force: true });
throw err;
}
}
async function fetchRegistry(cachePath, registryUrl, version) {
const repoGit = simpleGit(cachePath);
try {
await repoGit.fetch(["--prune"]);
const isLatest = version === "latest";
if (isLatest) {
const defaultBranch = await getDefaultBranch(repoGit);
await repoGit.checkout(defaultBranch);
await repoGit.pull();
} else {
await repoGit.checkout(version);
const isBranchLike = !/^[0-9a-f]{7,40}$/.test(version);
if (isBranchLike) {
try {
await repoGit.pull();
} catch {
}
}
}
const commit = await getCommitHash(repoGit);
return { cachePath, commit, fromCache: false };
} catch {
const commit = await getCommitHash(repoGit).catch(() => "unknown");
return { cachePath, commit, fromCache: true };
}
}
async function getCommitHash(git) {
const result = await git.revparse(["HEAD"]);
return result.trim();
}
async function getDefaultBranch(git) {
try {
const result = await git.raw(["symbolic-ref", "refs/remotes/origin/HEAD"]);
return result.trim().replace("refs/remotes/origin/", "");
} catch {
const branches = await git.branch(["-r"]);
if (branches.all.includes("origin/main")) return "main";
return "master";
}
}
async function listRegistryTags(registryUrl) {
const cachePath = registryCachePath(registryUrl);
if (!fs.existsSync(cachePath)) return [];
const git = simpleGit(cachePath);
const tags = await git.tags();
return tags.all;
}
async function listRegistryBranches(registryUrl) {
const cachePath = registryCachePath(registryUrl);
if (!fs.existsSync(cachePath)) return [];
const git = simpleGit(cachePath);
const branches = await git.branch(["-r"]);
return branches.all.map((b) => b.trim().replace(/^origin\//, "")).filter((b) => b !== "HEAD");
}
async function resolveRefType(registryUrl, ref) {
if (/^[0-9a-f]{7,40}$/.test(ref)) return "commit";
const tags = await listRegistryTags(registryUrl);
if (tags.includes(ref)) return "tag";
const branches = await listRegistryBranches(registryUrl);
if (branches.includes(ref)) return "branch";
return null;
}
function registryFile(cachePath, ...segments) {
return path.join(cachePath, ...segments);
}
function registryCacheExists(registryUrl) {
return fs.existsSync(registryCachePath(registryUrl));
}
export {
registryCachePath,
ensureRegistry,
listRegistryTags,
listRegistryBranches,
resolveRefType,
registryFile,
registryCacheExists
};
//# sourceMappingURL=chunk-LBLURIVB.js.map
{"version":3,"sources":["../src/core/registry.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { simpleGit } from \"simple-git\";\nimport { CACHE_DIR } from \"./config.js\";\nimport { ensureDir, urlToKey } from \"../utils/fs.js\";\n\n/** Returns the path to the registry clone for a given URL */\nexport function registryCachePath(registryUrl: string): string {\n return path.join(CACHE_DIR, urlToKey(registryUrl));\n}\n\nexport interface RegistryInfo {\n cachePath: string;\n commit: string;\n fromCache: boolean; // true if offline and using cached version\n}\n\n/**\n * Ensure the registry is cloned/fetched and checked out at the target version.\n * Returns the path to the working tree and the current commit hash.\n */\nexport async function ensureRegistry(\n registryUrl: string,\n version = \"latest\"\n): Promise<RegistryInfo> {\n ensureDir(CACHE_DIR);\n const cachePath = registryCachePath(registryUrl);\n\n if (fs.existsSync(cachePath)) {\n return await fetchRegistry(cachePath, registryUrl, version);\n } else {\n return await cloneRegistry(cachePath, registryUrl, version);\n }\n}\n\nasync function cloneRegistry(\n cachePath: string,\n registryUrl: string,\n version: string\n): Promise<RegistryInfo> {\n const git = simpleGit();\n const isLatest = version === \"latest\";\n\n try {\n if (isLatest) {\n await git.clone(registryUrl, cachePath, [\"--depth=1\"]);\n } else {\n // Full clone needed for tag/commit/branch checkout\n await git.clone(registryUrl, cachePath);\n }\n\n const repoGit = simpleGit(cachePath);\n if (!isLatest) {\n await repoGit.checkout(version);\n }\n\n const commit = await getCommitHash(repoGit);\n return { cachePath, commit, fromCache: false };\n } catch (err) {\n // If clone failed and no cache exists, rethrow\n if (!fs.existsSync(cachePath)) throw err;\n // Partial clone — clean up and rethrow\n fs.rmSync(cachePath, { recursive: true, force: true });\n throw err;\n }\n}\n\nasync function fetchRegistry(\n cachePath: string,\n registryUrl: string,\n version: string\n): Promise<RegistryInfo> {\n const repoGit = simpleGit(cachePath);\n\n try {\n await repoGit.fetch([\"--prune\"]);\n\n const isLatest = version === \"latest\";\n if (isLatest) {\n // Checkout default branch (main or master)\n const defaultBranch = await getDefaultBranch(repoGit);\n await repoGit.checkout(defaultBranch);\n await repoGit.pull();\n } else {\n await repoGit.checkout(version);\n // Pull only if it's a branch (not a tag or commit hash)\n const isBranchLike = !/^[0-9a-f]{7,40}$/.test(version);\n if (isBranchLike) {\n try {\n await repoGit.pull();\n } catch {\n // May not have upstream tracking — ignore\n }\n }\n }\n\n const commit = await getCommitHash(repoGit);\n return { cachePath, commit, fromCache: false };\n } catch {\n // Offline or fetch failed — use whatever is checked out\n const commit = await getCommitHash(repoGit).catch(() => \"unknown\");\n return { cachePath, commit, fromCache: true };\n }\n}\n\nasync function getCommitHash(git: ReturnType<typeof simpleGit>): Promise<string> {\n const result = await git.revparse([\"HEAD\"]);\n return result.trim();\n}\n\nasync function getDefaultBranch(\n git: ReturnType<typeof simpleGit>\n): Promise<string> {\n try {\n // Try to get the symbolic ref of origin/HEAD\n const result = await git.raw([\"symbolic-ref\", \"refs/remotes/origin/HEAD\"]);\n return result.trim().replace(\"refs/remotes/origin/\", \"\");\n } catch {\n // Fallback: try main, then master\n const branches = await git.branch([\"-r\"]);\n if (branches.all.includes(\"origin/main\")) return \"main\";\n return \"master\";\n }\n}\n\n/** List all available tags in the registry */\nexport async function listRegistryTags(\n registryUrl: string\n): Promise<string[]> {\n const cachePath = registryCachePath(registryUrl);\n if (!fs.existsSync(cachePath)) return [];\n const git = simpleGit(cachePath);\n const tags = await git.tags();\n return tags.all;\n}\n\n/** List all remote branches in the registry cache */\nexport async function listRegistryBranches(\n registryUrl: string\n): Promise<string[]> {\n const cachePath = registryCachePath(registryUrl);\n if (!fs.existsSync(cachePath)) return [];\n const git = simpleGit(cachePath);\n const branches = await git.branch([\"-r\"]);\n return branches.all\n .map((b) => b.trim().replace(/^origin\\//, \"\"))\n .filter((b) => b !== \"HEAD\");\n}\n\n/**\n * Determine what type of ref a version string is.\n * Returns null if the ref is not found in the cached registry.\n */\nexport async function resolveRefType(\n registryUrl: string,\n ref: string\n): Promise<\"tag\" | \"branch\" | \"commit\" | null> {\n if (/^[0-9a-f]{7,40}$/.test(ref)) return \"commit\";\n const tags = await listRegistryTags(registryUrl);\n if (tags.includes(ref)) return \"tag\";\n const branches = await listRegistryBranches(registryUrl);\n if (branches.includes(ref)) return \"branch\";\n return null;\n}\n\n/** Read a file from the registry cache */\nexport function registryFile(cachePath: string, ...segments: string[]): string {\n return path.join(cachePath, ...segments);\n}\n\n/** Check if the registry cache exists */\nexport function registryCacheExists(registryUrl: string): boolean {\n return fs.existsSync(registryCachePath(registryUrl));\n}\n"],"mappings":"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","names":[]}
#!/usr/bin/env node
// src/core/config.ts
import fs from "fs";
import path from "path";
import os from "os";
import { parse, stringify } from "yaml";
var GLOBAL_DIR = path.join(os.homedir(), ".syncer");
var GLOBAL_CONFIG_PATH = path.join(GLOBAL_DIR, "config.yaml");
var GLOBAL_STATE_PATH = path.join(GLOBAL_DIR, "state.json");
var CACHE_DIR = path.join(GLOBAL_DIR, "cache");
var PROJECT_CONFIG_FILE = ".syncer.yaml";
var PROJECT_CONFIG_FILE_NEW = ".syncer/syncer.yaml";
var PROJECT_LOCK_FILE = ".syncer/syncer.lock";
var PROJECT_CACHE_DIR = ".syncer";
var REGISTRY_MARKER_FILE = ".syncer-registry.yaml";
function findProjectConfigPath(cwd) {
for (const rel of [PROJECT_CONFIG_FILE_NEW, PROJECT_CONFIG_FILE_NEW_YML, PROJECT_CONFIG_FILE]) {
const p = path.join(cwd, rel);
if (fs.existsSync(p)) return p;
}
return null;
}
function detectContext(cwd) {
if (fs.existsSync(path.join(cwd, REGISTRY_MARKER_FILE))) return "registry";
if (findProjectConfigPath(cwd)) return "project";
return "unconfigured";
}
function readProjectConfig(cwd) {
const configPath = findProjectConfigPath(cwd);
if (!configPath) {
throw new Error(
`No syncer config found. Run \`syncer init\` to set up this project.`
);
}
const raw = fs.readFileSync(configPath, "utf8");
return parse(raw);
}
function writeProjectConfig(cwd, config) {
const configPath = findProjectConfigPath(cwd) ?? path.join(cwd, PROJECT_CONFIG_FILE_NEW);
fs.mkdirSync(path.dirname(configPath), { recursive: true });
fs.writeFileSync(configPath, stringify(config), "utf8");
}
function readRegistryMarker(cwd) {
const markerPath = path.join(cwd, REGISTRY_MARKER_FILE);
const raw = fs.readFileSync(markerPath, "utf8");
return parse(raw);
}
function writeRegistryMarker(cwd, marker) {
const markerPath = path.join(cwd, REGISTRY_MARKER_FILE);
fs.writeFileSync(markerPath, stringify(marker), "utf8");
}
function readGlobalConfig() {
if (!fs.existsSync(GLOBAL_CONFIG_PATH)) return {};
const raw = fs.readFileSync(GLOBAL_CONFIG_PATH, "utf8");
return parse(raw) ?? {};
}
function writeGlobalConfig(config) {
fs.mkdirSync(GLOBAL_DIR, { recursive: true });
fs.writeFileSync(GLOBAL_CONFIG_PATH, stringify(config), "utf8");
}
function resolveConfig(project, global) {
const registry = project.registry ?? global.default_registry ?? "";
const defaultPack = global.default_pack;
const packIncludes = project.packs?.include ?? [];
const packs = packIncludes.length > 0 ? packIncludes : defaultPack ? [defaultPack] : [];
return {
registry,
version: project.version ?? "latest",
targets: project.targets ?? ["claude"],
link_mode: project.link_mode ?? "symlink",
packs,
skills: {
include: project.skills?.include ?? [],
exclude: project.skills?.exclude ?? []
},
agents: {
include: project.agents?.include ?? [],
exclude: project.agents?.exclude ?? []
},
commands: {
include: project.commands?.include ?? [],
exclude: project.commands?.exclude ?? []
}
};
}
export {
GLOBAL_DIR,
GLOBAL_STATE_PATH,
CACHE_DIR,
PROJECT_CONFIG_FILE,
PROJECT_LOCK_FILE,
PROJECT_CACHE_DIR,
REGISTRY_MARKER_FILE,
findProjectConfigPath,
detectContext,
readProjectConfig,
writeProjectConfig,
readRegistryMarker,
writeRegistryMarker,
readGlobalConfig,
writeGlobalConfig,
resolveConfig
};
//# sourceMappingURL=chunk-YOYA4EIK.js.map
{"version":3,"sources":["../src/core/config.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { parse, stringify } from \"yaml\";\nimport type { GlobalConfig, ProjectConfig, RegistryMarker } from \"../types.js\";\n\n// ─── Paths ───────────────────────────────────────────────────────────────────\n\nexport const GLOBAL_DIR = path.join(os.homedir(), \".syncer\");\nexport const GLOBAL_CONFIG_PATH = path.join(GLOBAL_DIR, \"config.yaml\");\nexport const GLOBAL_STATE_PATH = path.join(GLOBAL_DIR, \"state.json\");\nexport const CACHE_DIR = path.join(GLOBAL_DIR, \"cache\");\n\nexport const PROJECT_CONFIG_FILE = \".syncer.yaml\"; // legacy root-level config\nexport const PROJECT_CONFIG_FILE_NEW = \".syncer/syncer.yaml\"; // new default location\nexport const PROJECT_LOCK_FILE = \".syncer/syncer.lock\";\nexport const PROJECT_CACHE_DIR = \".syncer\";\nexport const REGISTRY_MARKER_FILE = \".syncer-registry.yaml\";\n\n// ─── Context detection ───────────────────────────────────────────────────────\n\nexport type Context = \"project\" | \"registry\" | \"unconfigured\";\n\n/** Returns the absolute path to the project config, checking new location first. */\nexport function findProjectConfigPath(cwd: string): string | null {\n for (const rel of [PROJECT_CONFIG_FILE_NEW, PROJECT_CONFIG_FILE_NEW_YML, PROJECT_CONFIG_FILE]) {\n const p = path.join(cwd, rel);\n if (fs.existsSync(p)) return p;\n }\n return null;\n}\n\nexport function detectContext(cwd: string): Context {\n if (fs.existsSync(path.join(cwd, REGISTRY_MARKER_FILE))) return \"registry\";\n if (findProjectConfigPath(cwd)) return \"project\";\n return \"unconfigured\";\n}\n\n// ─── Project config ──────────────────────────────────────────────────────────\n\nexport function readProjectConfig(cwd: string): ProjectConfig {\n const configPath = findProjectConfigPath(cwd);\n if (!configPath) {\n throw new Error(\n `No syncer config found. Run \\`syncer init\\` to set up this project.`\n );\n }\n const raw = fs.readFileSync(configPath, \"utf8\");\n return parse(raw) as ProjectConfig;\n}\n\nexport function writeProjectConfig(cwd: string, config: ProjectConfig): void {\n // Write back to wherever the config currently lives; default to new location.\n const configPath = findProjectConfigPath(cwd) ?? path.join(cwd, PROJECT_CONFIG_FILE_NEW);\n fs.mkdirSync(path.dirname(configPath), { recursive: true });\n fs.writeFileSync(configPath, stringify(config), \"utf8\");\n}\n\n// ─── Registry marker ─────────────────────────────────────────────────────────\n\nexport function readRegistryMarker(cwd: string): RegistryMarker {\n const markerPath = path.join(cwd, REGISTRY_MARKER_FILE);\n const raw = fs.readFileSync(markerPath, \"utf8\");\n return parse(raw) as RegistryMarker;\n}\n\nexport function writeRegistryMarker(\n cwd: string,\n marker: RegistryMarker\n): void {\n const markerPath = path.join(cwd, REGISTRY_MARKER_FILE);\n fs.writeFileSync(markerPath, stringify(marker), \"utf8\");\n}\n\n// ─── Global config ───────────────────────────────────────────────────────────\n\nexport function readGlobalConfig(): GlobalConfig {\n if (!fs.existsSync(GLOBAL_CONFIG_PATH)) return {};\n const raw = fs.readFileSync(GLOBAL_CONFIG_PATH, \"utf8\");\n return (parse(raw) as GlobalConfig) ?? {};\n}\n\nexport function writeGlobalConfig(config: GlobalConfig): void {\n fs.mkdirSync(GLOBAL_DIR, { recursive: true });\n fs.writeFileSync(GLOBAL_CONFIG_PATH, stringify(config), \"utf8\");\n}\n\n// ─── Merged / resolved config ────────────────────────────────────────────────\n\nexport interface ResolvedConfig {\n registry: string;\n version: string;\n targets: (string | import(\"../types.js\").CustomTarget)[];\n link_mode: \"symlink\" | \"copy\";\n packs: string[];\n skills: { include: string[]; exclude: string[] };\n agents: { include: string[]; exclude: string[] };\n commands: { include: string[]; exclude: string[] };\n}\n\nexport function resolveConfig(\n project: ProjectConfig,\n global: GlobalConfig\n): ResolvedConfig {\n const registry =\n project.registry ?? global.default_registry ?? \"\";\n\n const defaultPack = global.default_pack;\n const packIncludes = project.packs?.include ?? [];\n const packs =\n packIncludes.length > 0\n ? packIncludes\n : defaultPack\n ? [defaultPack]\n : [];\n\n return {\n registry,\n version: project.version ?? \"latest\",\n targets: project.targets ?? [\"claude\"],\n link_mode: project.link_mode ?? \"symlink\",\n packs,\n skills: {\n include: project.skills?.include ?? [],\n exclude: project.skills?.exclude ?? [],\n },\n agents: {\n include: project.agents?.include ?? [],\n exclude: project.agents?.exclude ?? [],\n },\n commands: {\n include: project.commands?.include ?? [],\n exclude: project.commands?.exclude ?? [],\n },\n };\n}\n"],"mappings":"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","names":[]}
#!/usr/bin/env node
import {
ensureRegistry,
listRegistryBranches,
listRegistryTags,
registryCacheExists,
registryCachePath,
registryFile,
resolveRefType
} from "./chunk-LBLURIVB.js";
import "./chunk-N6JPW7IT.js";
import "./chunk-YOYA4EIK.js";
export {
ensureRegistry,
listRegistryBranches,
listRegistryTags,
registryCacheExists,
registryCachePath,
registryFile,
resolveRefType
};
//# sourceMappingURL=registry-YRN3VYE4.js.map
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
#!/usr/bin/env node
import {
applyOverrides,
listAvailableAgents,
listAvailableCommands,
listAvailablePacks,
listAvailableSkills,
loadPack,
resolvePacks,
validateRegistry
} from "./chunk-3PXPG3CQ.js";
import "./chunk-YOYA4EIK.js";
export {
applyOverrides,
listAvailableAgents,
listAvailableCommands,
listAvailablePacks,
listAvailableSkills,
loadPack,
resolvePacks,
validateRegistry
};
//# sourceMappingURL=resolver-LCDPSI5Y.js.map
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
+20
-1
{
"name": "@syncer/cli",
"version": "0.2.0",
"version": "0.3.0",
"description": "Keep AI agent skills, subagents, and commands consistent across all repositories",

@@ -21,2 +21,21 @@ "type": "module",

},
"repository": {
"type": "git",
"url": "https://github.com/not-ebx/syncer.git"
},
"homepage": "https://github.com/not-ebx/syncer#readme",
"bugs": {
"url": "https://github.com/not-ebx/syncer/issues"
},
"author": "not-ebx",
"keywords": [
"ai",
"agents",
"skills",
"claude",
"codex",
"sync",
"cli",
"registry"
],
"license": "MIT",

@@ -23,0 +42,0 @@ "devDependencies": {

+85
-37

@@ -1,19 +0,38 @@

# Syncer
<div align="center">
> One command, every repo has the right skills.
```
_____
/ ___/__ ______ ________ _____
\__ \/ / / / __ \/ ___/ _ \/ ___/
___/ / /_/ / / / / /__/ __/ /
/____/\__, /_/ /_/\___/\___/_/
/____/
```
Syncer is a lightweight Node.js CLI that keeps AI agent skills, custom subagents, and commands consistent across all repositories in an organization. It solves version drift, missing skills, and the manual overhead of copy-pasting files across repos.
**One command. Every repo has the right skills.**
[![npm version](https://img.shields.io/npm/v/@syncer/cli)](https://www.npmjs.com/package/@syncer/cli)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Node.js 20+](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org)
</div>
---
Syncer is a lightweight CLI that keeps AI agent skills, custom subagents, and slash commands consistent across every repository in your organization. It solves version drift, missing skills, and the manual overhead of copy-pasting files across repos — using a plain Git repo as the single source of truth.
## The Problem
When using AI coding agents (Claude Code, Codex, Gemini CLI, etc.) across multiple repositories:
When working with AI coding agents (Claude Code, Codex, Gemini CLI, etc.) across multiple repositories:
- **Version drift** — Repo A has v2 of a skill, Repo B has v1, Repo C has a fork
- **Missing skills** — New repos are created without any skills
- **Missing skills** — New repos are created without any skills configured
- **Inconsistent behavior** — The same prompt produces different results because the underlying skills differ
- **Manual overhead** — Keeping skills in sync requires copy-pasting files that nobody does consistently
- **Manual overhead** — Keeping files in sync requires copy-pasting that nobody does consistently
## How It Works
Syncer uses a standard Git repository as a **registry** — the single source of truth for all skills, agents, and commands in your org. Each project declares what it needs in a small `.syncer.yaml` config file. Running `syncer sync` fetches from the registry and creates symlinks into the AI agent tool directories (`.claude/`, `.codex/`, etc.).
Syncer uses a standard Git repository as a **registry** — the single source of truth for all skills, agents, and commands in your org. Each project declares what it needs in a `.syncer/syncer.yaml` config file. Running `syncer sync` fetches from the registry and symlinks everything into the AI agent tool directories. Gitignore files are auto-managed so only the config and lock file are committed — generated cache and symlinks stay local.

@@ -24,8 +43,10 @@ ```

skills/ my-project/
├── code-review/ → ├── .syncer.yaml (committed)
├── testing/ → ├── .syncer.lock (committed)
agents/ ├── .syncer/ (gitignored)
├── explorer.md → │ ├── skills/
commands/ │ └── agents/
└── lint.md → └── .claude/
├── code-review/ → ├── .syncer/ (committed)
├── testing/ → │ ├── syncer.yaml (config — committed)
agents/ │ ├── syncer.lock (lock — committed)
├── explorer.md → │ ├── .gitignore (auto-managed)
commands/ │ ├── skills/ (cache — gitignored)
└── lint.md → │ └── agents/ (cache — gitignored)
└── .claude/
├── .gitignore (auto-managed)
├── skills/code-review → ../../.syncer/skills/code-review

@@ -38,5 +59,11 @@ └── agents/explorer.md → ../../.syncer/agents/explorer.md

```bash
npm install -g syncer
npm install -g @syncer/cli
```
Or run without installing:
```bash
npx @syncer/cli sync
```
Requires Node.js 20+.

@@ -47,5 +74,2 @@

```bash
# Set up global defaults (optional)
syncer init --global
# In any project, run the interactive wizard

@@ -56,4 +80,4 @@ cd my-project

# → Detects AI agent tools (.claude/, .codex/, etc.)
# → Lists available packs from registry
# → Creates .syncer.yaml and runs first sync
# → Lists available packs from the registry
# → Creates .syncer/syncer.yaml and runs first sync

@@ -63,3 +87,3 @@ # On subsequent machines / new team members

cd some-project
syncer sync # everything is ready in <30 seconds
syncer sync # everything is ready in seconds
```

@@ -76,2 +100,3 @@

syncer sync --no-fetch # Re-resolve and re-link without fetching
syncer sync --prune # Remove symlinks for items no longer in config
syncer status # What's installed, is it current?

@@ -114,3 +139,3 @@ syncer status --all # All known projects' states

### Project config (`.syncer.yaml` — commit this)
### Project config (`.syncer/syncer.yaml` — commit this)

@@ -135,6 +160,6 @@ ```yaml

# version: latest # Track HEAD (default)
# version: v2.1.0 # Pin to a tag
# version: latest # Track HEAD (default)
# version: v2.1.0 # Pin to a tag
# version: feature/my-branch # Track a branch (testing only — non-deterministic)
# version: abc123f # Pin to a commit (maximum reproducibility)
# version: abc123f # Pin to a commit (maximum reproducibility)
```

@@ -165,3 +190,3 @@

syncer init --registry
# Creates .syncer-registry.yaml and the skills/, agents/, commands/, packs/ directories
# Creates .syncer-registry.yaml and the skills/, agents/, commands/ directories
```

@@ -175,3 +200,3 @@

skills-registry/
├── .syncer-registry.yaml
├── .syncer-registry.yaml ← registry config + all pack definitions
├── skills/

@@ -182,6 +207,4 @@ │ └── code-review/

│ └── explorer.md
├── commands/
│ └── lint.md
└── packs/
└── default.yaml
└── commands/
└── lint.md
```

@@ -191,15 +214,40 @@

Packs are named collections of skills, agents, and commands defined in the registry. They can extend other packs:
Packs are named collections of skills, agents, and commands defined directly in `.syncer-registry.yaml`. They support inheritance via `extends`:
```yaml
# packs/frontend.yaml
name: frontend
extends: default
skills:
- component-guidelines
- accessibility-check
# .syncer-registry.yaml
name: my-registry
packs:
default:
description: Core tools for every project
skills:
- code-review
- testing
agents:
- explorer
frontend:
description: Frontend-specific additions
extends: default
skills:
- component-guidelines
- accessibility-check
agents:
- design-reviewer
```
Projects include packs by name — when the pack is updated in the registry, everyone gets the update on their next `syncer sync`.
## Why Git as a Registry?
- **No new infrastructure** — your team already knows Git
- **Full history** — every change is audited, reversible, and reviewable via PRs
- **Pinning** — projects can pin to a tag or commit for stability, then upgrade deliberately
- **Private by default** — private repos work out of the box with existing SSH keys
## Contributing
Issues and PRs are welcome. See the repo for development setup.
## License
MIT
#!/usr/bin/env node
import {
ensureDir,
urlToKey
} from "./chunk-N6JPW7IT.js";
// src/core/registry.ts
import fs2 from "fs";
import path2 from "path";
import { simpleGit } from "simple-git";
// src/core/config.ts
import fs from "fs";
import path from "path";
import os from "os";
import { parse, stringify } from "yaml";
var GLOBAL_DIR = path.join(os.homedir(), ".syncer");
var GLOBAL_CONFIG_PATH = path.join(GLOBAL_DIR, "config.yaml");
var GLOBAL_STATE_PATH = path.join(GLOBAL_DIR, "state.json");
var CACHE_DIR = path.join(GLOBAL_DIR, "cache");
var PROJECT_CONFIG_FILE = ".syncer.yaml";
var PROJECT_LOCK_FILE = ".syncer.lock";
var PROJECT_CACHE_DIR = ".syncer";
var REGISTRY_MARKER_FILE = ".syncer-registry.yaml";
function detectContext(cwd) {
if (fs.existsSync(path.join(cwd, REGISTRY_MARKER_FILE))) return "registry";
if (fs.existsSync(path.join(cwd, PROJECT_CONFIG_FILE))) return "project";
return "unconfigured";
}
function readProjectConfig(cwd) {
const configPath = path.join(cwd, PROJECT_CONFIG_FILE);
if (!fs.existsSync(configPath)) {
throw new Error(
`No .syncer.yaml found. Run \`syncer init\` to set up this project.`
);
}
const raw = fs.readFileSync(configPath, "utf8");
return parse(raw);
}
function writeProjectConfig(cwd, config) {
const configPath = path.join(cwd, PROJECT_CONFIG_FILE);
fs.writeFileSync(configPath, stringify(config), "utf8");
}
function writeRegistryMarker(cwd, marker) {
const markerPath = path.join(cwd, REGISTRY_MARKER_FILE);
fs.writeFileSync(markerPath, stringify(marker), "utf8");
}
function readGlobalConfig() {
if (!fs.existsSync(GLOBAL_CONFIG_PATH)) return {};
const raw = fs.readFileSync(GLOBAL_CONFIG_PATH, "utf8");
return parse(raw) ?? {};
}
function writeGlobalConfig(config) {
fs.mkdirSync(GLOBAL_DIR, { recursive: true });
fs.writeFileSync(GLOBAL_CONFIG_PATH, stringify(config), "utf8");
}
function resolveConfig(project, global) {
const registry = project.registry ?? global.default_registry ?? "";
const defaultPack = global.default_pack;
const packIncludes = project.packs?.include ?? [];
const packs = packIncludes.length > 0 ? packIncludes : defaultPack ? [defaultPack] : [];
return {
registry,
version: project.version ?? "latest",
targets: project.targets ?? ["claude"],
link_mode: project.link_mode ?? "symlink",
packs,
skills: {
include: project.skills?.include ?? [],
exclude: project.skills?.exclude ?? []
},
agents: {
include: project.agents?.include ?? [],
exclude: project.agents?.exclude ?? []
},
commands: {
include: project.commands?.include ?? [],
exclude: project.commands?.exclude ?? []
}
};
}
// src/core/registry.ts
function registryCachePath(registryUrl) {
return path2.join(CACHE_DIR, urlToKey(registryUrl));
}
async function ensureRegistry(registryUrl, version = "latest") {
ensureDir(CACHE_DIR);
const cachePath = registryCachePath(registryUrl);
if (fs2.existsSync(cachePath)) {
return await fetchRegistry(cachePath, registryUrl, version);
} else {
return await cloneRegistry(cachePath, registryUrl, version);
}
}
async function cloneRegistry(cachePath, registryUrl, version) {
const git = simpleGit();
const isLatest = version === "latest";
try {
if (isLatest) {
await git.clone(registryUrl, cachePath, ["--depth=1"]);
} else {
await git.clone(registryUrl, cachePath);
}
const repoGit = simpleGit(cachePath);
if (!isLatest) {
await repoGit.checkout(version);
}
const commit = await getCommitHash(repoGit);
return { cachePath, commit, fromCache: false };
} catch (err) {
if (!fs2.existsSync(cachePath)) throw err;
fs2.rmSync(cachePath, { recursive: true, force: true });
throw err;
}
}
async function fetchRegistry(cachePath, registryUrl, version) {
const repoGit = simpleGit(cachePath);
try {
await repoGit.fetch(["--prune"]);
const isLatest = version === "latest";
if (isLatest) {
const defaultBranch = await getDefaultBranch(repoGit);
await repoGit.checkout(defaultBranch);
await repoGit.pull();
} else {
await repoGit.checkout(version);
const isBranchLike = !/^[0-9a-f]{7,40}$/.test(version);
if (isBranchLike) {
try {
await repoGit.pull();
} catch {
}
}
}
const commit = await getCommitHash(repoGit);
return { cachePath, commit, fromCache: false };
} catch {
const commit = await getCommitHash(repoGit).catch(() => "unknown");
return { cachePath, commit, fromCache: true };
}
}
async function getCommitHash(git) {
const result = await git.revparse(["HEAD"]);
return result.trim();
}
async function getDefaultBranch(git) {
try {
const result = await git.raw(["symbolic-ref", "refs/remotes/origin/HEAD"]);
return result.trim().replace("refs/remotes/origin/", "");
} catch {
const branches = await git.branch(["-r"]);
if (branches.all.includes("origin/main")) return "main";
return "master";
}
}
async function listRegistryTags(registryUrl) {
const cachePath = registryCachePath(registryUrl);
if (!fs2.existsSync(cachePath)) return [];
const git = simpleGit(cachePath);
const tags = await git.tags();
return tags.all;
}
async function listRegistryBranches(registryUrl) {
const cachePath = registryCachePath(registryUrl);
if (!fs2.existsSync(cachePath)) return [];
const git = simpleGit(cachePath);
const branches = await git.branch(["-r"]);
return branches.all.map((b) => b.trim().replace(/^origin\//, "")).filter((b) => b !== "HEAD");
}
async function resolveRefType(registryUrl, ref) {
if (/^[0-9a-f]{7,40}$/.test(ref)) return "commit";
const tags = await listRegistryTags(registryUrl);
if (tags.includes(ref)) return "tag";
const branches = await listRegistryBranches(registryUrl);
if (branches.includes(ref)) return "branch";
return null;
}
function registryFile(cachePath, ...segments) {
return path2.join(cachePath, ...segments);
}
function registryCacheExists(registryUrl) {
return fs2.existsSync(registryCachePath(registryUrl));
}
export {
GLOBAL_DIR,
GLOBAL_STATE_PATH,
CACHE_DIR,
PROJECT_CONFIG_FILE,
PROJECT_LOCK_FILE,
PROJECT_CACHE_DIR,
REGISTRY_MARKER_FILE,
detectContext,
readProjectConfig,
writeProjectConfig,
writeRegistryMarker,
readGlobalConfig,
writeGlobalConfig,
resolveConfig,
registryCachePath,
ensureRegistry,
listRegistryTags,
listRegistryBranches,
resolveRefType,
registryFile,
registryCacheExists
};
//# sourceMappingURL=chunk-24V2ACOX.js.map
{"version":3,"sources":["../src/core/registry.ts","../src/core/config.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { simpleGit } from \"simple-git\";\nimport { CACHE_DIR } from \"./config.js\";\nimport { ensureDir, urlToKey } from \"../utils/fs.js\";\n\n/** Returns the path to the registry clone for a given URL */\nexport function registryCachePath(registryUrl: string): string {\n return path.join(CACHE_DIR, urlToKey(registryUrl));\n}\n\nexport interface RegistryInfo {\n cachePath: string;\n commit: string;\n fromCache: boolean; // true if offline and using cached version\n}\n\n/**\n * Ensure the registry is cloned/fetched and checked out at the target version.\n * Returns the path to the working tree and the current commit hash.\n */\nexport async function ensureRegistry(\n registryUrl: string,\n version = \"latest\"\n): Promise<RegistryInfo> {\n ensureDir(CACHE_DIR);\n const cachePath = registryCachePath(registryUrl);\n\n if (fs.existsSync(cachePath)) {\n return await fetchRegistry(cachePath, registryUrl, version);\n } else {\n return await cloneRegistry(cachePath, registryUrl, version);\n }\n}\n\nasync function cloneRegistry(\n cachePath: string,\n registryUrl: string,\n version: string\n): Promise<RegistryInfo> {\n const git = simpleGit();\n const isLatest = version === \"latest\";\n\n try {\n if (isLatest) {\n await git.clone(registryUrl, cachePath, [\"--depth=1\"]);\n } else {\n // Full clone needed for tag/commit/branch checkout\n await git.clone(registryUrl, cachePath);\n }\n\n const repoGit = simpleGit(cachePath);\n if (!isLatest) {\n await repoGit.checkout(version);\n }\n\n const commit = await getCommitHash(repoGit);\n return { cachePath, commit, fromCache: false };\n } catch (err) {\n // If clone failed and no cache exists, rethrow\n if (!fs.existsSync(cachePath)) throw err;\n // Partial clone — clean up and rethrow\n fs.rmSync(cachePath, { recursive: true, force: true });\n throw err;\n }\n}\n\nasync function fetchRegistry(\n cachePath: string,\n registryUrl: string,\n version: string\n): Promise<RegistryInfo> {\n const repoGit = simpleGit(cachePath);\n\n try {\n await repoGit.fetch([\"--prune\"]);\n\n const isLatest = version === \"latest\";\n if (isLatest) {\n // Checkout default branch (main or master)\n const defaultBranch = await getDefaultBranch(repoGit);\n await repoGit.checkout(defaultBranch);\n await repoGit.pull();\n } else {\n await repoGit.checkout(version);\n // Pull only if it's a branch (not a tag or commit hash)\n const isBranchLike = !/^[0-9a-f]{7,40}$/.test(version);\n if (isBranchLike) {\n try {\n await repoGit.pull();\n } catch {\n // May not have upstream tracking — ignore\n }\n }\n }\n\n const commit = await getCommitHash(repoGit);\n return { cachePath, commit, fromCache: false };\n } catch {\n // Offline or fetch failed — use whatever is checked out\n const commit = await getCommitHash(repoGit).catch(() => \"unknown\");\n return { cachePath, commit, fromCache: true };\n }\n}\n\nasync function getCommitHash(git: ReturnType<typeof simpleGit>): Promise<string> {\n const result = await git.revparse([\"HEAD\"]);\n return result.trim();\n}\n\nasync function getDefaultBranch(\n git: ReturnType<typeof simpleGit>\n): Promise<string> {\n try {\n // Try to get the symbolic ref of origin/HEAD\n const result = await git.raw([\"symbolic-ref\", \"refs/remotes/origin/HEAD\"]);\n return result.trim().replace(\"refs/remotes/origin/\", \"\");\n } catch {\n // Fallback: try main, then master\n const branches = await git.branch([\"-r\"]);\n if (branches.all.includes(\"origin/main\")) return \"main\";\n return \"master\";\n }\n}\n\n/** List all available tags in the registry */\nexport async function listRegistryTags(\n registryUrl: string\n): Promise<string[]> {\n const cachePath = registryCachePath(registryUrl);\n if (!fs.existsSync(cachePath)) return [];\n const git = simpleGit(cachePath);\n const tags = await git.tags();\n return tags.all;\n}\n\n/** List all remote branches in the registry cache */\nexport async function listRegistryBranches(\n registryUrl: string\n): Promise<string[]> {\n const cachePath = registryCachePath(registryUrl);\n if (!fs.existsSync(cachePath)) return [];\n const git = simpleGit(cachePath);\n const branches = await git.branch([\"-r\"]);\n return branches.all\n .map((b) => b.trim().replace(/^origin\\//, \"\"))\n .filter((b) => b !== \"HEAD\");\n}\n\n/**\n * Determine what type of ref a version string is.\n * Returns null if the ref is not found in the cached registry.\n */\nexport async function resolveRefType(\n registryUrl: string,\n ref: string\n): Promise<\"tag\" | \"branch\" | \"commit\" | null> {\n if (/^[0-9a-f]{7,40}$/.test(ref)) return \"commit\";\n const tags = await listRegistryTags(registryUrl);\n if (tags.includes(ref)) return \"tag\";\n const branches = await listRegistryBranches(registryUrl);\n if (branches.includes(ref)) return \"branch\";\n return null;\n}\n\n/** Read a file from the registry cache */\nexport function registryFile(cachePath: string, ...segments: string[]): string {\n return path.join(cachePath, ...segments);\n}\n\n/** Check if the registry cache exists */\nexport function registryCacheExists(registryUrl: string): boolean {\n return fs.existsSync(registryCachePath(registryUrl));\n}\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { parse, stringify } from \"yaml\";\nimport type { GlobalConfig, ProjectConfig, RegistryMarker } from \"../types.js\";\n\n// ─── Paths ───────────────────────────────────────────────────────────────────\n\nexport const GLOBAL_DIR = path.join(os.homedir(), \".syncer\");\nexport const GLOBAL_CONFIG_PATH = path.join(GLOBAL_DIR, \"config.yaml\");\nexport const GLOBAL_STATE_PATH = path.join(GLOBAL_DIR, \"state.json\");\nexport const CACHE_DIR = path.join(GLOBAL_DIR, \"cache\");\n\nexport const PROJECT_CONFIG_FILE = \".syncer.yaml\";\nexport const PROJECT_LOCK_FILE = \".syncer.lock\";\nexport const PROJECT_CACHE_DIR = \".syncer\";\nexport const REGISTRY_MARKER_FILE = \".syncer-registry.yaml\";\n\n// ─── Context detection ───────────────────────────────────────────────────────\n\nexport type Context = \"project\" | \"registry\" | \"unconfigured\";\n\nexport function detectContext(cwd: string): Context {\n if (fs.existsSync(path.join(cwd, REGISTRY_MARKER_FILE))) return \"registry\";\n if (fs.existsSync(path.join(cwd, PROJECT_CONFIG_FILE))) return \"project\";\n return \"unconfigured\";\n}\n\n// ─── Project config ──────────────────────────────────────────────────────────\n\nexport function readProjectConfig(cwd: string): ProjectConfig {\n const configPath = path.join(cwd, PROJECT_CONFIG_FILE);\n if (!fs.existsSync(configPath)) {\n throw new Error(\n `No .syncer.yaml found. Run \\`syncer init\\` to set up this project.`\n );\n }\n const raw = fs.readFileSync(configPath, \"utf8\");\n return parse(raw) as ProjectConfig;\n}\n\nexport function writeProjectConfig(cwd: string, config: ProjectConfig): void {\n const configPath = path.join(cwd, PROJECT_CONFIG_FILE);\n fs.writeFileSync(configPath, stringify(config), \"utf8\");\n}\n\n// ─── Registry marker ─────────────────────────────────────────────────────────\n\nexport function readRegistryMarker(cwd: string): RegistryMarker {\n const markerPath = path.join(cwd, REGISTRY_MARKER_FILE);\n const raw = fs.readFileSync(markerPath, \"utf8\");\n return parse(raw) as RegistryMarker;\n}\n\nexport function writeRegistryMarker(\n cwd: string,\n marker: RegistryMarker\n): void {\n const markerPath = path.join(cwd, REGISTRY_MARKER_FILE);\n fs.writeFileSync(markerPath, stringify(marker), \"utf8\");\n}\n\n// ─── Global config ───────────────────────────────────────────────────────────\n\nexport function readGlobalConfig(): GlobalConfig {\n if (!fs.existsSync(GLOBAL_CONFIG_PATH)) return {};\n const raw = fs.readFileSync(GLOBAL_CONFIG_PATH, \"utf8\");\n return (parse(raw) as GlobalConfig) ?? {};\n}\n\nexport function writeGlobalConfig(config: GlobalConfig): void {\n fs.mkdirSync(GLOBAL_DIR, { recursive: true });\n fs.writeFileSync(GLOBAL_CONFIG_PATH, stringify(config), \"utf8\");\n}\n\n// ─── Merged / resolved config ────────────────────────────────────────────────\n\nexport interface ResolvedConfig {\n registry: string;\n version: string;\n targets: (string | import(\"../types.js\").CustomTarget)[];\n link_mode: \"symlink\" | \"copy\";\n packs: string[];\n skills: { include: string[]; exclude: string[] };\n agents: { include: string[]; exclude: string[] };\n commands: { include: string[]; exclude: string[] };\n}\n\nexport function resolveConfig(\n project: ProjectConfig,\n global: GlobalConfig\n): ResolvedConfig {\n const registry =\n project.registry ?? global.default_registry ?? \"\";\n\n const defaultPack = global.default_pack;\n const packIncludes = project.packs?.include ?? [];\n const packs =\n packIncludes.length > 0\n ? packIncludes\n : defaultPack\n ? [defaultPack]\n : [];\n\n return {\n registry,\n version: project.version ?? \"latest\",\n targets: project.targets ?? [\"claude\"],\n link_mode: project.link_mode ?? \"symlink\",\n packs,\n skills: {\n include: project.skills?.include ?? [],\n exclude: project.skills?.exclude ?? [],\n },\n agents: {\n include: project.agents?.include ?? [],\n exclude: project.agents?.exclude ?? [],\n },\n commands: {\n include: project.commands?.include ?? [],\n exclude: project.commands?.exclude ?? [],\n },\n };\n}\n"],"mappings":"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","names":["fs","path","path","fs"]}
#!/usr/bin/env node
// src/core/resolver.ts
import fs from "fs";
import path from "path";
import { parse } from "yaml";
function loadPack(registryPath, packName) {
const packFile = path.join(registryPath, "packs", `${packName}.yaml`);
if (!fs.existsSync(packFile)) {
throw new Error(`Pack "${packName}" not found in registry (${packFile})`);
}
const raw = fs.readFileSync(packFile, "utf8");
return parse(raw);
}
function listAvailablePacks(registryPath) {
const packsDir = path.join(registryPath, "packs");
if (!fs.existsSync(packsDir)) return [];
return fs.readdirSync(packsDir).filter((f) => f.endsWith(".yaml")).map((f) => f.replace(/\.yaml$/, ""));
}
function listAvailableSkills(registryPath) {
const dir = path.join(registryPath, "skills");
if (!fs.existsSync(dir)) return [];
return fs.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
}
function listAvailableAgents(registryPath) {
const dir = path.join(registryPath, "agents");
if (!fs.existsSync(dir)) return [];
return fs.readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
}
function listAvailableCommands(registryPath) {
const dir = path.join(registryPath, "commands");
if (!fs.existsSync(dir)) return [];
return fs.readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
}
function resolvePacks(registryPath, packNames) {
const merged = { skills: [], agents: [], commands: [] };
for (const packName of packNames) {
const resolved = resolveOnePack(registryPath, packName, []);
mergeInto(merged, resolved);
}
return {
skills: dedupe(merged.skills),
agents: dedupe(merged.agents),
commands: dedupe(merged.commands)
};
}
function resolveOnePack(registryPath, packName, chain) {
if (chain.includes(packName)) {
throw new Error(
`Circular pack extends detected: ${[...chain, packName].join(" \u2192 ")}`
);
}
const pack = loadPack(registryPath, packName);
const result = { skills: [], agents: [], commands: [] };
if (pack.extends) {
const parent = resolveOnePack(registryPath, pack.extends, [
...chain,
packName
]);
mergeInto(result, parent);
}
if (pack.skills) result.skills.push(...pack.skills);
if (pack.agents) result.agents.push(...pack.agents);
if (pack.commands) result.commands.push(...pack.commands);
return result;
}
function applyOverrides(base, config) {
return {
skills: applyOverride(
base.skills,
config.skills.include,
config.skills.exclude
),
agents: applyOverride(
base.agents,
config.agents.include,
config.agents.exclude
),
commands: applyOverride(
base.commands,
config.commands.include,
config.commands.exclude
)
};
}
function applyOverride(base, include, exclude) {
const set = /* @__PURE__ */ new Set([...base, ...include]);
for (const item of exclude) set.delete(item);
return Array.from(set);
}
function validateRegistry(registryPath) {
const errors = [];
const skillsDir = path.join(registryPath, "skills");
if (fs.existsSync(skillsDir)) {
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
const skillMd = path.join(skillsDir, entry.name, "SKILL.md");
if (!fs.existsSync(skillMd)) {
errors.push(`Skill "${entry.name}" is missing SKILL.md`);
}
}
}
const packNames = listAvailablePacks(registryPath);
const availableSkills = new Set(listAvailableSkills(registryPath));
const availableAgents = new Set(listAvailableAgents(registryPath));
const availableCommands = new Set(listAvailableCommands(registryPath));
for (const packName of packNames) {
try {
const pack = loadPack(registryPath, packName);
if (pack.extends && !packNames.includes(pack.extends)) {
errors.push(
`Pack "${packName}" extends unknown pack "${pack.extends}"`
);
}
try {
resolveOnePack(registryPath, packName, []);
} catch (err) {
errors.push(String(err));
}
for (const skill of pack.skills ?? []) {
if (!availableSkills.has(skill)) {
errors.push(
`Pack "${packName}" references unknown skill "${skill}"`
);
}
}
for (const agent of pack.agents ?? []) {
if (!availableAgents.has(agent)) {
errors.push(
`Pack "${packName}" references unknown agent "${agent}"`
);
}
}
for (const cmd of pack.commands ?? []) {
if (!availableCommands.has(cmd)) {
errors.push(
`Pack "${packName}" references unknown command "${cmd}"`
);
}
}
} catch (err) {
errors.push(`Pack "${packName}" failed to load: ${err}`);
}
}
return { valid: errors.length === 0, errors };
}
function mergeInto(target, source) {
target.skills.push(...source.skills);
target.agents.push(...source.agents);
target.commands.push(...source.commands);
}
function dedupe(arr) {
return Array.from(new Set(arr));
}
export {
loadPack,
listAvailablePacks,
listAvailableSkills,
listAvailableAgents,
listAvailableCommands,
resolvePacks,
applyOverrides,
validateRegistry
};
//# sourceMappingURL=chunk-P4W7ZFI4.js.map
{"version":3,"sources":["../src/core/resolver.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { parse } from \"yaml\";\nimport type { PackDef, ResolvedContent } from \"../types.js\";\nimport type { ResolvedConfig } from \"./config.js\";\n\n// ─── Pack loading ─────────────────────────────────────────────────────────────\n\nexport function loadPack(registryPath: string, packName: string): PackDef {\n const packFile = path.join(registryPath, \"packs\", `${packName}.yaml`);\n if (!fs.existsSync(packFile)) {\n throw new Error(`Pack \"${packName}\" not found in registry (${packFile})`);\n }\n const raw = fs.readFileSync(packFile, \"utf8\");\n return parse(raw) as PackDef;\n}\n\n/** List all available pack names in the registry */\nexport function listAvailablePacks(registryPath: string): string[] {\n const packsDir = path.join(registryPath, \"packs\");\n if (!fs.existsSync(packsDir)) return [];\n return fs\n .readdirSync(packsDir)\n .filter((f) => f.endsWith(\".yaml\"))\n .map((f) => f.replace(/\\.yaml$/, \"\"));\n}\n\n/** List all available skills in the registry */\nexport function listAvailableSkills(registryPath: string): string[] {\n const dir = path.join(registryPath, \"skills\");\n if (!fs.existsSync(dir)) return [];\n return fs\n .readdirSync(dir, { withFileTypes: true })\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n}\n\n/** List all available agents in the registry */\nexport function listAvailableAgents(registryPath: string): string[] {\n const dir = path.join(registryPath, \"agents\");\n if (!fs.existsSync(dir)) return [];\n return fs\n .readdirSync(dir)\n .filter((f) => f.endsWith(\".md\"))\n .map((f) => f.replace(/\\.md$/, \"\"));\n}\n\n/** List all available commands in the registry */\nexport function listAvailableCommands(registryPath: string): string[] {\n const dir = path.join(registryPath, \"commands\");\n if (!fs.existsSync(dir)) return [];\n return fs\n .readdirSync(dir)\n .filter((f) => f.endsWith(\".md\"))\n .map((f) => f.replace(/\\.md$/, \"\"));\n}\n\n// ─── Pack resolution ─────────────────────────────────────────────────────────\n\n/**\n * Resolve a list of pack names into merged skills/agents/commands.\n * Handles `extends` chains recursively with cycle detection.\n */\nexport function resolvePacks(\n registryPath: string,\n packNames: string[]\n): ResolvedContent {\n const merged: ResolvedContent = { skills: [], agents: [], commands: [] };\n\n for (const packName of packNames) {\n const resolved = resolveOnePack(registryPath, packName, []);\n mergeInto(merged, resolved);\n }\n\n return {\n skills: dedupe(merged.skills),\n agents: dedupe(merged.agents),\n commands: dedupe(merged.commands),\n };\n}\n\nfunction resolveOnePack(\n registryPath: string,\n packName: string,\n chain: string[]\n): ResolvedContent {\n if (chain.includes(packName)) {\n throw new Error(\n `Circular pack extends detected: ${[...chain, packName].join(\" → \")}`\n );\n }\n\n const pack = loadPack(registryPath, packName);\n const result: ResolvedContent = { skills: [], agents: [], commands: [] };\n\n // Resolve parent first (extends)\n if (pack.extends) {\n const parent = resolveOnePack(registryPath, pack.extends, [\n ...chain,\n packName,\n ]);\n mergeInto(result, parent);\n }\n\n // Add this pack's own items\n if (pack.skills) result.skills.push(...pack.skills);\n if (pack.agents) result.agents.push(...pack.agents);\n if (pack.commands) result.commands.push(...pack.commands);\n\n return result;\n}\n\n// ─── Override application ────────────────────────────────────────────────────\n\n/**\n * Apply project-level include/exclude overrides on top of resolved pack content.\n */\nexport function applyOverrides(\n base: ResolvedContent,\n config: ResolvedConfig\n): ResolvedContent {\n return {\n skills: applyOverride(\n base.skills,\n config.skills.include,\n config.skills.exclude\n ),\n agents: applyOverride(\n base.agents,\n config.agents.include,\n config.agents.exclude\n ),\n commands: applyOverride(\n base.commands,\n config.commands.include,\n config.commands.exclude\n ),\n };\n}\n\nfunction applyOverride(\n base: string[],\n include: string[],\n exclude: string[]\n): string[] {\n const set = new Set([...base, ...include]);\n for (const item of exclude) set.delete(item);\n return Array.from(set);\n}\n\n// ─── Validation (registry mode) ──────────────────────────────────────────────\n\nexport interface ValidationResult {\n valid: boolean;\n errors: string[];\n}\n\nexport function validateRegistry(registryPath: string): ValidationResult {\n const errors: string[] = [];\n\n // Validate skills\n const skillsDir = path.join(registryPath, \"skills\");\n if (fs.existsSync(skillsDir)) {\n for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {\n if (!entry.isDirectory()) continue;\n const skillMd = path.join(skillsDir, entry.name, \"SKILL.md\");\n if (!fs.existsSync(skillMd)) {\n errors.push(`Skill \"${entry.name}\" is missing SKILL.md`);\n }\n }\n }\n\n // Validate packs\n const packNames = listAvailablePacks(registryPath);\n const availableSkills = new Set(listAvailableSkills(registryPath));\n const availableAgents = new Set(listAvailableAgents(registryPath));\n const availableCommands = new Set(listAvailableCommands(registryPath));\n\n for (const packName of packNames) {\n try {\n const pack = loadPack(registryPath, packName);\n\n // Validate extends reference\n if (pack.extends && !packNames.includes(pack.extends)) {\n errors.push(\n `Pack \"${packName}\" extends unknown pack \"${pack.extends}\"`\n );\n }\n\n // Validate extends chains for cycles\n try {\n resolveOnePack(registryPath, packName, []);\n } catch (err) {\n errors.push(String(err));\n }\n\n // Validate referenced content exists\n for (const skill of pack.skills ?? []) {\n if (!availableSkills.has(skill)) {\n errors.push(\n `Pack \"${packName}\" references unknown skill \"${skill}\"`\n );\n }\n }\n for (const agent of pack.agents ?? []) {\n if (!availableAgents.has(agent)) {\n errors.push(\n `Pack \"${packName}\" references unknown agent \"${agent}\"`\n );\n }\n }\n for (const cmd of pack.commands ?? []) {\n if (!availableCommands.has(cmd)) {\n errors.push(\n `Pack \"${packName}\" references unknown command \"${cmd}\"`\n );\n }\n }\n } catch (err) {\n errors.push(`Pack \"${packName}\" failed to load: ${err}`);\n }\n }\n\n return { valid: errors.length === 0, errors };\n}\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\nfunction mergeInto(target: ResolvedContent, source: ResolvedContent): void {\n target.skills.push(...source.skills);\n target.agents.push(...source.agents);\n target.commands.push(...source.commands);\n}\n\nfunction dedupe(arr: string[]): string[] {\n return Array.from(new Set(arr));\n}\n"],"mappings":"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","names":[]}
#!/usr/bin/env node
import {
ensureRegistry,
listRegistryBranches,
listRegistryTags,
registryCacheExists,
registryCachePath,
registryFile,
resolveRefType
} from "./chunk-24V2ACOX.js";
import "./chunk-N6JPW7IT.js";
export {
ensureRegistry,
listRegistryBranches,
listRegistryTags,
registryCacheExists,
registryCachePath,
registryFile,
resolveRefType
};
//# sourceMappingURL=registry-VV4RSYRL.js.map
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
#!/usr/bin/env node
import {
applyOverrides,
listAvailableAgents,
listAvailableCommands,
listAvailablePacks,
listAvailableSkills,
loadPack,
resolvePacks,
validateRegistry
} from "./chunk-P4W7ZFI4.js";
export {
applyOverrides,
listAvailableAgents,
listAvailableCommands,
listAvailablePacks,
listAvailableSkills,
loadPack,
resolvePacks,
validateRegistry
};
//# sourceMappingURL=resolver-NEXMV5VE.js.map
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display