freestyle-sync 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +2 -1
  2. package/{freestyle-sync.config.ts → dist/freestyle-sync.config.js} +2 -3
  3. package/dist/main.js +1319 -0
  4. package/{plugins/agent-claude/src/index.ts → dist/plugins/agent-claude/src/index.js} +32 -29
  5. package/{plugins/agent-codex/src/index.ts → dist/plugins/agent-codex/src/index.js} +13 -14
  6. package/{plugins/agent-copilot/src/index.ts → dist/plugins/agent-copilot/src/index.js} +151 -164
  7. package/{plugins/auth-aws/src/index.ts → dist/plugins/auth-aws/src/index.js} +14 -8
  8. package/{plugins/auth-azure/src/index.ts → dist/plugins/auth-azure/src/index.js} +14 -8
  9. package/dist/plugins/auth-context.js +213 -0
  10. package/{plugins/auth-docker/src/index.ts → dist/plugins/auth-docker/src/index.js} +14 -8
  11. package/{plugins/auth-env/src/index.ts → dist/plugins/auth-env/src/index.js} +11 -11
  12. package/{plugins/auth-gcloud/src/index.ts → dist/plugins/auth-gcloud/src/index.js} +14 -8
  13. package/{plugins/auth-git/src/index.ts → dist/plugins/auth-git/src/index.js} +24 -17
  14. package/{plugins/auth-github-cli/src/index.ts → dist/plugins/auth-github-cli/src/index.js} +20 -14
  15. package/{plugins/auth-npm/src/index.ts → dist/plugins/auth-npm/src/index.js} +19 -13
  16. package/{plugins/auth-ssh/src/index.ts → dist/plugins/auth-ssh/src/index.js} +19 -13
  17. package/dist/plugins/auth-yarn/src/index.js +24 -0
  18. package/{plugins/node-npm/src/index.ts → dist/plugins/node-npm/src/index.js} +6 -8
  19. package/dist/plugins/npm-native-deps.js +307 -0
  20. package/{plugins/shell-history/src/index.ts → dist/plugins/shell-history/src/index.js} +13 -12
  21. package/{plugins/vscode/src/index.ts → dist/plugins/vscode/src/index.js} +38 -40
  22. package/{src/main.ts → dist/src/main.js} +406 -463
  23. package/dist/src/plugin-api.js +6 -0
  24. package/dist/src/pushvm.config.js +36 -0
  25. package/package.json +8 -4
  26. package/PUBLISHING.md +0 -3
  27. package/plugins/agent-claude/package.json +0 -8
  28. package/plugins/agent-codex/package.json +0 -8
  29. package/plugins/agent-copilot/package.json +0 -8
  30. package/plugins/auth-aws/package.json +0 -8
  31. package/plugins/auth-azure/package.json +0 -8
  32. package/plugins/auth-docker/package.json +0 -8
  33. package/plugins/auth-env/package.json +0 -8
  34. package/plugins/auth-gcloud/package.json +0 -8
  35. package/plugins/auth-git/package.json +0 -8
  36. package/plugins/auth-github-cli/package.json +0 -8
  37. package/plugins/auth-npm/package.json +0 -8
  38. package/plugins/auth-ssh/package.json +0 -8
  39. package/plugins/auth-yarn/package.json +0 -8
  40. package/plugins/auth-yarn/src/index.ts +0 -19
  41. package/plugins/node-npm/package.json +0 -8
  42. package/plugins/shell-history/package.json +0 -8
  43. package/plugins/vscode/package.json +0 -8
  44. package/src/plugin-api.ts +0 -107
  45. package/tsconfig.json +0 -18
@@ -1,23 +1,29 @@
1
1
  import { stat } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
