freestyle-sync 0.1.0
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.
- package/PUBLISHING.md +3 -0
- package/README.md +47 -0
- package/freestyle-sync.config.ts +38 -0
- package/package.json +28 -0
- package/plugins/agent-claude/package.json +8 -0
- package/plugins/agent-claude/src/index.ts +113 -0
- package/plugins/agent-codex/package.json +8 -0
- package/plugins/agent-codex/src/index.ts +69 -0
- package/plugins/agent-copilot/package.json +8 -0
- package/plugins/agent-copilot/src/index.ts +542 -0
- package/plugins/auth-aws/package.json +8 -0
- package/plugins/auth-aws/src/index.ts +23 -0
- package/plugins/auth-azure/package.json +8 -0
- package/plugins/auth-azure/src/index.ts +23 -0
- package/plugins/auth-docker/package.json +8 -0
- package/plugins/auth-docker/src/index.ts +23 -0
- package/plugins/auth-env/package.json +8 -0
- package/plugins/auth-env/src/index.ts +35 -0
- package/plugins/auth-gcloud/package.json +8 -0
- package/plugins/auth-gcloud/src/index.ts +23 -0
- package/plugins/auth-git/package.json +8 -0
- package/plugins/auth-git/src/index.ts +43 -0
- package/plugins/auth-github-cli/package.json +8 -0
- package/plugins/auth-github-cli/src/index.ts +33 -0
- package/plugins/auth-npm/package.json +8 -0
- package/plugins/auth-npm/src/index.ts +32 -0
- package/plugins/auth-ssh/package.json +8 -0
- package/plugins/auth-ssh/src/index.ts +36 -0
- package/plugins/auth-yarn/package.json +8 -0
- package/plugins/auth-yarn/src/index.ts +19 -0
- package/plugins/node-npm/package.json +8 -0
- package/plugins/node-npm/src/index.ts +390 -0
- package/plugins/shell-history/package.json +8 -0
- package/plugins/shell-history/src/index.ts +64 -0
- package/plugins/vscode/package.json +8 -0
- package/plugins/vscode/src/index.ts +162 -0
- package/src/main.ts +1136 -0
- package/src/plugin-api.ts +107 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { definePlugin } from "../../../src/plugin-api.ts";
|
|
2
|
+
|
|
3
|
+
export function envAuthPlugin() {
|
|
4
|
+
return definePlugin({
|
|
5
|
+
name: "@freestyle-sync/auth-env",
|
|
6
|
+
collectEnvironment({ options }) {
|
|
7
|
+
if (!options.includeAuth) return {};
|
|
8
|
+
return collectEnvironment(options.envKeys);
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function collectEnvironment(extraKeys: string[]): Record<string, string> {
|
|
14
|
+
const allow = new Set(extraKeys);
|
|
15
|
+
const env: Record<string, string> = {};
|
|
16
|
+
const tokenPattern = /(^|_)(TOKEN|SECRET|PASSWORD|PASS|API_KEY|ACCESS_KEY|PRIVATE_KEY|AUTH|CREDENTIALS?)(_|$)/i;
|
|
17
|
+
const exactNames = new Set([
|
|
18
|
+
"ANTHROPIC_API_KEY",
|
|
19
|
+
"CODEX_HOME",
|
|
20
|
+
"GH_TOKEN",
|
|
21
|
+
"GITHUB_TOKEN",
|
|
22
|
+
"GIT_ASKPASS",
|
|
23
|
+
"NPM_TOKEN",
|
|
24
|
+
"OPENAI_API_KEY",
|
|
25
|
+
"FREESTYLE_API_KEY",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
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;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return env;
|
|
35
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { definePlugin, type ContextCandidate } from "../../../src/plugin-api.ts";
|
|
5
|
+
|
|
6
|
+
export function gcloudAuthPlugin() {
|
|
7
|
+
return definePlugin({
|
|
8
|
+
name: "@freestyle-sync/auth-gcloud",
|
|
9
|
+
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" };
|
|
12
|
+
return await exists(item.source) ? [item] : [];
|
|
13
|
+
},
|
|
14
|
+
async afterContextSync({ vm, changedRemotePaths, utils }) {
|
|
15
|
+
if (!changedRemotePaths.some((remotePath) => remotePath === "/root/.config/gcloud" || remotePath.startsWith("/root/.config/gcloud/"))) return;
|
|
16
|
+
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
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function exists(filePath: string) {
|
|
22
|
+
try { await stat(filePath); return true; } catch { return false; }
|
|
23
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { definePlugin, type ContextCandidate } from "../../../src/plugin-api.ts";
|
|
5
|
+
|
|
6
|
+
export function gitAuthPlugin() {
|
|
7
|
+
return definePlugin({
|
|
8
|
+
name: "@freestyle-sync/auth-git",
|
|
9
|
+
async discoverContextCandidates({ options }) {
|
|
10
|
+
if (!options.includeAuth) return [];
|
|
11
|
+
return existing([
|
|
12
|
+
candidate(path.join(homedir(), ".gitconfig"), "~/.gitconfig", "git config", "context:git-config"),
|
|
13
|
+
candidate(path.join(homedir(), ".git-credentials"), "~/.git-credentials", "git credentials", "context:git-credentials"),
|
|
14
|
+
candidate(path.join(homedir(), ".netrc"), "~/.netrc", "netrc credentials", "context:netrc"),
|
|
15
|
+
candidate(path.join(homedir(), ".config", "git"), "~/.config/git", "git config directory", "context:git-config-directory"),
|
|
16
|
+
]);
|
|
17
|
+
},
|
|
18
|
+
async afterContextSync({ vm, changedRemotePaths, utils }) {
|
|
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("; "));
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function candidate(source: string, remoteRoot: string, label: string, preferenceKey: string): ContextCandidate {
|
|
26
|
+
return { source, remoteRoot: expandRemoteHome(remoteRoot), label, sensitive: true, preferenceKey, promptLabel: label };
|
|
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);
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function expandRemoteHome(value: string) {
|
|
36
|
+
if (value === "~") return "/root";
|
|
37
|
+
if (value.startsWith("~/")) return `/root/${value.slice(2)}`;
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function exists(filePath: string) {
|
|
42
|
+
try { await stat(filePath); return true; } catch { return false; }
|
|
43
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { definePlugin, type ContextCandidate } from "../../../src/plugin-api.ts";
|
|
5
|
+
|
|
6
|
+
export function githubCliAuthPlugin() {
|
|
7
|
+
return singleAuthDirectoryPlugin("@freestyle-sync/auth-github-cli", path.join(homedir(), ".config", "gh"), "~/.config/gh", "GitHub CLI auth", "context:github-cli");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function singleAuthDirectoryPlugin(name: string, source: string, remoteRoot: string, label: string, preferenceKey: string) {
|
|
11
|
+
return definePlugin({
|
|
12
|
+
name,
|
|
13
|
+
async discoverContextCandidates({ options }) {
|
|
14
|
+
if (!options.includeAuth) return [];
|
|
15
|
+
const item: ContextCandidate = { source, remoteRoot: expandRemoteHome(remoteRoot), label, sensitive: true, preferenceKey, promptLabel: label };
|
|
16
|
+
return await exists(source) ? [item] : [];
|
|
17
|
+
},
|
|
18
|
+
async afterContextSync({ vm, changedRemotePaths, utils }) {
|
|
19
|
+
if (!changedRemotePaths.some((remotePath) => remotePath === expandRemoteHome(remoteRoot) || remotePath.startsWith(`${expandRemoteHome(remoteRoot)}/`))) return;
|
|
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
|
+
},
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function expandRemoteHome(value: string) {
|
|
26
|
+
if (value === "~") return "/root";
|
|
27
|
+
if (value.startsWith("~/")) return `/root/${value.slice(2)}`;
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function exists(filePath: string) {
|
|
32
|
+
try { await stat(filePath); return true; } catch { return false; }
|
|
33
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { definePlugin, type ContextCandidate } from "../../../src/plugin-api.ts";
|
|
5
|
+
|
|
6
|
+
export function npmAuthPlugin() {
|
|
7
|
+
return definePlugin({
|
|
8
|
+
name: "@freestyle-sync/auth-npm",
|
|
9
|
+
async discoverContextCandidates({ options }) {
|
|
10
|
+
if (!options.includeAuth) return [];
|
|
11
|
+
const item = candidate(path.join(homedir(), ".npmrc"), "~/.npmrc", "npm auth", "context:npm-auth");
|
|
12
|
+
return await exists(item.source) ? [item] : [];
|
|
13
|
+
},
|
|
14
|
+
async afterContextSync({ vm, changedRemotePaths, utils }) {
|
|
15
|
+
if (changedRemotePaths.includes("/root/.npmrc")) await utils.checkedExec(vm, "chmod 600 /root/.npmrc 2>/dev/null || true");
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function candidate(source: string, remoteRoot: string, label: string, preferenceKey: string): ContextCandidate {
|
|
21
|
+
return { source, remoteRoot: expandRemoteHome(remoteRoot), label, sensitive: true, preferenceKey, promptLabel: label };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function expandRemoteHome(value: string) {
|
|
25
|
+
if (value === "~") return "/root";
|
|
26
|
+
if (value.startsWith("~/")) return `/root/${value.slice(2)}`;
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function exists(filePath: string) {
|
|
31
|
+
try { await stat(filePath); return true; } catch { return false; }
|
|
32
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { definePlugin, type ContextCandidate } from "../../../src/plugin-api.ts";
|
|
5
|
+
|
|
6
|
+
export function sshAuthPlugin() {
|
|
7
|
+
return definePlugin({
|
|
8
|
+
name: "@freestyle-sync/auth-ssh",
|
|
9
|
+
async discoverContextCandidates({ options }) {
|
|
10
|
+
if (!options.includeAuth) return [];
|
|
11
|
+
const item = candidate(path.join(homedir(), ".ssh"), "~/.ssh", "ssh credentials", "context:ssh");
|
|
12
|
+
return await exists(item.source) ? [item] : [];
|
|
13
|
+
},
|
|
14
|
+
isProtectedRemotePath(remotePath) {
|
|
15
|
+
return remotePath === "/root/.ssh/authorized_keys" || remotePath === "/root/.ssh/authorized_keys2";
|
|
16
|
+
},
|
|
17
|
+
async afterContextSync({ vm, changedRemotePaths, utils }) {
|
|
18
|
+
if (!changedRemotePaths.some((remotePath) => remotePath === "/root/.ssh" || remotePath.startsWith("/root/.ssh/"))) return;
|
|
19
|
+
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
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function candidate(source: string, remoteRoot: string, label: string, preferenceKey: string): ContextCandidate {
|
|
25
|
+
return { source, remoteRoot: expandRemoteHome(remoteRoot), label, sensitive: true, preferenceKey, promptLabel: label };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function expandRemoteHome(value: string) {
|
|
29
|
+
if (value === "~") return "/root";
|
|
30
|
+
if (value.startsWith("~/")) return `/root/${value.slice(2)}`;
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function exists(filePath: string) {
|
|
35
|
+
try { await stat(filePath); return true; } catch { return false; }
|
|
36
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { definePlugin, type ContextCandidate } from "../../../src/plugin-api.ts";
|
|
5
|
+
|
|
6
|
+
export function yarnAuthPlugin() {
|
|
7
|
+
return definePlugin({
|
|
8
|
+
name: "@freestyle-sync/auth-yarn",
|
|
9
|
+
async discoverContextCandidates({ options }) {
|
|
10
|
+
if (!options.includeAuth) return [];
|
|
11
|
+
const item: ContextCandidate = { 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
|
+
|
|
17
|
+
async function exists(filePath: string) {
|
|
18
|
+
try { await stat(filePath); return true; } catch { return false; }
|
|
19
|
+
}
|