@xultrax-web/agent-memory-mcp 0.8.0 → 0.9.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.
Files changed (4) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +52 -3
  3. package/dist/index.js +283 -1
  4. package/package.json +1 -1
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 xultrax-web
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xultrax-web
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -200,6 +200,9 @@ Custom path:
200
200
  | `verify_memory` | Re-evaluate a memory's claims. Extracts URLs/dates/file refs, flags stale-date signals, returns type-specific verification heuristics. Pairs with the `audit_stale` prompt. |
201
201
  | `find_backlinks` | List memories that link to the given memory via `[[wiki-link]]` syntax in their bodies. Useful for "what references this" views. |
202
202
  | `find_related` | Surface memories related to one by combining outbound links, inbound backlinks, shared tags, type match, and content similarity. Navigates the memory graph by association. |
203
+ | `sync_status` | Report git-sync state: remote URL, branch, uncommitted local files, ahead/behind origin. |
204
+ | `sync_push` | Commit local memory changes + push to the configured git remote. Auto-timestamps the commit message if none given. |
205
+ | `sync_pull` | Fast-forward pull from the git remote. Refuses to pull if local changes are uncommitted. |
203
206
 
204
207
  ### Prompts
205
208
 
@@ -265,8 +268,44 @@ agent-memory save my-mem --type project --description "X" --content "Body" --tag
265
268
  agent-memory list --tags "production" # filter by tag (intersection)
266
269
  agent-memory backlinks deploy-process # memories that link to deploy-process
267
270
  agent-memory related deploy-process # ranked discovery: links + tags + similarity
