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.
Files changed (2) hide show
  1. package/extensions/sync.ts +39 -8
  2. package/package.json +1 -1
@@ -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
- const ahead = await git(["status", "--porcelain", "--branch"]);
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 })),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-git-sync",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Sync Pi agent config through Git",
5
5
  "keywords": [
6
6
  "pi-package",