pi-git-sync 0.1.0 → 0.1.1
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/extensions/sync.ts +39 -8
- package/package.json +1 -1
package/extensions/sync.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { execFile } from "node:child_process";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
@@ -54,7 +54,9 @@ git/
|
|
|
54
54
|
node_modules/
|
|
55
55
|
`;
|
|
56
56
|
|
|
57
|
-
type Config = { repo: string; branch: string };
|
|
57
|
+
type Config = { repo: string; branch: string; lastCheckAt?: string };
|
|
58
|
+
|
|
59
|
+
const startupCheckEveryMs = 60 * 60 * 1000;
|
|
58
60
|
|
|
59
61
|
function repoOwner(repo?: string) {
|
|
60
62
|
if (!repo) return "you";
|
|
@@ -86,16 +88,16 @@ Generated automatically by pi-git-sync.
|
|
|
86
88
|
`;
|
|
87
89
|
}
|
|
88
90
|
|
|
89
|
-
function sh(cmd: string, args: string[], cwd = agentDir) {
|
|
91
|
+
function sh(cmd: string, args: string[], cwd = agentDir, timeout = 0) {
|
|
90
92
|
return new Promise<{ code: number; stdout: string; stderr: string }>((resolve) => {
|
|
91
|
-
execFile(cmd, args, { cwd }, (err, stdout, stderr) => {
|
|
93
|
+
execFile(cmd, args, { cwd, timeout }, (err, stdout, stderr) => {
|
|
92
94
|
resolve({ code: (err as any)?.code ?? 0, stdout: String(stdout), stderr: String(stderr) });
|
|
93
95
|
});
|
|
94
96
|
});
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
async function git(args: string[]) {
|
|
98
|
-
return sh("git", args);
|
|
99
|
+
async function git(args: string[], timeout = 0) {
|
|
100
|
+
return sh("git", args, agentDir, timeout);
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
async function mustGit(args: string[]) {
|
|
@@ -279,8 +281,7 @@ async function normalSync(pi: ExtensionAPI, config: Config, ctx: ExtensionComman
|
|
|
279
281
|
return;
|
|
280
282
|
}
|
|
281
283
|
|
|
282
|
-
|
|
283
|
-
if (ahead.stdout.includes("ahead")) await mustGit(["push", "origin", config.branch]);
|
|
284
|
+
await mustGit(["push", "-u", "origin", config.branch]);
|
|
284
285
|
ctx.ui.notify("Pi config synced", "info");
|
|
285
286
|
}
|
|
286
287
|
|
|
@@ -289,11 +290,41 @@ async function showStatus(ctx: ExtensionCommandContext) {
|
|
|
289
290
|
ctx.ui.notify(status.stdout.trim() || "No git status", "info");
|
|
290
291
|
}
|
|
291
292
|
|
|
293
|
+
async function checkStartupSync(config: Config, ctx: ExtensionContext) {
|
|
294
|
+
if (!ctx.hasUI || !existsSync(join(agentDir, ".git")) || inMerge()) return;
|
|
295
|
+
if (config.lastCheckAt && Date.now() - Date.parse(config.lastCheckAt) < startupCheckEveryMs) return;
|
|
296
|
+
|
|
297
|
+
const localStatus = await git(["status", "--porcelain", "--", ...allow], 3000);
|
|
298
|
+
const localHead = await git(["rev-parse", "HEAD"], 3000);
|
|
299
|
+
const remoteHead = await git(["ls-remote", "origin", config.branch], 5000);
|
|
300
|
+
|
|
301
|
+
if (localStatus.code !== 0 || localHead.code !== 0 || remoteHead.code !== 0) return;
|
|
302
|
+
const remoteSha = remoteHead.stdout.trim().split(/\s+/)[0];
|
|
303
|
+
let hasRemote = false;
|
|
304
|
+
if (remoteSha && remoteSha !== localHead.stdout.trim() && (await git(["fetch", "--quiet", "origin", config.branch], 10000)).code === 0) {
|
|
305
|
+
hasRemote = (await git(["merge-base", "--is-ancestor", `origin/${config.branch}`, "HEAD"], 3000)).code === 1;
|
|
306
|
+
}
|
|
307
|
+
const warnings = [
|
|
308
|
+
localStatus.stdout.trim() ? "Local config changed. Run /sync" : undefined,
|
|
309
|
+
hasRemote ? "Remote config updated. Run /sync" : undefined,
|
|
310
|
+
].filter((x): x is string => !!x);
|
|
311
|
+
|
|
312
|
+
config.lastCheckAt = new Date().toISOString();
|
|
313
|
+
await saveConfig(config);
|
|
314
|
+
|
|
315
|
+
setTimeout(() => warnings.forEach((warning) => ctx.ui.notify(warning, "info")), 750);
|
|
316
|
+
}
|
|
317
|
+
|
|
292
318
|
function looksLikeRepo(arg: string) {
|
|
293
319
|
return /^(git@|https?:\/\/|ssh:\/\/|git:\/\/)/.test(arg) || /^[\w.-]+\/[\w./-]+(\.git)?$/.test(arg);
|
|
294
320
|
}
|
|
295
321
|
|
|
296
322
|
export default function piGitSync(pi: ExtensionAPI) {
|
|
323
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
324
|
+
const config = await loadConfig().catch(() => undefined);
|
|
325
|
+
if (config) await checkStartupSync(config, ctx);
|
|
326
|
+
});
|
|
327
|
+
|
|
297
328
|
pi.registerCommand("sync", {
|
|
298
329
|
description: "Sync ~/.pi/agent config through git",
|
|
299
330
|
getArgumentCompletions: (prefix) => ["status"].filter((x) => x.startsWith(prefix)).map((value) => ({ value, label: value })),
|