- import { definePlugin, type ContextCandidate } from "../../../src/plugin-api.ts";
5
-
4
+ import { definePlugin } from "../../../src/plugin-api.js";
6
5
  export function azureAuthPlugin() {
7
6
  return definePlugin({
8
7
  name: "@freestyle-sync/auth-azure",
9
8
  async discoverContextCandidates({ options }) {
10
- if (!options.includeAuth) return [];
11
- const item: ContextCandidate = { source: path.join(homedir(), ".azure"), remoteRoot: "/root/.azure", label: "Azure credentials", sensitive: true, preferenceKey: "context:azure", promptLabel: "Azure credentials" };
9
+ if (!options.includeAuth)
10
+ return [];
11
+ const item = { source: path.join(homedir(), ".azure"), remoteRoot: "/root/.azure", label: "Azure credentials", sensitive: true, preferenceKey: "context:azure", promptLabel: "Azure credentials" };
12
12
  return await exists(item.source) ? [item] : [];
13
13
  },
14
14
  async afterContextSync({ vm, changedRemotePaths, utils }) {
15
- if (!changedRemotePaths.some((remotePath) => remotePath === "/root/.azure" || remotePath.startsWith("/root/.azure/"))) return;
15
+ if (!changedRemotePaths.some((remotePath) => remotePath === "/root/.azure" || remotePath.startsWith("/root/.azure/")))
16
+ return;
16
17
  await utils.checkedExec(vm, "chown -R root:root /root/.azure 2>/dev/null || true; chmod 700 /root/.azure 2>/dev/null || true");
17
18
  },
18
19
  });
19
20
  }
