feat: add copySymlinks option and better default excludes for workspace sync

This commit is contained in:
Ash Brener 2026-01-28 15:01:38 +02:00
parent 38230547d6
commit 65bfb9f9d7
6 changed files with 30 additions and 1 deletions

View File

@ -162,6 +162,7 @@ export function registerWorkspaceCli(program: Command): void {
localPath: resolved.localPath,
conflictResolve: resolved.conflictResolve,
exclude: resolved.exclude,
copySymlinks: resolved.copySymlinks,
resync: opts.resync,
dryRun: opts.dryRun,
verbose: opts.verbose,
@ -563,6 +564,7 @@ export function registerWorkspaceCli(program: Command): void {
localPath: resolved.localPath,
conflictResolve: resolved.conflictResolve,
exclude: resolved.exclude,
copySymlinks: resolved.copySymlinks,
resync: true,
});

View File

@ -76,9 +76,17 @@ export type WorkspaceSyncConfig = {
/**
* File patterns to exclude from sync (glob patterns).
* Defaults include: .git/**, node_modules/**, .venv/**, *.log, .DS_Store
*/
exclude?: string[];
/**
* Follow symlinks during sync (default: false).
* When false, symlinks are skipped with a notice.
* When true, symlinks are followed and their targets are copied.
*/
copySymlinks?: boolean;
/**
* S3-specific configuration (when provider is "s3").
*/

View File

@ -552,6 +552,7 @@ export const MoltbotSchema = z
.union([z.literal("newer"), z.literal("local"), z.literal("remote")])
.optional(),
exclude: z.array(z.string()).optional(),
copySymlinks: z.boolean().optional(),
s3: z
.object({
endpoint: z.string().optional(),

View File

@ -103,6 +103,7 @@ async function runSync(): Promise<void> {
localPath: resolved.localPath,
conflictResolve: resolved.conflictResolve,
exclude: resolved.exclude,
copySymlinks: resolved.copySymlinks,
resync: needsResync,
});
@ -116,6 +117,7 @@ async function runSync(): Promise<void> {
localPath: resolved.localPath,
conflictResolve: resolved.conflictResolve,
exclude: resolved.exclude,
copySymlinks: resolved.copySymlinks,
resync: true,
});
}

View File

@ -95,6 +95,7 @@ const workspaceSyncHandler: HookHandler = async (event) => {
localPath: resolved.localPath,
conflictResolve: resolved.conflictResolve,
exclude: resolved.exclude,
copySymlinks: resolved.copySymlinks,
verbose: false,
});

View File

@ -11,7 +11,14 @@ const DEFAULT_REMOTE_NAME = "cloud";
const DEFAULT_LOCAL_PATH = "shared";
const DEFAULT_REMOTE_PATH = "moltbot-share";
const DEFAULT_CONFLICT_RESOLVE = "newer";
const DEFAULT_EXCLUDES = [".git/**", "node_modules/**", "*.log", ".DS_Store"];
const DEFAULT_EXCLUDES = [
".git/**",
"node_modules/**",
".venv/**",
"__pycache__/**",
"*.log",
".DS_Store",
];
export type RcloneSyncResult = {
ok: boolean;
@ -171,6 +178,7 @@ export function resolveSyncConfig(
configPath: string;
conflictResolve: "newer" | "local" | "remote";
exclude: string[];
copySymlinks: boolean;
interval: number;
onSessionStart: boolean;
onSessionEnd: boolean;
@ -183,6 +191,7 @@ export function resolveSyncConfig(
configPath: config?.configPath ?? getDefaultRcloneConfigPath(stateDir),
conflictResolve: config?.conflictResolve ?? DEFAULT_CONFLICT_RESOLVE,
exclude: config?.exclude ?? DEFAULT_EXCLUDES,
copySymlinks: config?.copySymlinks ?? false,
interval: config?.interval ?? 0,
onSessionStart: config?.onSessionStart ?? false,
onSessionEnd: config?.onSessionEnd ?? false,
@ -398,6 +407,7 @@ export async function runBisync(params: {
localPath: string;
conflictResolve: "newer" | "local" | "remote";
exclude: string[];
copySymlinks?: boolean;
resync?: boolean;
dryRun?: boolean;
verbose?: boolean;
@ -426,6 +436,11 @@ export async function runBisync(params: {
args.push("--exclude", pattern);
}
// Follow symlinks if configured
if (params.copySymlinks) {
args.push("--copy-links");
}
if (params.resync) {
args.push("--resync");
}