271
+ agent-memory sync init git@github.com:you/agent-memory.git # multi-machine setup (one-time)
272
+ agent-memory sync push # commit + push local changes
273
+ agent-memory sync pull # fast-forward from remote
274
+ agent-memory sync status # local + ahead/behind state
268
275
  ```
269
276
 
277
+ ### Multi-machine memory (git sync)
278
+
279
+ The killer feature for file-based memory: every dev machine has git, and markdown merges cleanly. `agent-memory sync` turns `.agent-memory/` into a git repo pointed at a (private) remote, and your memories follow you across desktop/laptop/server.
280
+
281
+ ```bash
282
+ # One-time setup
283
+ agent-memory sync init git@github.com:you/agent-memory.git
284
+
285
+ # End of the day on desktop
286
+ agent-memory sync push
287
+
288
+ # Pick up your laptop before bed
289
+ agent-memory sync pull
290
+
291
+ # Save a new memory while reading in bed
292
+ agent-memory save bedtime-thought --type project --description "..." --content "..."
293
+ agent-memory sync push
294
+
295
+ # Next morning at desktop
296
+ agent-memory sync pull # picks up the bedtime memory
297
+ ```
298
+
299
+ What's NOT synced (per-machine state, kept local):
300
+
301
+ - `.lock` — per-process file lock
302
+ - `.events.jsonl` — per-machine audit trail
303
+ - `.trash/` — soft-delete staging
304
+
305
+ What IS synced: every memory file, the `MEMORY.md` index, and any `.gitignore` you add.
306
+
307
+ Commits use the identity `agent-memory <agent-memory@local>` by default — set `GIT_AUTHOR_EMAIL` / `GIT_COMMITTER_EMAIL` in your environment if you want per-machine attribution.
308
+
270
309
  ### Audit log + structured logging
271
310
 
272
311
  Every mutation appends one JSON line to `.agent-memory/.events.jsonl`:
@@ -379,11 +418,21 @@ This server is built to be used daily, not to demo well once.
379
418
  - **`find_backlinks`** tool + `agent-memory backlinks <name>` CLI — "what links to this".
380
419
  - **`find_related`** tool + `agent-memory related <name>` CLI — combines outbound + inbound links, shared tags, type match, and content similarity into a ranked discovery view.
381
420
 
382
- **Landing in v0.9+:**
421
+ **Shipped in v0.9 · the moat — multi-machine memory via git:**
422
+
423
+ - **`agent-memory sync init <remote-url>`** — convert `.agent-memory/` into a git repo, push to remote.
424
+ - **`agent-memory sync push`** — auto-commit local changes + push.
425
+ - **`agent-memory sync pull`** — fast-forward from remote.
426
+ - **`agent-memory sync status`** — local state + commits ahead/behind origin.
427
+ - **`agent-memory sync log`** — history of cross-machine memory changes.
428
+ - **`sync_status` / `sync_push` / `sync_pull` MCP tools** — the LLM can do this too.
429
+ - Per-machine state (`.lock`, `.events.jsonl`, `.trash/`) auto-excluded from sync.
430
+ - Default commit identity injected (`agent-memory@local`) so machines without `git config --global user.email` work without setup.
431
+
432
+ **Landing in v0.10+:**
383
433
 
384
- - Folder support (`.agent-memory/work/`, `.agent-memory/personal/`)
385
434
  - TUI / web UI for browsing + editing memories in a clean interface
386
- - `agent-memory sync` for git-backed multi-machine memory (the moat)
435
+ - Folder support (`.agent-memory/work/`, `.agent-memory/personal/`)
387
436
  - Memory packs for shareable curated bundles
388
437
 
389
438
  ---
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
26
26
  import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
27
27
  import Fuse from "fuse.js";
28
28
  import matter from "gray-matter";
29
+ import { spawnSync } from "node:child_process";
29
30
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync, } from "node:fs";
30
31
  import { homedir } from "node:os";
31
32
  import { join, resolve } from "node:path";
@@ -913,6 +914,218 @@ function toolFindRelated(args) {
913
914
  return lines.join("\n");
914
915
  }
915
916
  // -------------------------------------------------------------
917
+ // Git sync · multi-machine memory via git remote
918
+ // -------------------------------------------------------------
919
+ //
920
+ // The killer feature for file-based memory: every dev machine has git,
921
+ // and markdown files merge cleanly. Convert .agent-memory/ into a git
922
+ // repo, point it at a (private) GitHub repo, and `sync push` / `sync
923
+ // pull` becomes the multi-machine story.
924
+ //
925
+ // Usage flow:
926
+ // agent-memory sync init git@github.com:you/agent-memory.git
927
+ // ... save some memories ...
928
+ // agent-memory sync push # commit + push
929
+ // # later, on another machine:
930
+ // agent-memory sync pull # pull updates
931
+ // agent-memory sync status # ahead/behind/clean
932
+ //
933
+ // Files we EXCLUDE from sync (per-machine state):
934
+ // .lock · proper-lockfile per-process lock
935
+ // .events.jsonl · per-machine audit log
936
+ // .trash/ · per-machine soft-delete staging
937
+ const SYNC_GITIGNORE = "# Per-machine state · do not sync across devices\n" +
938
+ ".lock\n" +
939
+ ".events.jsonl\n" +
940
+ ".trash/\n";
941
+ function git(args) {
942
+ // Inject a default commit identity so machines without `git config
943
+ // --global user.email` can still sync. Env vars are git's highest-
944
+ // precedence identity source, so they override any later config —
945
+ // fine for automated memory-sync where per-commit attribution
946
+ // doesn't matter. Honors operator overrides if set in the environment.
947
+ const result = spawnSync("git", args, {
948
+ cwd: MEMORY_DIR,
949
+ encoding: "utf8",
950
+ env: {
951
+ ...process.env,
952
+ GIT_AUTHOR_NAME: process.env.GIT_AUTHOR_NAME ?? "agent-memory",
953
+ GIT_AUTHOR_EMAIL: process.env.GIT_AUTHOR_EMAIL ?? "agent-memory@local",
954
+ GIT_COMMITTER_NAME: process.env.GIT_COMMITTER_NAME ?? "agent-memory",
955
+ GIT_COMMITTER_EMAIL: process.env.GIT_COMMITTER_EMAIL ?? "agent-memory@local",
956
+ },
957
+ });
958
+ return {
959
+ stdout: (result.stdout ?? "").trim(),
960
+ stderr: (result.stderr ?? "").trim(),
961
+ exitCode: result.status ?? -1,
962
+ };
963
+ }
964
+ function isGitRepo() {
965
+ return existsSync(join(MEMORY_DIR, ".git"));
966
+ }
967
+ function requireRepo() {
968
+ if (!isGitRepo()) {
969
+ throw new Error(`${MEMORY_DIR} is not a git repo. Run 'agent-memory sync init <remote-url>' first.`);
970
+ }
971
+ }
972
+ function toolSyncInit(args) {
973
+ const remoteUrl = String(args.remote ?? args.url ?? "").trim();
974
+ if (!remoteUrl) {
975
+ throw new Error("Usage: agent-memory sync init <remote-url>\nExample: agent-memory sync init git@github.com:you/agent-memory.git");
976
+ }
977
+ ensureStorage();
978
+ if (isGitRepo()) {
979
+ return `${MEMORY_DIR} is already a git repo. Use 'sync push' or 'sync pull'.`;
980
+ }
981
+ const init = git(["init", "-b", "main"]);
982
+ if (init.exitCode !== 0)
983
+ throw new Error(`git init failed: ${init.stderr}`);
984
+ writeFileSync(join(MEMORY_DIR, ".gitignore"), SYNC_GITIGNORE, "utf8");
985
+ const addRemote = git(["remote", "add", "origin", remoteUrl]);
986
+ if (addRemote.exitCode !== 0)
987
+ throw new Error(`git remote add failed: ${addRemote.stderr}`);
988
+ const add = git(["add", "-A"]);
989
+ if (add.exitCode !== 0)
990
+ throw new Error(`git add failed: ${add.stderr}`);
991
+ const commit = git(["commit", "-m", "agent-memory · initial sync"]);
992
+ // commit can fail if there's nothing to commit (empty store) — that's OK
993
+ if (commit.exitCode !== 0 && !commit.stderr.includes("nothing to commit")) {
994
+ log("warn", "initial commit had no changes", { stderr: commit.stderr });
995
+ }
996
+ const push = git(["push", "-u", "origin", "main"]);
997
+ logEvent("sync_init", { remote: remoteUrl, pushed: push.exitCode === 0 });
998
+ const lines = [
999
+ c(ANSI.green, "✓ initialized memory sync"),
1000
+ ` storage : ${MEMORY_DIR}`,
1001
+ ` remote : ${remoteUrl}`,
1002
+ ` branch : main`,
1003
+ ];
1004
+ if (push.exitCode !== 0) {
1005
+ lines.push("");
1006
+ lines.push(c(ANSI.yellow, "Initial push failed (remote may not exist yet):"));
1007
+ lines.push(` ${push.stderr.split("\n")[0]}`);
1008
+ lines.push("");
1009
+ lines.push("Create the empty remote on GitHub (or your git host), then run:");
1010
+ lines.push(" agent-memory sync push");
1011
+ }
1012
+ else {
1013
+ lines.push("");
1014
+ lines.push("Future commands: 'sync push' / 'sync pull' / 'sync status' / 'sync log'");
1015
+ }
1016
+ return lines.join("\n");
1017
+ }
1018
+ function toolSyncStatus(_args) {
1019
+ if (!isGitRepo()) {
1020
+ return `${MEMORY_DIR} is not a git repo. Use 'sync init <remote-url>' to set it up.`;
1021
+ }
1022
+ const remote = git(["remote", "get-url", "origin"]);
1023
+ const branch = git(["branch", "--show-current"]);
1024
+ const fetch = git(["fetch", "origin", "--quiet"]);
1025
+ const offline = fetch.exitCode !== 0;
1026
+ const status = git(["status", "--porcelain"]);
1027
+ const localChanges = status.stdout.split("\n").filter(Boolean).length;
1028
+ const lines = [];
1029
+ lines.push(c(ANSI.bold, "agent-memory sync · status"));
1030
+ lines.push(` storage : ${MEMORY_DIR}`);
1031
+ lines.push(` remote : ${remote.stdout || c(ANSI.yellow, "(none configured)")}`);
1032
+ lines.push(` branch : ${branch.stdout || "(unknown)"}`);
1033
+ if (offline) {
1034
+ lines.push(` fetch : ${c(ANSI.yellow, "offline — couldn't reach remote")}`);
1035
+ }
1036
+ lines.push("");
1037
+ lines.push(c(ANSI.bold, "local state:"));
1038
+ if (localChanges === 0) {
1039
+ lines.push(` ${c(ANSI.green, "✓ clean")} — no uncommitted changes`);
1040
+ }
1041
+ else {
1042
+ lines.push(` ${c(ANSI.yellow, `${localChanges} file(s) uncommitted`)} — run 'sync push' to commit + send`);
1043
+ }
1044
+ if (!offline && branch.stdout) {
1045
+ const ahead = git(["rev-list", "--count", `origin/${branch.stdout}..HEAD`]);
1046
+ const behind = git(["rev-list", "--count", `HEAD..origin/${branch.stdout}`]);
1047
+ const aheadN = Number(ahead.stdout || "0");
1048
+ const behindN = Number(behind.stdout || "0");
1049
+ lines.push("");
1050
+ lines.push(c(ANSI.bold, "vs origin:"));
1051
+ if (aheadN === 0 && behindN === 0) {
1052
+ lines.push(` ${c(ANSI.green, "✓ in sync")}`);
1053
+ }
1054
+ else {
1055
+ if (aheadN > 0)
1056
+ lines.push(` ${c(ANSI.cyan, `↑ ${aheadN} commit(s) ahead`)} — run 'sync push'`);
1057
+ if (behindN > 0)
1058
+ lines.push(` ${c(ANSI.cyan, `↓ ${behindN} commit(s) behind`)} — run 'sync pull'`);
1059
+ }
1060
+ }
1061
+ return lines.join("\n");
1062
+ }
1063
+ function toolSyncPush(args) {
1064
+ requireRepo();
1065
+ const message = args.message
1066
+ ? String(args.message)
1067
+ : `agent-memory · sync ${new Date().toISOString().slice(0, 19).replace("T", " ")}Z`;
1068
+ const add = git(["add", "-A"]);
1069
+ if (add.exitCode !== 0)
1070
+ throw new Error(`git add failed: ${add.stderr}`);
1071
+ const status = git(["status", "--porcelain"]);
1072
+ const hadChanges = status.stdout.length > 0;
1073
+ if (hadChanges) {
1074
+ const commit = git(["commit", "-m", message]);
1075
+ if (commit.exitCode !== 0)
1076
+ throw new Error(`commit failed: ${commit.stderr}`);
1077
+ }
1078
+ const push = git(["push"]);
1079
+ if (push.exitCode !== 0)
1080
+ throw new Error(`push failed: ${push.stderr}`);
1081
+ logEvent("sync_push", { hadChanges, commitMessage: hadChanges ? message : null });
1082
+ return hadChanges
1083
+ ? c(ANSI.green, `✓ committed local changes + pushed to remote\n message: ${message}`)
1084
+ : c(ANSI.green, `✓ nothing new locally; pushed any unpushed commits`);
1085
+ }
1086
+ function toolSyncPull(_args) {
1087
+ requireRepo();
1088
+ const status = git(["status", "--porcelain"]);
1089
+ if (status.stdout) {
1090
+ return (c(ANSI.yellow, "Local changes uncommitted.") +
1091
+ "\nRun 'agent-memory sync push' first to commit them, then pull.");
1092
+ }
1093
+ const pull = git(["pull", "--ff-only"]);
1094
+ if (pull.exitCode !== 0) {
1095
+ return (c(ANSI.red, "✗ pull failed:") +
1096
+ `\n ${pull.stderr.split("\n").slice(0, 3).join("\n ")}\n\n` +
1097
+ `Likely diverged history (commits on both sides). Resolve manually:\n` +
1098
+ ` cd ${MEMORY_DIR}\n` +
1099
+ ` git pull # do the merge by hand`);
1100
+ }
1101
+ logEvent("sync_pull", { output: pull.stdout.split("\n")[0] });
1102
+ return c(ANSI.green, "✓ pulled from remote") + (pull.stdout ? `\n${pull.stdout}` : "");
1103
+ }
1104
+ function toolSyncLog(args) {
1105
+ if (!isGitRepo())
1106
+ return `${MEMORY_DIR} is not a git repo.`;
1107
+ const limit = args.limit ? Number(args.limit) : 20;
1108
+ const log = git(["log", `--max-count=${limit}`, "--pretty=format:%h %ci %s", "--no-decorate"]);
1109
+ if (log.exitCode !== 0)
1110
+ return `git log failed: ${log.stderr}`;
1111
+ if (!log.stdout)
1112
+ return "No sync history yet.";
1113
+ const lines = [];
1114
+ lines.push(c(ANSI.bold, `Recent sync history (last ${limit}):`));
1115
+ lines.push("");
1116
+ for (const line of log.stdout.split("\n")) {
1117
+ // Format: <short-sha> <iso-date> <subject>
1118
+ const m = line.match(/^(\S+)\s+(\S+\s+\S+\s+\S+)\s+(.*)$/);
1119
+ if (m) {
1120
+ lines.push(` ${c(ANSI.cyan, m[1])} ${c(ANSI.dim, m[2])} ${m[3]}`);
1121
+ }
1122
+ else {
1123
+ lines.push(` ${line}`);
1124
+ }
1125
+ }
1126
+ return lines.join("\n");
1127
+ }
1128
+ // -------------------------------------------------------------
916
1129
  // Stats · operator dashboard
