@jive-sh/skill-sync 0.2.2-latest-linux-x64 → 0.2.2

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"folders.js","sourceRoot":"","sources":["../../../../src/internal/folders.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ import { spawn } from "node:child_process";
2
+ /**
3
+ * A desktop notification, best effort.
4
+ *
5
+ * Best effort is the whole contract: a daemon that cannot tell you about a conflict is worse than one
6
+ * that cannot sync, but a daemon that *dies* because a notification helper is missing is worse than
7
+ * both. Every failure here is swallowed.
8
+ *
9
+ * This works because these daemons are registered per-user and run inside the logged-in session — a
10
+ * LaunchAgent, a `systemctl --user` unit, a scheduled task. A system-wide service would have no desktop
11
+ * to post to.
12
+ */
13
+ /** No shell anywhere below: the arguments carry file paths, and a path is not a safe thing to interpolate. */
14
+ function run(command, args) {
15
+ try {
16
+ spawn(command, [...args], { stdio: "ignore", detached: true }).unref();
17
+ }
18
+ catch {
19
+ // Swallowed on purpose; see above.
20
+ }
21
+ }
22
+ export function notify(title, body) {
23
+ switch (process.platform) {
24
+ case "darwin":
25
+ // AppleScript string literals take double quotes and backslashes as escapes, and nothing else.
26
+ return run("osascript", [
27
+ "-e",
28
+ `display notification "${escapeAppleScript(body)}" with title "${escapeAppleScript(title)}"`,
29
+ ]);
30
+ case "linux":
31
+ return run("notify-send", [title, body]);
32
+ case "win32":
33
+ return run("powershell", [
34
+ "-NoProfile", "-NonInteractive", "-Command",
35
+ "[reflection.assembly]::LoadWithPartialName('System.Windows.Forms') > $null;"
36
+ + "$n = New-Object System.Windows.Forms.NotifyIcon;"
37
+ + "$n.Icon = [System.Drawing.SystemIcons]::Warning; $n.Visible = $true;"
38
+ + `$n.ShowBalloonTip(10000, '${escapeSingleQuotes(title)}', '${escapeSingleQuotes(body)}', 'Warning')`,
39
+ ]);
40
+ default:
41
+ return;
42
+ }
43
+ }
44
+ function escapeAppleScript(value) {
45
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
46
+ }
47
+ function escapeSingleQuotes(value) {
48
+ return value.replace(/'/g, "''");
49
+ }
50
+ //# sourceMappingURL=notify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notify.js","sourceRoot":"","sources":["../../../../src/internal/notify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C;;;;;;;;;;GAUG;AAEH,8GAA8G;AAC9G,SAAS,GAAG,CAAC,OAAe,EAAE,IAA2B;IACvD,IAAI,CAAC;QACH,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;IACzE,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;IACrC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,IAAY;IAChD,QAAQ,OAAO,CAAC,QAAQ,EAAE,CAAC;QACzB,KAAK,QAAQ;YACX,+FAA+F;YAC/F,OAAO,GAAG,CAAC,WAAW,EAAE;gBACtB,IAAI;gBACJ,yBAAyB,iBAAiB,CAAC,IAAI,CAAC,iBAAiB,iBAAiB,CAAC,KAAK,CAAC,GAAG;aAC7F,CAAC,CAAC;QACL,KAAK,OAAO;YACV,OAAO,GAAG,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;QAC3C,KAAK,OAAO;YACV,OAAO,GAAG,CAAC,YAAY,EAAE;gBACvB,YAAY,EAAE,iBAAiB,EAAE,UAAU;gBAC3C,6EAA6E;sBAC3E,kDAAkD;sBAClD,sEAAsE;sBACtE,6BAA6B,kBAAkB,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,IAAI,CAAC,eAAe;aACvG,CAAC,CAAC;QACL;YACE,OAAO;IACX,CAAC;AACH,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAa;IACvC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACnC,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Validation for the one piece of untrusted input that becomes a filesystem path.
3
+ *
4
+ * Shared by the Worker and the daemon on purpose, and deliberately free of any Node import so the Worker
5
+ * can bundle it. Both ends check: the server so one compromised client cannot poison what every machine
6
+ * pulls, and the daemon because a server it does not control is not a thing to take paths from either.
7
+ *
8
+ * Without this, a pushed path of `../../.ssh/authorized_keys` is joined to the skills directory and
9
+ * written wherever the daemon's user can write.
10
+ */
11
+ /** Long enough for any real nesting, short enough that nothing can be used to exhaust anything. */
12
+ const MAX_LENGTH = 512;
13
+ export function invalidPathReason(path) {
14
+ if (path.length === 0 || path.length > MAX_LENGTH)
15
+ return "path is empty or too long";
16
+ if (path.includes("\0"))
17
+ return "path contains a null byte";
18
+ if (path.includes("\\"))
19
+ return "path must use forward slashes";
20
+ if (path.startsWith("/"))
21
+ return "path must be relative";
22
+ // A Windows drive letter is absolute on the machine that matters even though it has no leading slash.
23
+ if (/^[A-Za-z]:/.test(path))
24
+ return "path must be relative";
25
+ for (const segment of path.split("/")) {
26
+ if (segment === "")
27
+ return "path has an empty segment";
28
+ if (segment === "." || segment === "..")
29
+ return "path must not contain . or .. segments";
30
+ }
31
+ return undefined;
32
+ }
33
+ export function isValidPath(path) {
34
+ return invalidPathReason(path) === undefined;
35
+ }
36
+ //# sourceMappingURL=path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path.js","sourceRoot":"","sources":["../../../../src/internal/path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,mGAAmG;AACnG,MAAM,UAAU,GAAG,GAAG,CAAC;AAEvB,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,UAAU;QAAE,OAAO,2BAA2B,CAAC;IACtF,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,2BAA2B,CAAC;IAC5D,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,+BAA+B,CAAC;IAChE,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,uBAAuB,CAAC;IACzD,sGAAsG;IACtG,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,uBAAuB,CAAC;IAE5D,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,IAAI,OAAO,KAAK,EAAE;YAAE,OAAO,2BAA2B,CAAC;QACvD,IAAI,OAAO,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,wCAAwC,CAAC;IAC3F,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,iBAAiB,CAAC,IAAI,CAAC,KAAK,SAAS,CAAC;AAC/C,CAAC"}
@@ -0,0 +1,124 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { basename, delimiter, dirname, join } from "node:path";
5
+ const BACKUP_ROOT = join(homedir(), ".jive", "skill-sync", "reconciled");
6
+ /** A long skill is tens of thousands of tokens to write back out. */
7
+ const TIMEOUT_MS = 15 * 60 * 1000;
8
+ /**
9
+ * A merge shorter than this fraction of the longer input is refused as probably truncated: a model that
10
+ * stops early produces a file that looks fine and silently drops half the rules.
11
+ */
12
+ const MIN_LENGTH_RATIO = 0.6;
13
+ const SYSTEM_PROMPT = `You merge two versions of one file from a user's Claude skills folder into one.
14
+ Answer in exactly this form and nothing else:
15
+ <conflict>One or two plain sentences: what differs between the versions.</conflict>
16
+ <resolution>One or two plain sentences: what the merge kept from each, and anything dropped or reworded.</resolution>
17
+ <merged>
18
+ The complete merged file, exactly as it should be saved. No code fences.
19
+ </merged>
20
+ Rules for the merge:
21
+ - Keep every rule, example and fact that either version has, unless the other version clearly replaces it.
22
+ - Where the two disagree, prefer the newer version's wording, and keep anything only the older one has.
23
+ - If a base version is given, it is what both started from: use it to tell additions from deletions, and
24
+ honour a deletion made on one side when the other side did not edit that part.
25
+ - Keep the file's frontmatter and structure.
26
+ In <conflict> and <resolution>, call the two versions by the names they are given (for example "the copy in
27
+ ~/.claude-chaine/skills on MacBookPro" and "the synced copy"), never "Version A" or "Version B": these are
28
+ read later by a person auditing the merge, who never saw this prompt.`;
29
+ /**
30
+ * Both versions are copied out of the synced folder before anything is written, so a bad merge costs a
31
+ * file copy, not the user's work. Outside the folder so the copies are never synced or read as skills.
32
+ */
33
+ export function backUp(conflict) {
34
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
35
+ const dir = join(BACKUP_ROOT, stamp, basename(dirname(conflict.folder)), conflict.path);
36
+ mkdirSync(dir, { recursive: true });
37
+ writeFileSync(join(dir, "local"), conflict.local.content);
38
+ writeFileSync(join(dir, "remote"), conflict.remote.content);
39
+ if (conflict.base !== null)
40
+ writeFileSync(join(dir, "base"), conflict.base);
41
+ return dir;
42
+ }
43
+ export async function reconcile(conflict) {
44
+ const sections = [
45
+ `File: ${conflict.path}`,
46
+ conflict.base === null ? "No base version: these two have never been in sync." : `=== the base version both started from:\n${conflict.base}`,
47
+ `=== ${conflict.local.label}:\n${conflict.local.content}`,
48
+ `=== ${conflict.remote.label}:\n${conflict.remote.content}`,
49
+ "Write the merged file now.",
50
+ ];
51
+ const answer = await runClaude(sections.join("\n\n-----\n\n"), dirname(conflict.folder));
52
+ const start = answer.indexOf("<merged>");
53
+ const end = answer.lastIndexOf("</merged>");
54
+ if (start === -1 || end <= start)
55
+ throw new Error("Claude's answer had no <merged> section");
56
+ // The file is everything between the tags, minus the newline that follows the opening one.
57
+ const merged = answer.slice(start + "<merged>".length, end).replace(/^\r?\n/, "");
58
+ const conflictText = /<conflict>([\s\S]*?)<\/conflict>/.exec(answer)?.[1]?.trim() ?? "";
59
+ const resolutionText = /<resolution>([\s\S]*?)<\/resolution>/.exec(answer)?.[1]?.trim() ?? "";
60
+ const longest = Math.max(conflict.local.content.length, conflict.remote.content.length);
61
+ if (merged.trim().length === 0)
62
+ throw new Error("Claude returned an empty merge");
63
+ if (merged.length < longest * MIN_LENGTH_RATIO) {
64
+ throw new Error(`the merge is ${merged.length} characters against ${longest}; refusing it as probably truncated`);
65
+ }
66
+ return {
67
+ merged: merged.endsWith("\n") ? merged : `${merged}\n`,
68
+ conflict: conflictText || "Claude did not describe the conflict.",
69
+ resolution: resolutionText || "Claude did not describe the resolution.",
70
+ };
71
+ }
72
+ /** `configDir` is the Claude home that owns the folder, e.g. `~/.claude-chaine` for `~/.claude-chaine/skills`. */
73
+ function runClaude(input, configDir) {
74
+ const claude = findClaude();
75
+ if (claude === undefined)
76
+ return Promise.reject(new Error("the claude CLI is not installed"));
77
+ // Always set, never inherited or left unset: unset, `claude` falls back to `~/.claude` and writes its
78
+ // session files, backups and caches there, recreating that home every time it is deleted.
79
+ const env = { ...process.env, CLAUDE_CONFIG_DIR: configDir };
80
+ return new Promise((resolve, reject) => {
81
+ const child = spawn(claude, [
82
+ "-p", "--output-format", "json", "--tools", "", "--strict-mcp-config", "--setting-sources", "",
83
+ "--no-session-persistence", "--system-prompt", SYSTEM_PROMPT,
84
+ ], { env, cwd: homedir(), stdio: ["pipe", "pipe", "pipe"] });
85
+ let stdout = "";
86
+ let stderr = "";
87
+ child.stdout.on("data", (chunk) => { stdout += chunk.toString(); });
88
+ child.stderr.on("data", (chunk) => { stderr += chunk.toString(); });
89
+ const timer = setTimeout(() => child.kill("SIGTERM"), TIMEOUT_MS);
90
+ child.on("error", (cause) => { clearTimeout(timer); reject(cause); });
91
+ child.on("close", (code) => {
92
+ clearTimeout(timer);
93
+ try {
94
+ const result = JSON.parse(stdout);
95
+ if (code !== 0 || result.is_error || typeof result.result !== "string") {
96
+ reject(new Error(`claude failed (exit ${code}): ${result.result ?? stderr.trim()}`));
97
+ }
98
+ else {
99
+ resolve(result.result);
100
+ }
101
+ }
102
+ catch {
103
+ reject(new Error(`claude failed (exit ${code}): ${stderr.trim() || stdout.slice(0, 300)}`));
104
+ }
105
+ });
106
+ child.stdin.end(input);
107
+ });
108
+ }
109
+ /**
110
+ * The daemon starts at login with the service manager's minimal PATH, which rarely includes where the
111
+ * Claude installer puts `claude`, so the usual install locations are checked too.
112
+ */
113
+ function findClaude() {
114
+ const name = process.platform === "win32" ? "claude.exe" : "claude";
115
+ const fromPath = (process.env["PATH"] ?? "").split(delimiter).filter(Boolean).map((dir) => join(dir, name));
116
+ const known = [
117
+ join(homedir(), ".local", "bin", name),
118
+ join(homedir(), ".claude", "local", "claude"),
119
+ "/opt/homebrew/bin/claude",
120
+ "/usr/local/bin/claude",
121
+ ];
122
+ return [...fromPath, ...known].find((candidate) => existsSync(candidate));
123
+ }
124
+ //# sourceMappingURL=reconcile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../../../src/internal/reconcile.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAwC/D,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,YAAY,EAAE,YAAY,CAAC,CAAC;AACzE,qEAAqE;AACrE,MAAM,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAClC;;;GAGG;AACH,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;sEAegD,CAAC;AAEvE;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,QAAkB;IACvC,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IACxF,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC1D,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5D,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI;QAAE,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC5E,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,QAAkB;IAChD,MAAM,QAAQ,GAAG;QACf,SAAS,QAAQ,CAAC,IAAI,EAAE;QACxB,QAAQ,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,qDAAqD,CAAC,CAAC,CAAC,4CAA4C,QAAQ,CAAC,IAAI,EAAE;QAC5I,OAAO,QAAQ,CAAC,KAAK,CAAC,KAAK,MAAM,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE;QACzD,OAAO,QAAQ,CAAC,MAAM,CAAC,KAAK,MAAM,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE;QAC3D,4BAA4B;KAC7B,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IACzF,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC;IAC5C,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI,GAAG,IAAI,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7F,2FAA2F;IAC3F,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,GAAG,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAClF,MAAM,YAAY,GAAG,kCAAkC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACxF,MAAM,cAAc,GAAG,sCAAsC,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAE9F,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACxF,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IAClF,IAAI,MAAM,CAAC,MAAM,GAAG,OAAO,GAAG,gBAAgB,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,gBAAgB,MAAM,CAAC,MAAM,uBAAuB,OAAO,qCAAqC,CAAC,CAAC;IACpH,CAAC;IACD,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI;QACtD,QAAQ,EAAE,YAAY,IAAI,uCAAuC;QACjE,UAAU,EAAE,cAAc,IAAI,yCAAyC;KACxE,CAAC;AACJ,CAAC;AAED,kHAAkH;AAClH,SAAS,SAAS,CAAC,KAAa,EAAE,SAAiB;IACjD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC,CAAC;IAE9F,sGAAsG;IACtG,0FAA0F;IAC1F,MAAM,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,iBAAiB,EAAE,SAAS,EAAE,CAAC;IAE7D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE;YAC1B,IAAI,EAAE,iBAAiB,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,EAAE;YAC9F,0BAA0B,EAAE,iBAAiB,EAAE,aAAa;SAC7D,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QAE7D,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5E,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5E,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,CAAC,CAAC;QAElE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACtE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAA4C,CAAC;gBAC7E,IAAI,IAAI,KAAK,CAAC,IAAI,MAAM,CAAC,QAAQ,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;oBACvE,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,IAAI,MAAM,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;gBACvF,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACzB,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,CAAC,IAAI,KAAK,CAAC,uBAAuB,IAAI,MAAM,MAAM,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YAC9F,CAAC;QACH,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,SAAS,UAAU;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC;IACpE,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IAC5G,MAAM,KAAK,GAAG;QACZ,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC;QACtC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,CAAC;QAC7C,0BAA0B;QAC1B,uBAAuB;KACxB,CAAC;IACF,OAAO,CAAC,GAAG,QAAQ,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;AAC5E,CAAC"}
@@ -0,0 +1,241 @@
1
+ import { mkdirSync, readFileSync, rmSync, statSync, writeFileSync, existsSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { CONFLICT_SUFFIX, hashBytes, listLocalFiles } from "./config";
4
+ import { invalidPathReason } from "./path";
5
+ import { backUp } from "./reconcile";
6
+ import { skillOf } from "./wire";
7
+ /** True when a report has nothing in it worth telling anyone. */
8
+ export function isQuiet(report) {
9
+ return [report.pulled, report.pushed, report.deletedLocally, report.conflicts, report.merged,
10
+ report.mergeFailures, report.rejected].every((list) => list.length === 0);
11
+ }
12
+ /**
13
+ * `skills` are the skills this folder has opted into. Everything else in the folder is local to it: never
14
+ * pushed, never overwritten, never deleted by a sync.
15
+ */
16
+ export async function syncOnce(config, folder, state, skills, reconciler) {
17
+ const report = {
18
+ pulled: [], pushed: [], deletedLocally: [], conflicts: [], merged: [], mergeFailures: [], reconciliations: [],
19
+ rejected: [], retry: false,
20
+ };
21
+ mkdirSync(folder, { recursive: true });
22
+ // A skill opted in since last cycle may have server files older than the cursor; re-read from the start.
23
+ if ([...skills].some((skill) => !(state.skills ?? []).includes(skill)))
24
+ state.cursor = 0;
25
+ state.skills = [...skills].sort();
26
+ // A skill opted out stops being tracked here. Left in `known`, its files would look deleted from this
27
+ // folder and the push would tombstone them on every machine.
28
+ for (const path of Object.keys(state.known)) {
29
+ if (!skills.has(skillOf(path)))
30
+ delete state.known[path];
31
+ }
32
+ const present = new Set(listLocalFiles(folder, config.exclude).filter((path) => skills.has(skillOf(path))));
33
+ const ctx = { config, folder, state, report, reconciler, skills };
34
+ /**
35
+ * Files deleted here since the last cycle, worked out before anything is fetched.
36
+ *
37
+ * A push does not advance this machine's cursor, so the next pull sees its own earlier write as news.
38
+ * With the file now gone from disk that looked like "remote has it, we do not" and the pull put it
39
+ * straight back, undoing the delete every time. Deciding what was deleted up front, and refusing to
40
+ * recreate those paths, is what lets the push below turn them into tombstones.
41
+ */
42
+ const pendingDeletes = new Set(Object.keys(state.known).filter((path) => !present.has(path)));
43
+ // `present` is passed through both passes and kept current: the pull removes tombstoned files and adds
44
+ // new ones, and a push that read a stale snapshot would try to open a file the pull had just deleted.
45
+ await pull(ctx, pendingDeletes, present);
46
+ await push(ctx, present);
47
+ return report;
48
+ }
49
+ async function pull(ctx, pendingDeletes, present) {
50
+ const { config, folder, state, report } = ctx;
51
+ const response = await call(config, `/files?since=${state.cursor}`);
52
+ const body = await response.json();
53
+ for (const file of body.files) {
54
+ // The server is not trusted to supply a path either: one bad row would otherwise write outside the
55
+ // skills directory on every machine that syncs.
56
+ const badPath = invalidPathReason(file.path);
57
+ if (badPath) {
58
+ report.rejected.push(`${file.path} (${badPath})`);
59
+ continue;
60
+ }
61
+ // Local to this folder: the server's version is not wanted here.
62
+ if (!ctx.skills.has(skillOf(file.path)))
63
+ continue;
64
+ const local = join(folder, file.path);
65
+ // Deleted here and not yet reported; the push pass owns this path for the rest of the cycle.
66
+ if (pendingDeletes.has(file.path))
67
+ continue;
68
+ if (file.deleted) {
69
+ if (existsSync(local)) {
70
+ // Never destroy content this machine never agreed with the server on. A file written here that
71
+ // the server happens to have tombstoned — a fresh machine, or one that was offline through the
72
+ // delete — is not the same file, and deleting it silently would lose work nobody could recover.
73
+ const localHash = hashBytes(readFileSync(local));
74
+ if (localHash !== state.known[file.path]) {
75
+ writeFileSync(`${local}${CONFLICT_SUFFIX}`, readFileSync(local));
76
+ report.conflicts.push(file.path);
77
+ }
78
+ rmSync(local, { force: true });
79
+ report.deletedLocally.push(file.path);
80
+ }
81
+ present.delete(file.path);
82
+ delete state.known[file.path];
83
+ continue;
84
+ }
85
+ const localHash = existsSync(local) ? hashBytes(readFileSync(local)) : undefined;
86
+ if (localHash === file.hash) {
87
+ // Already identical — record agreement so the push pass does not treat it as a local edit.
88
+ state.known[file.path] = file.hash;
89
+ continue;
90
+ }
91
+ const content = new Uint8Array(await (await call(config, `/content/${file.hash}`)).arrayBuffer());
92
+ // Changed here and on the server. Merge the two when possible: the merge is written here, recorded as
93
+ // based on the server's copy, and so goes out in this cycle's push.
94
+ if (localHash !== undefined && localHash !== state.known[file.path]) {
95
+ if (await mergeInto(ctx, file, local, content)) {
96
+ present.add(file.path);
97
+ continue;
98
+ }
99
+ // No merge: a local edit we have not pushed yet would be destroyed by overwriting, so it is kept
100
+ // beside the incoming copy.
101
+ writeFileSync(`${local}${CONFLICT_SUFFIX}`, readFileSync(local));
102
+ report.conflicts.push(file.path);
103
+ }
104
+ mkdirSync(dirname(local), { recursive: true });
105
+ writeFileSync(local, content);
106
+ state.known[file.path] = file.hash;
107
+ present.add(file.path);
108
+ report.pulled.push(file.path);
109
+ }
110
+ state.cursor = body.cursor;
111
+ }
112
+ /**
113
+ * Merges this machine's copy with the server's and writes the result over the local file. False when
114
+ * there is no merger, either side is not text, or the merge failed — the caller then keeps `.local`.
115
+ */
116
+ async function mergeInto(ctx, file, local, remote) {
117
+ const { config, folder, state, report, reconciler } = ctx;
118
+ if (reconciler === undefined)
119
+ return false;
120
+ const mine = readFileSync(local);
121
+ const localText = asText(mine);
122
+ const remoteText = asText(remote);
123
+ if (localText === undefined || remoteText === undefined)
124
+ return false;
125
+ const known = state.known[file.path];
126
+ const base = known === undefined ? null : await fetchText(config, known);
127
+ const conflict = {
128
+ folder,
129
+ path: file.path,
130
+ local: {
131
+ content: localText,
132
+ label: `the copy in ${folder} on ${config.machine}, last edited ${statSync(local).mtime.toISOString()}`,
133
+ },
134
+ remote: {
135
+ content: remoteText,
136
+ label: `the synced copy, last uploaded by ${file.updatedBy} at ${new Date(file.updatedAt).toISOString()}`,
137
+ },
138
+ base,
139
+ };
140
+ backUp(conflict);
141
+ reconciler.onMerging?.(file.path);
142
+ let resolution;
143
+ try {
144
+ resolution = await reconciler.merge(conflict);
145
+ }
146
+ catch (cause) {
147
+ const reason = cause instanceof Error ? cause.message : String(cause);
148
+ report.mergeFailures.push(`${file.path}: ${reason}`);
149
+ report.reconciliations.push({
150
+ path: file.path, outcome: "failed",
151
+ conflict: "This machine's copy and the server's differ.",
152
+ resolution: `Not merged: ${reason}. The server's version was kept and this machine's saved as ${file.path}${CONFLICT_SUFFIX}.`,
153
+ });
154
+ return false;
155
+ }
156
+ writeFileSync(local, resolution.merged);
157
+ state.known[file.path] = file.hash;
158
+ report.merged.push(file.path);
159
+ report.reconciliations.push({
160
+ path: file.path, outcome: "merged", conflict: resolution.conflict, resolution: resolution.resolution,
161
+ });
162
+ return true;
163
+ }
164
+ /** Text only: a merge by a language model is meaningless for an image or a binary, and harmful. */
165
+ function asText(bytes) {
166
+ if (bytes.length > 512 * 1024 || bytes.includes(0))
167
+ return undefined;
168
+ try {
169
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes);
170
+ }
171
+ catch {
172
+ return undefined;
173
+ }
174
+ }
175
+ /** The base is a nicety for the merge; if the server no longer has it, merge without one. */
176
+ async function fetchText(config, hash) {
177
+ try {
178
+ return asText(new Uint8Array(await (await call(config, `/content/${hash}`)).arrayBuffer())) ?? null;
179
+ }
180
+ catch {
181
+ return null;
182
+ }
183
+ }
184
+ async function push(ctx, present) {
185
+ const { config, folder, state, report } = ctx;
186
+ for (const path of present) {
187
+ const bytes = readFileSync(join(folder, path));
188
+ const hash = hashBytes(bytes);
189
+ if (state.known[path] === hash)
190
+ continue;
191
+ const accepted = await put(config, {
192
+ path,
193
+ baseHash: state.known[path] ?? null,
194
+ hash,
195
+ content: Buffer.from(bytes).toString("base64"),
196
+ });
197
+ if (accepted) {
198
+ state.known[path] = hash;
199
+ report.pushed.push(path);
200
+ }
201
+ else {
202
+ // Another machine wrote first. Not a conflict yet: the next cycle pulls its version and merges.
203
+ report.retry = true;
204
+ }
205
+ }
206
+ // A path we previously agreed on that is no longer on disk was deleted here, and the server needs the
207
+ // tombstone or the next machine to sync will upload it back.
208
+ for (const path of Object.keys(state.known)) {
209
+ if (present.has(path))
210
+ continue;
211
+ if (await put(config, { path, baseHash: state.known[path], hash: null, content: null })) {
212
+ delete state.known[path];
213
+ report.pushed.push(`${path} (deleted)`);
214
+ }
215
+ else {
216
+ report.conflicts.push(path);
217
+ }
218
+ }
219
+ }
220
+ /** True when the server accepted the write; false means another machine got there first. */
221
+ async function put(config, body) {
222
+ const response = await fetch(`${config.endpoint}/files`, {
223
+ method: "PUT",
224
+ headers: { authorization: `Bearer ${config.token}`, "content-type": "application/json" },
225
+ body: JSON.stringify({ ...body, machine: config.machine }),
226
+ });
227
+ if (response.status === 409)
228
+ return false;
229
+ if (!response.ok)
230
+ throw new Error(`PUT /files ${body.path}: ${response.status} ${await response.text()}`);
231
+ return true;
232
+ }
233
+ async function call(config, path) {
234
+ const response = await fetch(`${config.endpoint}${path}`, {
235
+ headers: { authorization: `Bearer ${config.token}` },
236
+ });
237
+ if (!response.ok)
238
+ throw new Error(`GET ${path}: ${response.status} ${await response.text()}`);
239
+ return response;
240
+ }
241
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../../../src/internal/sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The shapes the daemon and the service exchange.
3
+ *
4
+ * Declared in the library and imported by the service so there is one definition rather than two that
5
+ * drift. The service depends on the library; nothing goes the other way.
6
+ */
7
+ /**
8
+ * A skill is a top-level entry of a skills folder — usually a directory like `jive/` holding `SKILL.md`
9
+ * and its files — and it is the unit of opting in: all of its files sync, or none do, so a skill never
10
+ * arrives half-present on another machine.
11
+ */
12
+ export const skillOf = (path) => path.split("/")[0];
13
+ //# sourceMappingURL=wire.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire.js","sourceRoot":"","sources":["../../../../src/internal/wire.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAoCH;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC"}