@agentmemory/agentmemory 0.9.17 → 0.9.19

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 (36) hide show
  1. package/.env.example +6 -0
  2. package/AGENTS.md +2 -2
  3. package/README.md +19 -5
  4. package/dist/.env.example +6 -0
  5. package/dist/cli.mjs +19 -9
  6. package/dist/cli.mjs.map +1 -1
  7. package/dist/hooks/post-commit.d.mts +1 -0
  8. package/dist/hooks/post-commit.mjs +102 -0
  9. package/dist/hooks/post-commit.mjs.map +1 -0
  10. package/dist/index.mjs +537 -91
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/{src-TiNuQ3Ub.mjs → src-2wwYDPGA.mjs} +503 -91
  13. package/dist/src-2wwYDPGA.mjs.map +1 -0
  14. package/dist/{standalone-BIXq6S80.mjs → standalone-DMLk7YxP.mjs} +17 -8
  15. package/dist/standalone-DMLk7YxP.mjs.map +1 -0
  16. package/dist/standalone.mjs +49 -7
  17. package/dist/standalone.mjs.map +1 -1
  18. package/dist/{tools-registry-BFKFKmYh.mjs → tools-registry-Dz8ssuMf.mjs} +34 -1
  19. package/dist/tools-registry-Dz8ssuMf.mjs.map +1 -0
  20. package/dist/viewer/favicon.svg +1 -0
  21. package/dist/viewer/index.html +190 -60
  22. package/package.json +2 -2
  23. package/plugin/.claude-plugin/plugin.json +1 -1
  24. package/plugin/.codex-plugin/plugin.json +1 -1
  25. package/plugin/.mcp.json +5 -1
  26. package/plugin/hooks/hooks.codex.json +4 -0
  27. package/plugin/scripts/post-commit.d.mts +1 -0
  28. package/plugin/scripts/post-commit.mjs +102 -0
  29. package/plugin/scripts/post-commit.mjs.map +1 -0
  30. package/plugin/skills/commit-context/SKILL.md +19 -0
  31. package/plugin/skills/commit-history/SKILL.md +20 -0
  32. package/plugin/skills/handoff/SKILL.md +21 -0
  33. package/plugin/skills/recap/SKILL.md +25 -0
  34. package/dist/src-TiNuQ3Ub.mjs.map +0 -1
  35. package/dist/standalone-BIXq6S80.mjs.map +0 -1
  36. package/dist/tools-registry-BFKFKmYh.mjs.map +0 -1
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ import { execFile } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+
5
+ //#region src/hooks/post-commit.ts
6
+ const exec = promisify(execFile);
7
+ function isSdkChildContext(payload) {
8
+ if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true;
9
+ if (!payload || typeof payload !== "object") return false;
10
+ return payload.entrypoint === "sdk-ts";
11
+ }
12
+ const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111";
13
+ const SECRET = process.env["AGENTMEMORY_SECRET"] || "";
14
+ const TIMEOUT_MS = 1500;
15
+ function authHeaders() {
16
+ const h = { "Content-Type": "application/json" };
17
+ if (SECRET) h["Authorization"] = `Bearer ${SECRET}`;
18
+ return h;
19
+ }
20
+ async function git(args, cwd) {
21
+ try {
22
+ const { stdout } = await exec("git", args, {
23
+ cwd,
24
+ timeout: 1500
25
+ });
26
+ return stdout.trim();
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ async function main() {
32
+ let input = "";
33
+ for await (const chunk of process.stdin) input += chunk;
34
+ let data = {};
35
+ if (input.trim()) try {
36
+ data = JSON.parse(input);
37
+ } catch {}
38
+ if (isSdkChildContext(data)) return;
39
+ const cwd = data.cwd || process.env["AGENTMEMORY_CWD"] || process.cwd();
40
+ const sessionId = data.session_id || process.env["AGENTMEMORY_SESSION_ID"] || void 0;
41
+ const sha = process.env["AGENTMEMORY_COMMIT_SHA"] || await git(["rev-parse", "HEAD"], cwd);
42
+ if (!sha) return;
43
+ const branch = await git([
44
+ "rev-parse",
45
+ "--abbrev-ref",
46
+ "HEAD"
47
+ ], cwd);
48
+ const repo = await git([
49
+ "config",
50
+ "--get",
51
+ "remote.origin.url"
52
+ ], cwd);
53
+ const message = await git([
54
+ "log",
55
+ "-1",
56
+ "--pretty=%B",
57
+ sha
58
+ ], cwd);
59
+ const author = await git([
60
+ "log",
61
+ "-1",
62
+ "--pretty=%an <%ae>",
63
+ sha
64
+ ], cwd);
65
+ const authoredAt = await git([
66
+ "log",
67
+ "-1",
68
+ "--pretty=%aI",
69
+ sha
70
+ ], cwd);
71
+ const filesRaw = await git([
72
+ "diff-tree",
73
+ "--no-commit-id",
74
+ "--name-only",
75
+ "-r",
76
+ sha
77
+ ], cwd);
78
+ const files = filesRaw ? filesRaw.split("\n").filter(Boolean) : void 0;
79
+ const body = {
80
+ sessionId,
81
+ sha,
82
+ branch: branch || void 0,
83
+ repo: repo || void 0,
84
+ message: message || void 0,
85
+ author: author || void 0,
86
+ authoredAt: authoredAt || void 0,
87
+ files
88
+ };
89
+ try {
90
+ await fetch(`${REST_URL}/agentmemory/session/commit`, {
91
+ method: "POST",
92
+ headers: authHeaders(),
93
+ body: JSON.stringify(body),
94
+ signal: AbortSignal.timeout(TIMEOUT_MS)
95
+ });
96
+ } catch {}
97
+ }
98
+ main();
99
+
100
+ //#endregion
101
+ export { };
102
+ //# sourceMappingURL=post-commit.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"post-commit.mjs","names":[],"sources":["../../src/hooks/post-commit.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\n\nconst exec = promisify(execFile);\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = process.env[\"AGENTMEMORY_SECRET\"] || \"\";\nconst TIMEOUT_MS = 1500;\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function git(args: string[], cwd: string): Promise<string | null> {\n try {\n const { stdout } = await exec(\"git\", args, { cwd, timeout: 1500 });\n return stdout.trim();\n } catch {\n return null;\n }\n}\n\nasync function main() {\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown> = {};\n if (input.trim()) {\n try {\n data = JSON.parse(input);\n } catch {\n // Direct invocation from .git/hooks/post-commit may pass no stdin.\n }\n }\n\n if (isSdkChildContext(data)) return;\n\n const cwd =\n (data.cwd as string) ||\n process.env[\"AGENTMEMORY_CWD\"] ||\n process.cwd();\n const sessionId =\n (data.session_id as string) ||\n process.env[\"AGENTMEMORY_SESSION_ID\"] ||\n undefined;\n\n const sha =\n process.env[\"AGENTMEMORY_COMMIT_SHA\"] ||\n (await git([\"rev-parse\", \"HEAD\"], cwd));\n if (!sha) return;\n\n const branch = await git([\"rev-parse\", \"--abbrev-ref\", \"HEAD\"], cwd);\n const repo = await git([\"config\", \"--get\", \"remote.origin.url\"], cwd);\n const message = await git([\"log\", \"-1\", \"--pretty=%B\", sha], cwd);\n const author = await git([\"log\", \"-1\", \"--pretty=%an <%ae>\", sha], cwd);\n const authoredAt = await git([\"log\", \"-1\", \"--pretty=%aI\", sha], cwd);\n const filesRaw = await git(\n [\"diff-tree\", \"--no-commit-id\", \"--name-only\", \"-r\", sha],\n cwd,\n );\n const files = filesRaw ? filesRaw.split(\"\\n\").filter(Boolean) : undefined;\n\n const body = {\n sessionId,\n sha,\n branch: branch || undefined,\n repo: repo || undefined,\n message: message || undefined,\n author: author || undefined,\n authoredAt: authoredAt || undefined,\n files,\n };\n\n try {\n await fetch(`${REST_URL}/agentmemory/session/commit`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(TIMEOUT_MS),\n });\n } catch {\n // best-effort\n }\n}\n\nmain();\n"],"mappings":";;;;;AAKA,MAAM,OAAO,UAAU,SAAS;AAEhC,SAAS,kBAAkB,SAA2B;AACpD,KAAI,QAAQ,IAAI,6BAA6B,IAAK,QAAO;AACzD,KAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAQ,QAAqC,eAAe;;AAG9D,MAAM,WAAW,QAAQ,IAAI,sBAAsB;AACnD,MAAM,SAAS,QAAQ,IAAI,yBAAyB;AACpD,MAAM,aAAa;AAEnB,SAAS,cAAsC;CAC7C,MAAM,IAA4B,EAAE,gBAAgB,oBAAoB;AACxE,KAAI,OAAQ,GAAE,mBAAmB,UAAU;AAC3C,QAAO;;AAGT,eAAe,IAAI,MAAgB,KAAqC;AACtE,KAAI;EACF,MAAM,EAAE,WAAW,MAAM,KAAK,OAAO,MAAM;GAAE;GAAK,SAAS;GAAM,CAAC;AAClE,SAAO,OAAO,MAAM;SACd;AACN,SAAO;;;AAIX,eAAe,OAAO;CACpB,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ,MAChC,UAAS;CAGX,IAAI,OAAgC,EAAE;AACtC,KAAI,MAAM,MAAM,CACd,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;SAClB;AAKV,KAAI,kBAAkB,KAAK,CAAE;CAE7B,MAAM,MACH,KAAK,OACN,QAAQ,IAAI,sBACZ,QAAQ,KAAK;CACf,MAAM,YACH,KAAK,cACN,QAAQ,IAAI,6BACZ;CAEF,MAAM,MACJ,QAAQ,IAAI,6BACX,MAAM,IAAI,CAAC,aAAa,OAAO,EAAE,IAAI;AACxC,KAAI,CAAC,IAAK;CAEV,MAAM,SAAS,MAAM,IAAI;EAAC;EAAa;EAAgB;EAAO,EAAE,IAAI;CACpE,MAAM,OAAO,MAAM,IAAI;EAAC;EAAU;EAAS;EAAoB,EAAE,IAAI;CACrE,MAAM,UAAU,MAAM,IAAI;EAAC;EAAO;EAAM;EAAe;EAAI,EAAE,IAAI;CACjE,MAAM,SAAS,MAAM,IAAI;EAAC;EAAO;EAAM;EAAsB;EAAI,EAAE,IAAI;CACvE,MAAM,aAAa,MAAM,IAAI;EAAC;EAAO;EAAM;EAAgB;EAAI,EAAE,IAAI;CACrE,MAAM,WAAW,MAAM,IACrB;EAAC;EAAa;EAAkB;EAAe;EAAM;EAAI,EACzD,IACD;CACD,MAAM,QAAQ,WAAW,SAAS,MAAM,KAAK,CAAC,OAAO,QAAQ,GAAG;CAEhE,MAAM,OAAO;EACX;EACA;EACA,QAAQ,UAAU;EAClB,MAAM,QAAQ;EACd,SAAS,WAAW;EACpB,QAAQ,UAAU;EAClB,YAAY,cAAc;EAC1B;EACD;AAED,KAAI;AACF,QAAM,MAAM,GAAG,SAAS,8BAA8B;GACpD,QAAQ;GACR,SAAS,aAAa;GACtB,MAAM,KAAK,UAAU,KAAK;GAC1B,QAAQ,YAAY,QAAQ,WAAW;GACxC,CAAC;SACI;;AAKV,MAAM"}
@@ -0,0 +1,19 @@
1
+ ---
2
+ name: commit-context
3
+ description: Trace a file, function, or line back to the agent session that produced its current commit. Use when the user asks "why is this code here", "what was the agent doing when this changed", or wants context on a specific location in the codebase.
4
+ argument-hint: "[file, function, or line]"
5
+ user-invocable: true
6
+ ---
7
+
8
+ The user wants commit context for: $ARGUMENTS
9
+
10
+ Run `git blame` (or `git log -L`) on the target file, function, or line in $ARGUMENTS to extract the most recent commit SHA that touched it. Use `git blame -L <start>,<end> <file>` when a line range is given, `git log -L :<function>:<file>` when a function name is given, and `git log -n 1 -- <file>` when only a path is given.
11
+
12
+ With the SHA in hand, look up the linked agent session via the `memory_commit_lookup` MCP tool with `sha: "<full-sha>"`. If the MCP tool is unavailable, fall back to HTTP: `GET $AGENTMEMORY_URL/agentmemory/session/by-commit?sha=<sha>` with `Authorization: Bearer $AGENTMEMORY_SECRET` when the secret is set.
13
+
14
+ Present the result as:
15
+ - The commit SHA, short SHA, branch, author, message
16
+ - The linked session(s): id, project, started/ended timestamps, observation count, summary if any
17
+ - A short list of the most important observations from that session (importance >= 7) when available via `memory_recall`
18
+
19
+ Do not fabricate intent. If the commit has no linked session, say so plainly and surface only what `git show` reveals. If `memory_commit_lookup` returns an empty `commit: null` body, that means the commit predates session linking — do not invent a session.
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: commit-history
3
+ description: List recent git commits that are linked to agent sessions, optionally filtered by branch or repo. Use when the user asks "show agent commits", "what has the agent shipped", or wants a list of commits with their session context.
4
+ argument-hint: "[branch=... repo=... limit=...]"
5
+ user-invocable: true
6
+ ---
7
+
8
+ The user wants a list of agent-linked commits. Filter args: $ARGUMENTS
9
+
10
+ Parse `$ARGUMENTS` for optional `branch=<name>`, `repo=<url-or-fragment>`, and `limit=<n>` tokens. A bare numeric token becomes the limit. Defaults: no branch filter, no repo filter, limit 100, max 500.
11
+
12
+ Call the `memory_commits` MCP tool with the parsed filters. If the MCP tool is unavailable, fall back to HTTP: build `GET $AGENTMEMORY_URL/agentmemory/commits` and append each filter as a URL-encoded query parameter (use `URLSearchParams` or `encodeURIComponent` on `branch`, `repo`, and `limit`) so values containing `?`, `&`, or `#` cannot corrupt the request. Include `Authorization: Bearer $AGENTMEMORY_SECRET` when set.
13
+
14
+ Render the result as a reverse-chronological list:
15
+ - Short SHA, branch, authored timestamp
16
+ - Commit message first line
17
+ - Linked session id(s) (first 8 chars each) and observation counts where present
18
+ - File count when `files` is provided
19
+
20
+ If the result is empty, tell the user the filter matched no commits and suggest dropping the branch/repo filter. Do not invent commits.
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: handoff
3
+ description: Resume the most recent agent session for the current working directory. Use when the user says "where were we", "resume", "handoff", "pick up where I left off", or starts a session with no fresh context.
4
+ argument-hint: "[optional cwd override]"
5
+ user-invocable: true
6
+ ---
7
+
8
+ The user wants to resume work. Optional cwd override: $ARGUMENTS
9
+
10
+ Determine the current project path: if `$ARGUMENTS` is provided, resolve it to an absolute, normalized path (accept relative inputs, e.g. `path.resolve(process.cwd(), $ARGUMENTS)`); otherwise use the current working directory.
11
+
12
+ Call the `memory_sessions` MCP tool. From the result, pick the most recent session whose normalized `cwd` matches the project path with a directory-boundary check — equality OR `session.cwd.startsWith(projectPath + path.sep)` OR `projectPath.startsWith(session.cwd + path.sep)`. Do NOT use a raw string prefix match: it produces false positives across unrelated repos that share a path prefix (e.g. `/repo-a` vs `/repo-a-staging`). Prefer sessions with status `completed` over `abandoned`. If nothing matches, fall back to the single most recent session overall.
13
+
14
+ Once a session is selected:
15
+ 1. If the session ended on an unanswered user-facing question, surface that question FIRST as the lead. Look for it in `summary` or in the last few observations (type `conversation` with `narrative` ending in `?`).
16
+ 2. Then summarize the session: title/summary, key files touched, key decisions or errors. Use `memory_recall` with a query derived from the session's top concepts to fetch supporting observations, limit 10.
17
+ 3. End with a short "next step?" pointer the user can act on.
18
+
19
+ If neither MCP tool is available, fall back to HTTP: `GET $AGENTMEMORY_URL/agentmemory/sessions` and `POST $AGENTMEMORY_URL/agentmemory/recall` with `Authorization: Bearer $AGENTMEMORY_SECRET` when set.
20
+
21
+ Do not invent observations. If the most recent session has zero observations, say so and offer to start fresh.
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: recap
3
+ description: Summarize the last N agent sessions for the current project, grouped by date. Use when the user asks "recap", "what have we been doing", "this week", "today", or wants a rollup of recent work.
4
+ argument-hint: "[last N | today | this week]"
5
+ user-invocable: true
6
+ ---
7
+
8
+ The user wants a recap. Time window args: $ARGUMENTS
9
+
10
+ Parse `$ARGUMENTS` to determine the window:
11
+ - `today` -> sessions started on the current local date
12
+ - `this week` -> sessions started in the last 7 days
13
+ - `last <n>` -> the most recent N sessions
14
+ - bare numeric -> treat as `last <n>`
15
+ - empty -> default to `last 10`
16
+
17
+ Call the `memory_sessions` MCP tool, then filter to the current project (match by `cwd` against the working directory). Apply the time window. Sort by `startedAt` descending.
18
+
19
+ Group the surviving sessions by their local calendar date (YYYY-MM-DD). For each date:
20
+ - List each session: id (first 8 chars), title or first prompt, observation count, status
21
+ - Indent two or three highlight observations per session (importance >= 7) drawn from `memory_recall` with a per-session query, limit 3
22
+
23
+ End with a one-line total: "N sessions across M days, K observations."
24
+
25
+ If MCP tools are unavailable, fall back to HTTP: `GET $AGENTMEMORY_URL/agentmemory/sessions` and `POST $AGENTMEMORY_URL/agentmemory/recall` with `Authorization: Bearer $AGENTMEMORY_SECRET` when set. Do not invent sessions; if the window is empty, say so.