917
1130
  // -------------------------------------------------------------
918
1131
  function toolStats(_args) {
@@ -1022,7 +1235,7 @@ function actionColor(action) {
1022
1235
  // -------------------------------------------------------------
1023
1236
  // Server wiring
1024
1237
  // -------------------------------------------------------------
1025
- const server = new Server({ name: "agent-memory", version: "0.8.0" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
1238
+ const server = new Server({ name: "agent-memory", version: "0.9.0" }, { capabilities: { tools: {}, resources: {}, prompts: {} } });
1026
1239
  // -------------------------------------------------------------
1027
1240
  // Resource URI scheme
1028
1241
  // -------------------------------------------------------------
@@ -1434,6 +1647,29 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
1434
1647
  required: ["name"],
1435
1648
  },
1436
1649
  },
1650
+ {
1651
+ name: "sync_status",
1652
+ description: "Report the git-sync state of the memory store: remote URL, branch, local uncommitted files, commits ahead/behind origin. Use this before opening a new session to know if you have stale memories from another machine.",
1653
+ inputSchema: { type: "object", properties: {} },
1654
+ },
1655
+ {
1656
+ name: "sync_push",
1657
+ description: "Commit any local memory changes and push to the configured git remote. Auto-generates a timestamped commit message if none provided. Use at the end of a session to make memories available on your other machines.",
1658
+ inputSchema: {
1659
+ type: "object",
1660
+ properties: {
1661
+ message: {
1662
+ type: "string",
1663
+ description: "Optional commit message. Defaults to a timestamp.",
1664
+ },
1665
+ },
1666
+ },
1667
+ },
1668
+ {
1669
+ name: "sync_pull",
1670
+ description: "Pull memory updates from the configured git remote (fast-forward only). Run at the start of a session to get memories saved on other machines. Refuses to pull if there are uncommitted local changes.",
1671
+ inputSchema: { type: "object", properties: {} },
1672
+ },
1437
1673
  ],
1438
1674
  }));
