Offload any development environment to a VM with 1 command. Authentication, agent context, environment, and dependencies are automatically transfered.
npx freestyle-sync
On it's first run, freestyle-sync creates freestyle-sync.config.mjs and configures a large set of default plugins. Plugins help sync parts of your development environment that require more complex logic than just copying the current directory's files.
import { defineConfig } from "freestyle-sync";
import { claudeAgentPlugin } from "@freestyle-sync/agent-claude";
import { codexAgentPlugin } from "@freestyle-sync/agent-codex";
import { copilotAgentPlugin } from "@freestyle-sync/agent-copilot";
import { astroPlugin } from "@freestyle-sync/astro";
import { awsAuthPlugin } from "@freestyle-sync/auth-aws";
import { azureAuthPlugin } from "@freestyle-sync/auth-azure";
import { dockerAuthPlugin } from "@freestyle-sync/auth-docker";
import { envAuthPlugin } from "@freestyle-sync/auth-env";
import { gcloudAuthPlugin } from "@freestyle-sync/auth-gcloud";
import { gitAuthPlugin } from "@freestyle-sync/auth-git";
import { githubCliAuthPlugin } from "@freestyle-sync/auth-github-cli";
import { npmAuthPlugin } from "@freestyle-sync/auth-npm";
import { sshAuthPlugin } from "@freestyle-sync/auth-ssh";
import { wranglerAuthPlugin } from "@freestyle-sync/auth-wrangler";
import { yarnAuthPlugin } from "@freestyle-sync/auth-yarn";
import { nextjsPlugin } from "@freestyle-sync/nextjs";
import { nodeNpmPlugin } from "@freestyle-sync/node-npm";
import { rustPlugin } from "@freestyle-sync/rust";
import { shellHistoryPlugin } from "@freestyle-sync/shell-history";
import { vitePlugin } from "@freestyle-sync/vite";
export default defineConfig({
plugins: [
envAuthPlugin(),
gitAuthPlugin(),
sshAuthPlugin(),
githubCliAuthPlugin(),
npmAuthPlugin(),
yarnAuthPlugin(),
wranglerAuthPlugin(),
dockerAuthPlugin(),
awsAuthPlugin(),
azureAuthPlugin(),
gcloudAuthPlugin(),
nodeNpmPlugin(),
astroPlugin(),
nextjsPlugin(),
vitePlugin(),
rustPlugin(),
claudeAgentPlugin(),
codexAgentPlugin(),
copilotAgentPlugin(),
shellHistoryPlugin(),
],
});You can also add personal plugins without changing the committed config. Put a local config at .freestyle-sync/config.mjs or .freestyle-sync/config.ts; .freestyle-sync is ignored by default, and its plugins are appended after the project config.
import { defineConfig } from "freestyle-sync";
import { myLocalPlugin } from "my-local-plugin";
export default defineConfig({
plugins: [
myLocalPlugin(),
],
});If the local config adds plugins that have not been configured on this machine yet, freestyle-sync will ask whether to enable them on the next interactive sync.
The default Node/framework/runtime plugins avoid uploading generated dependency and build output folders: You can exclude folders and include additional local paths. Include paths may point outside the project directory; they are copied into the remote project at the configured relative target.
export default defineConfig({
sync: {
exclude: ["dist", ".turbo"],
include: [
{ source: "../shared-config", target: "shared-config" },
],
}
});If you're integrating freestyle-sync into your own toolchain, you can use it as an sdk and take full control over how configuration and cache is stored.
import { defineConfig, sync, type SyncCache } from "freestyle-sync";
import { gitAuthPlugin } from "@freestyle-sync/auth-git";
import { nodeNpmPlugin } from "@freestyle-sync/node-npm";
let cache: SyncCache | undefined;
const result = await sync({
config: defineConfig({
plugins: [gitAuthPlugin(), nodeNpmPlugin()],
}),
options: {
projectRoot: process.cwd(),
autoSsh: false,
},
cache,
onCacheUpdate(nextCache) {
cache = nextCache;
},
});
console.log("synced runtime", result.vmId);