20
-
21
- async function exists(filePath: string) {
22
- try { await stat(filePath); return true; } catch { return false; }
21
+ async function exists(filePath) {
22
+ try {
23
+ await stat(filePath);
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
23
29
  }
@@ -0,0 +1,213 @@
1
+ import { stat, readFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import path from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ export async function discoverContextCandidates(options) {
6
+ const candidates = [];
7
+ const home = homedir();
8
+ if (options.includeAuth) {
9
+ addCandidate(candidates, path.join(home, ".gitconfig"), "~/.gitconfig", "git config", true, "context:git-config");
10
+ addCandidate(candidates, path.join(home, ".git-credentials"), "~/.git-credentials", "git credentials", true, "context:git-credentials");
11
+ addCandidate(candidates, path.join(home, ".netrc"), "~/.netrc", "netrc credentials", true, "context:netrc");
12
+ addCandidate(candidates, path.join(home, ".ssh"), "~/.ssh", "ssh credentials", true, "context:ssh");
13
+ addCandidate(candidates, path.join(home, ".config", "git"), "~/.config/git", "git config directory", true, "context:git-config-directory");
14
+ addCandidate(candidates, path.join(home, ".config", "gh"), "~/.config/gh", "GitHub CLI auth", true, "context:github-cli");
15
+ addCandidate(candidates, path.join(home, ".npmrc"), "~/.npmrc", "npm auth", true, "context:npm-auth");
16
+ addCandidate(candidates, path.join(home, ".yarnrc.yml"), "~/.yarnrc.yml", "Yarn auth", true, "context:yarn-auth");
17
+ addCandidate(candidates, path.join(home, ".docker", "config.json"), "~/.docker/config.json", "Docker auth", true, "context:docker-auth");
18
+ addCandidate(candidates, path.join(home, ".aws"), "~/.aws", "AWS credentials", true, "context:aws");
19
+ addCandidate(candidates, path.join(home, ".azure"), "~/.azure", "Azure credentials", true, "context:azure");
20
+ addCandidate(candidates, path.join(home, ".config", "gcloud"), "~/.config/gcloud", "Google Cloud auth", true, "context:gcloud");
21
+ }
22
+ if (options.includeAgentContext) {
23
+ addCandidate(candidates, path.join(home, ".claude"), "~/.claude", "Claude conversations", true, "context:claude-conversations");
24
+ addCandidate(candidates, path.join(home, ".claude.json"), "~/.claude.json", "Claude settings", true, "context:claude-settings");
25
+ addCandidate(candidates, path.join(home, ".codex"), "~/.codex", "Codex conversations", true, "context:codex-conversations");
26
+ for (const codeRoot of codeUserRoots(home)) {
27
+ await addCopilotCandidates(candidates, codeRoot, options);
28
+ }
29
+ }
30
+ const existing = [];
31
+ for (const candidate of candidates) {
32
+ if (await exists(candidate.source)) {
33
+ existing.push(candidate);
34
+ }
35
+ }
36
+ return dedupeCandidates(existing);
37
+ }
38
+ export function collectEnvironment(extraKeys) {
39
+ const allow = new Set(extraKeys);
40
+ const env = {};
41
+ const tokenPattern = /(^|_)(TOKEN|SECRET|PASSWORD|PASS|API_KEY|ACCESS_KEY|PRIVATE_KEY|AUTH|CREDENTIALS?)(_|$)/i;
42
+ const exactNames = new Set([
43
+ "ANTHROPIC_API_KEY",
44
+ "CODEX_HOME",
45
+ "GH_TOKEN",
46
+ "GITHUB_TOKEN",
47
+ "GIT_ASKPASS",
48
+ "NPM_TOKEN",
49
+ "OPENAI_API_KEY",
50
+ "FREESTYLE_API_KEY",
51
+ ]);
52
+ for (const [key, value] of Object.entries(process.env)) {
53
+ if (!value)
54
+ continue;
55
+ if (!isValidEnvName(key))
56
+ continue;
57
+ if (allow.has(key) || exactNames.has(key) || tokenPattern.test(key)) {
58
+ env[key] = value;
59
+ }
60
+ }
61
+ return env;
62
+ }
63
+ export function shouldSkipContextDirectory(_relativePath, _name) {
64
+ return false;
65
+ }
66
+ export function isProtectedRemotePath(remotePath) {
67
+ return remotePath === "/root/.ssh/authorized_keys" || remotePath === "/root/.ssh/authorized_keys2";
68
+ }
69
+ export async function hardenRemoteSecrets(vm, changedRemotePaths) {
70
+ const commands = ["chown root:root /root && chmod 700 /root"];
71
+ const touchedRoots = new Set();
72
+ for (const remotePath of changedRemotePaths) {
73
+ const secretRoot = remoteSecretRoot(remotePath);
74
+ if (secretRoot)
75
+ touchedRoots.add(secretRoot);
76
+ }
77
+ for (const secretRoot of touchedRoots) {
78
+ commands.push(`chown -R root:root ${shellQuote(secretRoot)} 2>/dev/null || true`);
79
+ commands.push(`chmod 700 ${shellQuote(secretRoot)} 2>/dev/null || true`);
80
+ }
81
+ if (touchedRoots.has("/root/.ssh")) {
82
+ commands.push("find /root/.ssh -type f ! -name authorized_keys ! -name authorized_keys2 -exec chmod 600 {} + 2>/dev/null || true");
83
+ }
84
+ for (const filePath of ["/root/.git-credentials", "/root/.netrc", "/root/.npmrc", "/root/.docker/config.json"]) {
85
+ if (changedRemotePaths.includes(filePath)) {
86
+ commands.push(`chmod 600 ${shellQuote(filePath)} 2>/dev/null || true`);
87
+ }
88
+ }
89
+ await checkedExec(vm, commands.join("; "));
90
+ }
91
+ function addCandidate(candidates, source, remoteRoot, label, sensitive, preferenceKey = `context:${label}:${remoteRoot}`, promptLabel = label) {
92
+ candidates.push({ source, remoteRoot: expandRemoteHome(remoteRoot), label, sensitive, preferenceKey, promptLabel });
93
+ }
94
+ async function addCopilotCandidates(candidates, codeUserRoot, options) {
95
+ if (!(await exists(codeUserRoot)))
96
+ return;
97
+ const remoteCodeUserRoot = remoteCodeRoot(codeUserRoot);
98
+ const globalStorage = path.join(codeUserRoot, "globalStorage", "github.copilot-chat");
99
+ addCandidate(candidates, globalStorage, `${remoteCodeUserRoot}/globalStorage/github.copilot-chat`, "Copilot global chat state", true, "context:copilot-global", "Copilot global chat state");
100
+ const workspaceStorage = path.join(codeUserRoot, "workspaceStorage");
101
+ const workspaceDirs = await listDirectories(workspaceStorage);
102
+ for (const workspaceDir of workspaceDirs) {
103
+ const copilotRoot = path.join(workspaceDir, "GitHub.copilot-chat");
104
+ const isCurrentWorkspace = await isWorkspaceStorageForProject(workspaceDir, options.projectRoot);
105
+ if (await exists(copilotRoot) && (isCurrentWorkspace || options.includeAllCopilotWorkspaces)) {
106
+ const workspaceId = path.basename(workspaceDir);
107
+ const key = isCurrentWorkspace ? "context:copilot-current-workspace" : "context:copilot-all-workspaces";
108
+ const label = isCurrentWorkspace ? "Copilot current workspace conversations" : "Copilot other workspace conversations";
109
+ addCandidate(candidates, copilotRoot, `${remoteCodeUserRoot}/workspaceStorage/${workspaceId}/GitHub.copilot-chat`, label, true, key, label);
110
+ }
111
+ }
112
+ }
113
+ async function isWorkspaceStorageForProject(workspaceDir, projectRoot) {
114
+ try {
115
+ const raw = await readFile(path.join(workspaceDir, "workspace.json"), "utf8");
116
+ const parsed = JSON.parse(raw);
117
+ const workspacePath = workspaceJsonPath(parsed.folder ?? parsed.workspace);
118
+ return workspacePath !== undefined && isSameOrParentPath(workspacePath, projectRoot);
119
+ }
120
+ catch {
121
+ return false;
122
+ }
123
+ }
124
+ function workspaceJsonPath(uri) {
125
+ if (!uri)
126
+ return undefined;
127
+ try {
128
+ if (uri.startsWith("file://"))
129
+ return path.resolve(fileURLToPath(uri));
130
+ return path.resolve(uri);
131
+ }
132
+ catch {
133
+ return undefined;
134
+ }
135
+ }
136
+ function isSameOrParentPath(workspacePath, projectRoot) {
137
+ const relative = path.relative(workspacePath, projectRoot);
138
+ return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative));
139
+ }
140
+ function remoteCodeRoot(localCodeUserRoot) {
141
+ if (localCodeUserRoot.includes("Cursor")) {
142
+ return "~/.config/Cursor/User";
143
+ }
144
+ if (localCodeUserRoot.includes("Code - Insiders")) {
145
+ return "~/.config/Code - Insiders/User";
146
+ }
147
+ return "~/.config/Code/User";
148
+ }
149
+ function codeUserRoots(home) {
150
+ return [
151
+ path.join(home, "Library", "Application Support", "Code", "User"),
152
+ path.join(home, "Library", "Application Support", "Code - Insiders", "User"),
153
+ path.join(home, "Library", "Application Support", "Cursor", "User"),
154
+ path.join(home, ".config", "Code", "User"),
155
+ path.join(home, ".config", "Code - Insiders", "User"),
156
+ path.join(home, ".config", "Cursor", "User"),
157
+ ];
158
+ }
159
+ async function listDirectories(directory) {
160
+ try {
161
+ const dirents = await import("node:fs/promises").then((fs) => fs.readdir(directory, { withFileTypes: true }));
162
+ return dirents.filter((dirent) => dirent.isDirectory()).map((dirent) => path.join(directory, dirent.name));
163
+ }
164
+ catch {
165
+ return [];
166
+ }
167
+ }
168
+ function dedupeCandidates(candidates) {
169
+ const seen = new Set();
170
+ return candidates.filter((candidate) => {
171
+ const key = `${candidate.source}\0${candidate.remoteRoot}`;
172
+ if (seen.has(key))
173
+ return false;
174
+ seen.add(key);
175
+ return true;
176
+ });
177
+ }
178
+ function remoteSecretRoot(remotePath) {
179
+ for (const secretRoot of ["/root/.ssh", "/root/.config/gh", "/root/.config/gcloud", "/root/.aws", "/root/.azure", "/root/.docker", "/root/.vmpush"]) {
180
+ if (remotePath === secretRoot || remotePath.startsWith(`${secretRoot}/`))
181
+ return secretRoot;
182
+ }
183
+ return undefined;
184
+ }
185
+ function expandRemoteHome(value) {
186
+ if (value === "~")
187
+ return "/root";
188
+ if (value.startsWith("~/"))
189
+ return `/root/${value.slice(2)}`;
190
+ return value;
191
+ }
192
+ function isValidEnvName(value) {
193
+ return /^[A-Za-z_][A-Za-z0-9_]*$/.test(value);
194
+ }
195
+ function shellQuote(value) {
196
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
197
+ }
198
+ async function exists(filePath) {
199
+ try {
200
+ await stat(filePath);
201
+ return true;
202
+ }
203
+ catch {
204
+ return false;
205
+ }
206
+ }
207
+ async function checkedExec(vm, command, timeoutMs) {
208
+ const result = await vm.exec({ command, timeoutMs });
209
+ if (result.statusCode && result.statusCode !== 0) {
210
+ throw new Error(`remote command failed (${result.statusCode}): ${command}\n${result.stderr ?? result.stdout ?? ""}`);
211
+ }
212
+ return result;
213
+ }
@@ -1,23 +1,29 @@
1
1
  import { stat } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
- import { definePlugin, type ContextCandidate } from "../../../src/plugin-api.ts";
5
-
4
+ import { definePlugin } from "../../../src/plugin-api.js";
6
5
  export function dockerAuthPlugin() {
7
6
  return definePlugin({
8
7
  name: "@freestyle-sync/auth-docker",
9
8
  async discoverContextCandidates({ options }) {
10
- if (!options.includeAuth) return [];
11
- const item: ContextCandidate = { source: path.join(homedir(), ".docker", "config.json"), remoteRoot: "/root/.docker/config.json", label: "Docker auth", sensitive: true, preferenceKey: "context:docker-auth", promptLabel: "Docker auth" };
9
+ if (!options.includeAuth)
10
+ return [];
11
+ const item = { source: path.join(homedir(), ".docker", "config.json"), remoteRoot: "/root/.docker/config.json", label: "Docker auth", sensitive: true, preferenceKey: "context:docker-auth", promptLabel: "Docker auth" };
12
12
  return await exists(item.source) ? [item] : [];
13
13
  },
14
14
  async afterContextSync({ vm, changedRemotePaths, utils }) {
15
- if (!changedRemotePaths.includes("/root/.docker/config.json")) return;
15
+ if (!changedRemotePaths.includes("/root/.docker/config.json"))
16
+ return;
16
17
  await utils.checkedExec(vm, "mkdir -p /root/.docker; chown -R root:root /root/.docker 2>/dev/null || true; chmod 700 /root/.docker 2>/dev/null || true; chmod 600 /root/.docker/config.json 2>/dev/null || true");
17
18
  },
18
19
  });
19
20
  }
20
-
21
- async function exists(filePath: string) {
22
- try { await stat(filePath); return true; } catch { return false; }
21
+ async function exists(filePath) {
22
+ try {
23
+ await stat(filePath);
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
23
29
  }
@@ -1,18 +1,17 @@
1
- import { definePlugin } from "../../../src/plugin-api.ts";
2
-
1
+ import { definePlugin } from "../../../src/plugin-api.js";
3
2
  export function envAuthPlugin() {
4
3
  return definePlugin({
5
4
  name: "@freestyle-sync/auth-env",
6
5
  collectEnvironment({ options }) {
7
- if (!options.includeAuth) return {};
6
+ if (!options.includeAuth)
7
+ return {};
8
8
  return collectEnvironment(options.envKeys);
9
9
  },
10
10
  });
11
11
  }
12
-
13
- function collectEnvironment(extraKeys: string[]): Record<string, string> {
12
+ function collectEnvironment(extraKeys) {
14
13
  const allow = new Set(extraKeys);
15
- const env: Record<string, string> = {};
14
+ const env = {};
16
15
  const tokenPattern = /(^|_)(TOKEN|SECRET|PASSWORD|PASS|API_KEY|ACCESS_KEY|PRIVATE_KEY|AUTH|CREDENTIALS?)(_|$)/i;
17
16
  const exactNames = new Set([
18
17
  "ANTHROPIC_API_KEY",
@@ -24,12 +23,13 @@ function collectEnvironment(extraKeys: string[]): Record<string, string> {
24
23
  "OPENAI_API_KEY",
25
24
  "FREESTYLE_API_KEY",
26
25
  ]);
27
-
28
26
  for (const [key, value] of Object.entries(process.env)) {
29
- if (!value) continue;
30
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
31
- if (allow.has(key) || exactNames.has(key) || tokenPattern.test(key)) env[key] = value;
27
+ if (!value)
28
+ continue;
29
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
30
+ continue;
31
+ if (allow.has(key) || exactNames.has(key) || tokenPattern.test(key))
32
+ env[key] = value;
32
33
  }
33
-
34
34
  return env;
35
35
  }
@@ -1,23 +1,29 @@
1
1
  import { stat } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
- import { definePlugin, type ContextCandidate } from "../../../src/plugin-api.ts";
5
-
4
+ import { definePlugin } from "../../../src/plugin-api.js";
6
5
  export function gcloudAuthPlugin() {
7
6
  return definePlugin({
8
7
  name: "@freestyle-sync/auth-gcloud",
9
8
  async discoverContextCandidates({ options }) {
10
- if (!options.includeAuth) return [];
11
- const item: ContextCandidate = { source: path.join(homedir(), ".config", "gcloud"), remoteRoot: "/root/.config/gcloud", label: "Google Cloud auth", sensitive: true, preferenceKey: "context:gcloud", promptLabel: "Google Cloud auth" };
9
+ if (!options.includeAuth)
10
+ return [];
11
+ const item = { source: path.join(homedir(), ".config", "gcloud"), remoteRoot: "/root/.config/gcloud", label: "Google Cloud auth", sensitive: true, preferenceKey: "context:gcloud", promptLabel: "Google Cloud auth" };
12
12
  return await exists(item.source) ? [item] : [];
13
13
  },
14
14
  async afterContextSync({ vm, changedRemotePaths, utils }) {
15
- if (!changedRemotePaths.some((remotePath) => remotePath === "/root/.config/gcloud" || remotePath.startsWith("/root/.config/gcloud/"))) return;
15
+ if (!changedRemotePaths.some((remotePath) => remotePath === "/root/.config/gcloud" || remotePath.startsWith("/root/.config/gcloud/")))
16
+ return;
16
17
  await utils.checkedExec(vm, "chown -R root:root /root/.config/gcloud 2>/dev/null || true; chmod 700 /root/.config/gcloud 2>/dev/null || true");
17
18
  },
18
19
  });
19
20
  }
20
-
21
- async function exists(filePath: string) {
22
- try { await stat(filePath); return true; } catch { return false; }
21
+ async function exists(filePath) {
22
+ try {
23
+ await stat(filePath);
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
23
29
  }
@@ -1,13 +1,13 @@
1
1
  import { stat } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
- import { definePlugin, type ContextCandidate } from "../../../src/plugin-api.ts";
5
-
4
+ import { definePlugin } from "../../../src/plugin-api.js";
6
5
  export function gitAuthPlugin() {
7
6
  return definePlugin({
8
7
  name: "@freestyle-sync/auth-git",
9
8
  async discoverContextCandidates({ options }) {
10
- if (!options.includeAuth) return [];
9
+ if (!options.includeAuth)
10
+ return [];
11
11
  return existing([
12
12
  candidate(path.join(homedir(), ".gitconfig"), "~/.gitconfig", "git config", "context:git-config"),
13
13
  candidate(path.join(homedir(), ".git-credentials"), "~/.git-credentials", "git credentials", "context:git-credentials"),
@@ -17,27 +17,34 @@ export function gitAuthPlugin() {
17
17
  },
18
18
  async afterContextSync({ vm, changedRemotePaths, utils }) {
19
19
  const files = ["/root/.git-credentials", "/root/.netrc"].filter((file) => changedRemotePaths.includes(file));
20
- if (files.length > 0) await utils.checkedExec(vm, files.map((file) => `chmod 600 ${utils.shellQuote(file)} 2>/dev/null || true`).join("; "));
20
+ if (files.length > 0)
21
+ await utils.checkedExec(vm, files.map((file) => `chmod 600 ${utils.shellQuote(file)} 2>/dev/null || true`).join("; "));
21
22
  },
22
23
  });
23
24
  }
24
-
25
- function candidate(source: string, remoteRoot: string, label: string, preferenceKey: string): ContextCandidate {
25
+ function candidate(source, remoteRoot, label, preferenceKey) {
26
26
  return { source, remoteRoot: expandRemoteHome(remoteRoot), label, sensitive: true, preferenceKey, promptLabel: label };
27
27
  }
28
-
29
- async function existing(candidates: ContextCandidate[]) {
30
- const result: ContextCandidate[] = [];
31
- for (const item of candidates) if (await exists(item.source)) result.push(item);
28
+ async function existing(candidates) {
29
+ const result = [];
30
+ for (const item of candidates)
31
+ if (await exists(item.source))
32
+ result.push(item);
32
33
  return result;
33
34
  }
34
-
35
- function expandRemoteHome(value: string) {
36
- if (value === "~") return "/root";
37
- if (value.startsWith("~/")) return `/root/${value.slice(2)}`;
35
+ function expandRemoteHome(value) {
36
+ if (value === "~")
37
+ return "/root";
38
+ if (value.startsWith("~/"))
39
+ return `/root/${value.slice(2)}`;
38
40
  return value;
39
41
  }
40
-
41
- async function exists(filePath: string) {
42
- try { await stat(filePath); return true; } catch { return false; }
42
+ async function exists(filePath) {
43
+ try {
44
+ await stat(filePath);
45
+ return true;
46
+ }
47
+ catch {
48
+ return false;
49
+ }
43
50
  }
@@ -1,33 +1,39 @@
1
1
  import { stat } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
- import { definePlugin, type ContextCandidate } from "../../../src/plugin-api.ts";
5
-
4
+ import { definePlugin } from "../../../src/plugin-api.js";
6
5
  export function githubCliAuthPlugin() {
7
6
  return singleAuthDirectoryPlugin("@freestyle-sync/auth-github-cli", path.join(homedir(), ".config", "gh"), "~/.config/gh", "GitHub CLI auth", "context:github-cli");
8
7
  }
9
-
10
- function singleAuthDirectoryPlugin(name: string, source: string, remoteRoot: string, label: string, preferenceKey: string) {
8
+ function singleAuthDirectoryPlugin(name, source, remoteRoot, label, preferenceKey) {
11
9
  return definePlugin({
12
10
  name,
13
11
  async discoverContextCandidates({ options }) {
14
- if (!options.includeAuth) return [];
15
- const item: ContextCandidate = { source, remoteRoot: expandRemoteHome(remoteRoot), label, sensitive: true, preferenceKey, promptLabel: label };
12
+ if (!options.includeAuth)
13
+ return [];
14
+ const item = { source, remoteRoot: expandRemoteHome(remoteRoot), label, sensitive: true, preferenceKey, promptLabel: label };
16
15
  return await exists(source) ? [item] : [];
17
16
  },
18
17
  async afterContextSync({ vm, changedRemotePaths, utils }) {
19
- if (!changedRemotePaths.some((remotePath) => remotePath === expandRemoteHome(remoteRoot) || remotePath.startsWith(`${expandRemoteHome(remoteRoot)}/`))) return;
18
+ if (!changedRemotePaths.some((remotePath) => remotePath === expandRemoteHome(remoteRoot) || remotePath.startsWith(`${expandRemoteHome(remoteRoot)}/`)))
19
+ return;
20
20
  await utils.checkedExec(vm, `chown -R root:root ${utils.shellQuote(expandRemoteHome(remoteRoot))} 2>/dev/null || true; chmod 700 ${utils.shellQuote(expandRemoteHome(remoteRoot))} 2>/dev/null || true`);
21
21
  },
22
22
  });
23
23
  }
24
-
25
- function expandRemoteHome(value: string) {
26
- if (value === "~") return "/root";
27
- if (value.startsWith("~/")) return `/root/${value.slice(2)}`;
24
+ function expandRemoteHome(value) {
25
+ if (value === "~")
26
+ return "/root";
27
+ if (value.startsWith("~/"))
28
+ return `/root/${value.slice(2)}`;
28
29
  return value;
29
30
  }
30
-
31
- async function exists(filePath: string) {
32
- try { await stat(filePath); return true; } catch { return false; }
31
+ async function exists(filePath) {
32
+ try {
33
+ await stat(filePath);
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
33
39
  }
@@ -1,32 +1,38 @@
1
1
  import { stat } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
- import { definePlugin, type ContextCandidate } from "../../../src/plugin-api.ts";
5
-
4
+ import { definePlugin } from "../../../src/plugin-api.js";
6
5
  export function npmAuthPlugin() {
7
6
  return definePlugin({
8
7
  name: "@freestyle-sync/auth-npm",
9
8
  async discoverContextCandidates({ options }) {
10
- if (!options.includeAuth) return [];
9
+ if (!options.includeAuth)
10
+ return [];
11
11
  const item = candidate(path.join(homedir(), ".npmrc"), "~/.npmrc", "npm auth", "context:npm-auth");
12
12
  return await exists(item.source) ? [item] : [];
13
13
  },
14
14
  async afterContextSync({ vm, changedRemotePaths, utils }) {
15
- if (changedRemotePaths.includes("/root/.npmrc")) await utils.checkedExec(vm, "chmod 600 /root/.npmrc 2>/dev/null || true");
15
+ if (changedRemotePaths.includes("/root/.npmrc"))
16
+ await utils.checkedExec(vm, "chmod 600 /root/.npmrc 2>/dev/null || true");
16
17
  },
17
18
  });
18
19
  }
19
-
20
- function candidate(source: string, remoteRoot: string, label: string, preferenceKey: string): ContextCandidate {
20
+ function candidate(source, remoteRoot, label, preferenceKey) {
21
21
  return { source, remoteRoot: expandRemoteHome(remoteRoot), label, sensitive: true, preferenceKey, promptLabel: label };
22
22
  }
23
-
24
- function expandRemoteHome(value: string) {
25
- if (value === "~") return "/root";
26
- if (value.startsWith("~/")) return `/root/${value.slice(2)}`;
23
+ function expandRemoteHome(value) {
24
+ if (value === "~")
25
+ return "/root";
26
+ if (value.startsWith("~/"))
27
+ return `/root/${value.slice(2)}`;
27
28
  return value;
28
29
  }
29
-
30
- async function exists(filePath: string) {
31
- try { await stat(filePath); return true; } catch { return false; }
30
+ async function exists(filePath) {
31
+ try {
32
+ await stat(filePath);
33
+ return true;
34
+ }
35
+ catch {
36
+ return false;
37
+ }
32
38
  }
@@ -1,13 +1,13 @@
1
1
  import { stat } from "node:fs/promises";
2
2
  import { homedir } from "node:os";
3
3
  import path from "node:path";
4
- import { definePlugin, type ContextCandidate } from "../../../src/plugin-api.ts";
5
-
4
+ import { definePlugin } from "../../../src/plugin-api.js";
6
5
  export function sshAuthPlugin() {
7
6
  return definePlugin({
8
7
  name: "@freestyle-sync/auth-ssh",
9
8
  async discoverContextCandidates({ options }) {
10
- if (!options.includeAuth) return [];
9
+ if (!options.includeAuth)
10
+ return [];
11
11
  const item = candidate(path.join(homedir(), ".ssh"), "~/.ssh", "ssh credentials", "context:ssh");
12
12
  return await exists(item.source) ? [item] : [];
13
13
  },
@@ -15,22 +15,28 @@ export function sshAuthPlugin() {
15
15
  return remotePath === "/root/.ssh/authorized_keys" || remotePath === "/root/.ssh/authorized_keys2";
16
16
  },
17
17
  async afterContextSync({ vm, changedRemotePaths, utils }) {
18
- if (!changedRemotePaths.some((remotePath) => remotePath === "/root/.ssh" || remotePath.startsWith("/root/.ssh/"))) return;
18
+ if (!changedRemotePaths.some((remotePath) => remotePath === "/root/.ssh" || remotePath.startsWith("/root/.ssh/")))
19
+ return;
19
20
  await utils.checkedExec(vm, "chown -R root:root /root/.ssh 2>/dev/null || true; chmod 700 /root/.ssh 2>/dev/null || true; find /root/.ssh -type f ! -name authorized_keys ! -name authorized_keys2 -exec chmod 600 {} + 2>/dev/null || true");
20
21
  },
21
22
  });
22
23
  }
23
-
24
- function candidate(source: string, remoteRoot: string, label: string, preferenceKey: string): ContextCandidate {
24
+ function candidate(source, remoteRoot, label, preferenceKey) {
25
25
  return { source, remoteRoot: expandRemoteHome(remoteRoot), label, sensitive: true, preferenceKey, promptLabel: label };
26
26
  }
27
-
28
- function expandRemoteHome(value: string) {
29
- if (value === "~") return "/root";
30
- if (value.startsWith("~/")) return `/root/${value.slice(2)}`;
27
+ function expandRemoteHome(value) {
28
+ if (value === "~")
29
+ return "/root";
30
+ if (value.startsWith("~/"))
31
+ return `/root/${value.slice(2)}`;
31
32
  return value;
32
33
  }
33
-
34
- async function exists(filePath: string) {
35
- try { await stat(filePath); return true; } catch { return false; }
34
+ async function exists(filePath) {
35
+ try {
36
+ await stat(filePath);
37
+ return true;
38
+ }
39
+ catch {
40
+ return false;
41
+ }
36
42
  }
@@ -0,0 +1,24 @@
1
+ import { stat } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import path from "node:path";
4
+ import { definePlugin } from "../../../src/plugin-api.js";
5
+ export function yarnAuthPlugin() {
6
+ return definePlugin({
7
+ name: "@freestyle-sync/auth-yarn",
8
+ async discoverContextCandidates({ options }) {
9
+ if (!options.includeAuth)
10
+ return [];
11
+ const item = { source: path.join(homedir(), ".yarnrc.yml"), remoteRoot: "/root/.yarnrc.yml", label: "Yarn auth", sensitive: true, preferenceKey: "context:yarn-auth", promptLabel: "Yarn auth" };
12
+ return await exists(item.source) ? [item] : [];
13
+ },
14
+ });
15
+ }
16
+ async function exists(filePath) {
17
+ try {
18
+ await stat(filePath);
19
+ return true;
20
+ }
21
+ catch {
22
+ return false;
23
+ }
24
+ }