1439
1675
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -1480,6 +1716,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1480
1716
  case "find_related":
1481
1717
  result = toolFindRelated(args);
1482
1718
  break;
1719
+ case "sync_status":
1720
+ result = toolSyncStatus(args);
1721
+ break;
1722
+ case "sync_push":
1723
+ result = toolSyncPush(args);
1724
+ break;
1725
+ case "sync_pull":
1726
+ result = toolSyncPull(args);
1727
+ break;
1483
1728
  default:
1484
1729
  throw new Error(`Unknown tool: ${name}`);
1485
1730
  }
@@ -1515,6 +1760,7 @@ const CLI_COMMANDS = new Set([
1515
1760
  "verify",
1516
1761
  "backlinks",
1517
1762
  "related",
1763
+ "sync",
1518
1764
  "import-claude-code",
1519
1765
  "help",
1520
1766
  "--help",
@@ -1670,6 +1916,36 @@ async function cliMain(command, rest) {
1670
1916
  }) + "\n");
1671
1917
  return 0;
1672
1918
  }
1919
+ case "sync": {
1920
+ const sub = positional[0];
1921
+ if (!sub) {
1922
+ throw new Error("Usage: agent-memory sync <init|push|pull|status|log>\n" +
1923
+ " init <remote-url> set up a new memory-sync repo\n" +
1924
+ " push [--message X] commit + push local changes\n" +
1925
+ " pull fast-forward pull from remote\n" +
1926
+ " status show local + remote state\n" +
1927
+ " log [--limit N] recent sync commit history");
1928
+ }
1929
+ switch (sub) {
1930
+ case "init":
1931
+ process.stdout.write(toolSyncInit({ remote: positional[1] }) + "\n");
1932
+ return 0;
1933
+ case "push":
1934
+ process.stdout.write(toolSyncPush({ message: flags.message }) + "\n");
1935
+ return 0;
1936
+ case "pull":
1937
+ process.stdout.write(toolSyncPull({}) + "\n");
1938
+ return 0;
1939
+ case "status":
1940
+ process.stdout.write(toolSyncStatus({}) + "\n");
1941
+ return 0;
1942
+ case "log":
1943
+ process.stdout.write(toolSyncLog({ limit: flags.limit ? Number(flags.limit) : undefined }) + "\n");
1944
+ return 0;
1945
+ default:
1946
+ throw new Error(`Unknown sync subcommand: ${sub}. Try 'sync' for help.`);
1947
+ }
1948
+ }
1673
1949
  case "log": {
1674
1950
  process.stdout.write(toolLogEvents({
1675
1951
  tail: flags.tail ? Number(flags.tail) : undefined,
@@ -1728,6 +2004,12 @@ COMMANDS
1728
2004
  backlinks <name> List memories that link to <name> via [[wiki-links]].
1729
2005
  related <name> [--max N] Surface related memories via outbound + inbound links,
1730
2006
  shared tags, type match, content similarity.
2007
+ sync <init|push|pull|status|log> Multi-machine memory via git remote.
2008
+ sync init <remote-url> Initialize .agent-memory/ as a git repo + push.
2009
+ sync push [--message X] Commit local changes + push to remote.
2010
+ sync pull Fast-forward pull from remote.
2011
+ sync status Show local + ahead/behind state.
2012
+ sync log [--limit N] Recent sync commit history.
1731
2013
  import-claude-code [--source <path>] [--project <pat>] [--overwrite] [--dry-run]
1732
2014
  Walk ~/.claude/projects/*/memory/ and
1733
2015
  import each memory into the current store.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xultrax-web/agent-memory-mcp",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "mcpName": "io.github.xultrax-web/agent-memory-mcp",
5
5
  "description": "Markdown memory for AI agents. Plain files you can read, edit, grep, and commit. The only MCP memory server that isn't a database.",
6
6
  "type": "module",