grounder 0.1.0 → 0.3.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 (167) hide show
  1. package/README.md +115 -28
  2. package/dist/agents/claude.d.ts +15 -0
  3. package/dist/agents/claude.d.ts.map +1 -1
  4. package/dist/agents/claude.js +256 -11
  5. package/dist/agents/claude.js.map +1 -1
  6. package/dist/agents/cursor-hook-input.d.ts +14 -0
  7. package/dist/agents/cursor-hook-input.d.ts.map +1 -0
  8. package/dist/agents/cursor-hook-input.js +80 -0
  9. package/dist/agents/cursor-hook-input.js.map +1 -0
  10. package/dist/agents/cursor.d.ts +11 -0
  11. package/dist/agents/cursor.d.ts.map +1 -1
  12. package/dist/agents/cursor.js +206 -11
  13. package/dist/agents/cursor.js.map +1 -1
  14. package/dist/agents/hook-runtime.d.ts +152 -0
  15. package/dist/agents/hook-runtime.d.ts.map +1 -0
  16. package/dist/agents/hook-runtime.js +401 -0
  17. package/dist/agents/hook-runtime.js.map +1 -0
  18. package/dist/agents/index.d.ts +18 -0
  19. package/dist/agents/index.d.ts.map +1 -1
  20. package/dist/agents/index.js +27 -4
  21. package/dist/agents/index.js.map +1 -1
  22. package/dist/agents/install-command.d.ts +37 -0
  23. package/dist/agents/install-command.d.ts.map +1 -0
  24. package/dist/agents/install-command.js +65 -0
  25. package/dist/agents/install-command.js.map +1 -0
  26. package/dist/agents/types.d.ts +23 -1
  27. package/dist/agents/types.d.ts.map +1 -1
  28. package/dist/cli.js +62 -53
  29. package/dist/cli.js.map +1 -1
  30. package/dist/commands/apply-agent-installs.d.ts +34 -0
  31. package/dist/commands/apply-agent-installs.d.ts.map +1 -0
  32. package/dist/commands/apply-agent-installs.js +137 -0
  33. package/dist/commands/apply-agent-installs.js.map +1 -0
  34. package/dist/commands/doctor.d.ts.map +1 -1
  35. package/dist/commands/doctor.js +231 -14
  36. package/dist/commands/doctor.js.map +1 -1
  37. package/dist/commands/handoff/list.d.ts +9 -0
  38. package/dist/commands/handoff/list.d.ts.map +1 -1
  39. package/dist/commands/handoff/list.js +20 -4
  40. package/dist/commands/handoff/list.js.map +1 -1
  41. package/dist/commands/handoff/peek.d.ts +41 -0
  42. package/dist/commands/handoff/peek.d.ts.map +1 -0
  43. package/dist/commands/handoff/peek.js +161 -0
  44. package/dist/commands/handoff/peek.js.map +1 -0
  45. package/dist/commands/handoff.d.ts.map +1 -1
  46. package/dist/commands/handoff.js +5 -0
  47. package/dist/commands/handoff.js.map +1 -1
  48. package/dist/commands/migrate.d.ts +23 -0
  49. package/dist/commands/migrate.d.ts.map +1 -0
  50. package/dist/commands/migrate.js +104 -0
  51. package/dist/commands/migrate.js.map +1 -0
  52. package/dist/commands/note.d.ts.map +1 -1
  53. package/dist/commands/note.js +5 -0
  54. package/dist/commands/note.js.map +1 -1
  55. package/dist/commands/package-version-notice.d.ts +18 -0
  56. package/dist/commands/package-version-notice.d.ts.map +1 -0
  57. package/dist/commands/package-version-notice.js +39 -0
  58. package/dist/commands/package-version-notice.js.map +1 -0
  59. package/dist/commands/path/logs.d.ts +1 -1
  60. package/dist/commands/path/logs.d.ts.map +1 -1
  61. package/dist/commands/path/logs.js +6 -1
  62. package/dist/commands/path/logs.js.map +1 -1
  63. package/dist/commands/path/notes.d.ts +1 -1
  64. package/dist/commands/path/notes.d.ts.map +1 -1
  65. package/dist/commands/path/notes.js +6 -1
  66. package/dist/commands/path/notes.js.map +1 -1
  67. package/dist/commands/path/plans.d.ts +7 -0
  68. package/dist/commands/path/plans.d.ts.map +1 -0
  69. package/dist/commands/path/plans.js +22 -0
  70. package/dist/commands/path/plans.js.map +1 -0
  71. package/dist/commands/plan/list.d.ts +23 -0
  72. package/dist/commands/plan/list.d.ts.map +1 -0
  73. package/dist/commands/plan/list.js +69 -0
  74. package/dist/commands/plan/list.js.map +1 -0
  75. package/dist/commands/plan.d.ts +34 -0
  76. package/dist/commands/plan.d.ts.map +1 -0
  77. package/dist/commands/plan.js +121 -0
  78. package/dist/commands/plan.js.map +1 -0
  79. package/dist/commands/repo/init.d.ts.map +1 -1
  80. package/dist/commands/repo/init.js +12 -1
  81. package/dist/commands/repo/init.js.map +1 -1
  82. package/dist/commands/status.d.ts +1 -1
  83. package/dist/commands/status.d.ts.map +1 -1
  84. package/dist/commands/status.js +52 -9
  85. package/dist/commands/status.js.map +1 -1
  86. package/dist/commands/upgrade-banner.d.ts +9 -0
  87. package/dist/commands/upgrade-banner.d.ts.map +1 -0
  88. package/dist/commands/upgrade-banner.js +27 -0
  89. package/dist/commands/upgrade-banner.js.map +1 -0
  90. package/dist/commands/vault/init.d.ts +2 -0
  91. package/dist/commands/vault/init.d.ts.map +1 -1
  92. package/dist/commands/vault/init.js +28 -11
  93. package/dist/commands/vault/init.js.map +1 -1
  94. package/dist/connector/repo.d.ts +2 -0
  95. package/dist/connector/repo.d.ts.map +1 -1
  96. package/dist/connector/repo.js +12 -1
  97. package/dist/connector/repo.js.map +1 -1
  98. package/dist/connector/state.d.ts +95 -0
  99. package/dist/connector/state.d.ts.map +1 -0
  100. package/dist/connector/state.js +189 -0
  101. package/dist/connector/state.js.map +1 -0
  102. package/dist/connector/unsupported-schema.d.ts +9 -0
  103. package/dist/connector/unsupported-schema.d.ts.map +1 -0
  104. package/dist/connector/unsupported-schema.js +14 -0
  105. package/dist/connector/unsupported-schema.js.map +1 -0
  106. package/dist/connector/vault.d.ts +2 -0
  107. package/dist/connector/vault.d.ts.map +1 -1
  108. package/dist/connector/vault.js +6 -1
  109. package/dist/connector/vault.js.map +1 -1
  110. package/dist/help.d.ts +45 -0
  111. package/dist/help.d.ts.map +1 -0
  112. package/dist/help.js +355 -0
  113. package/dist/help.js.map +1 -0
  114. package/dist/util/frontmatter.d.ts +11 -0
  115. package/dist/util/frontmatter.d.ts.map +1 -0
  116. package/dist/util/frontmatter.js +69 -0
  117. package/dist/util/frontmatter.js.map +1 -0
  118. package/dist/util/fs.d.ts +6 -0
  119. package/dist/util/fs.d.ts.map +1 -1
  120. package/dist/util/fs.js +15 -1
  121. package/dist/util/fs.js.map +1 -1
  122. package/dist/util/hash.d.ts +3 -0
  123. package/dist/util/hash.d.ts.map +1 -0
  124. package/dist/util/hash.js +6 -0
  125. package/dist/util/hash.js.map +1 -0
  126. package/dist/util/merge-json.d.ts +15 -0
  127. package/dist/util/merge-json.d.ts.map +1 -0
  128. package/dist/util/merge-json.js +40 -0
  129. package/dist/util/merge-json.js.map +1 -0
  130. package/dist/util/path.d.ts +10 -0
  131. package/dist/util/path.d.ts.map +1 -1
  132. package/dist/util/path.js +31 -0
  133. package/dist/util/path.js.map +1 -1
  134. package/dist/util/plan-name.d.ts +10 -0
  135. package/dist/util/plan-name.d.ts.map +1 -0
  136. package/dist/util/plan-name.js +18 -0
  137. package/dist/util/plan-name.js.map +1 -0
  138. package/dist/util/semver.d.ts +10 -0
  139. package/dist/util/semver.d.ts.map +1 -0
  140. package/dist/util/semver.js +40 -0
  141. package/dist/util/semver.js.map +1 -0
  142. package/dist/vault/find-usable-handoff.d.ts +19 -0
  143. package/dist/vault/find-usable-handoff.d.ts.map +1 -0
  144. package/dist/vault/find-usable-handoff.js +28 -0
  145. package/dist/vault/find-usable-handoff.js.map +1 -0
  146. package/dist/vault/layout.d.ts +2 -0
  147. package/dist/vault/layout.d.ts.map +1 -1
  148. package/dist/vault/layout.js +4 -0
  149. package/dist/vault/layout.js.map +1 -1
  150. package/dist/vault/list-plans.d.ts +11 -0
  151. package/dist/vault/list-plans.d.ts.map +1 -0
  152. package/dist/vault/list-plans.js +40 -0
  153. package/dist/vault/list-plans.js.map +1 -0
  154. package/dist/vault/write-plan.d.ts +28 -0
  155. package/dist/vault/write-plan.d.ts.map +1 -0
  156. package/dist/vault/write-plan.js +61 -0
  157. package/dist/vault/write-plan.js.map +1 -0
  158. package/package.json +1 -1
  159. package/templates/agents/claude/commands/grounder-note.md +1 -1
  160. package/templates/agents/claude/commands/grounder-plan.md +65 -0
  161. package/templates/agents/claude/commands/grounder-task-handoff.md +3 -3
  162. package/templates/agents/claude/commands/grounder-task.md +4 -4
  163. package/templates/agents/cursor/commands/grounder-note.md +1 -1
  164. package/templates/agents/cursor/commands/grounder-plan.md +65 -0
  165. package/templates/agents/cursor/commands/grounder-task-handoff.md +3 -3
  166. package/templates/agents/cursor/commands/grounder-task.md +4 -4
  167. package/templates/vault/plan.md +16 -0
@@ -0,0 +1,80 @@
1
+ /** Max wait for piped Cursor hook JSON on stdin before giving up. */
2
+ const STDIN_TIMEOUT_MS = 200;
3
+ /**
4
+ * Read Cursor's sessionStart hook payload from stdin and return the first
5
+ * workspace root when present.
6
+ *
7
+ * Cursor pipes JSON like `{ "workspace_roots": ["/path/to/repo"] }` into
8
+ * user-level hooks, whose `cwd` is `~/.cursor` — not the open project.
9
+ *
10
+ * Never throws. Returns `undefined` for TTY stdin, empty/malformed input,
11
+ * missing `workspace_roots`, or when no data arrives within the timeout
12
+ * (so interactive/`grounder handoff peek` runs never hang). If data arrived
13
+ * but stdin has not ended yet, the buffered payload is still parsed.
14
+ */
15
+ export async function readCursorHookWorkspaceRoot(stdin = process.stdin) {
16
+ if ("isTTY" in stdin && stdin.isTTY) {
17
+ return undefined;
18
+ }
19
+ const raw = await readStdinWithTimeout(stdin, STDIN_TIMEOUT_MS);
20
+ if (raw === undefined || raw.trim() === "") {
21
+ return undefined;
22
+ }
23
+ try {
24
+ const parsed = JSON.parse(raw);
25
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
26
+ return undefined;
27
+ }
28
+ const roots = parsed.workspace_roots;
29
+ if (!Array.isArray(roots) || roots.length === 0) {
30
+ return undefined;
31
+ }
32
+ const first = roots[0];
33
+ if (typeof first !== "string" || first.trim() === "") {
34
+ return undefined;
35
+ }
36
+ return first;
37
+ }
38
+ catch {
39
+ return undefined;
40
+ }
41
+ }
42
+ function readStdinWithTimeout(stdin, timeoutMs) {
43
+ return new Promise((resolve) => {
44
+ const chunks = [];
45
+ let settled = false;
46
+ const settle = (value) => {
47
+ if (settled) {
48
+ return;
49
+ }
50
+ settled = true;
51
+ clearTimeout(timer);
52
+ stdin.off("data", onData);
53
+ stdin.off("end", onEnd);
54
+ stdin.off("error", onError);
55
+ resolve(value);
56
+ };
57
+ const onData = (chunk) => {
58
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
59
+ };
60
+ const buffered = () => {
61
+ if (chunks.length === 0) {
62
+ return undefined;
63
+ }
64
+ return Buffer.concat(chunks).toString("utf8");
65
+ };
66
+ const onEnd = () => settle(buffered());
67
+ const onError = () => settle(undefined);
68
+ // On timeout, still use whatever arrived — Cursor may flush the JSON
69
+ // payload before closing stdin; discarding it would lose workspace_roots.
70
+ const timer = setTimeout(() => settle(buffered()), timeoutMs);
71
+ timer.unref?.();
72
+ stdin.on("data", onData);
73
+ stdin.on("end", onEnd);
74
+ stdin.on("error", onError);
75
+ if ("readableEnded" in stdin && stdin.readableEnded) {
76
+ onEnd();
77
+ }
78
+ });
79
+ }
80
+ //# sourceMappingURL=cursor-hook-input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor-hook-input.js","sourceRoot":"","sources":["../../src/agents/cursor-hook-input.ts"],"names":[],"mappings":"AAEA,qEAAqE;AACrE,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,QAA+B,OAAO,CAAC,KAAK;IAE5C,IAAI,OAAO,IAAI,KAAK,IAAI,KAAK,CAAC,KAAK,EAAE,CAAC;QACpC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,GAAG,GAAG,MAAM,oBAAoB,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC;IAChE,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAC3C,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3E,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,KAAK,GAAI,MAAkC,CAAC,eAAe,CAAC;QAClE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAChD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACvB,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACrD,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB,CAC3B,KAA4B,EAC5B,SAAiB;IAEjB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,MAAM,MAAM,GAAG,CAAC,KAAyB,EAAE,EAAE;YAC3C,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO;YACT,CAAC;YACD,OAAO,GAAG,IAAI,CAAC;YACf,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;YAC1B,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YACxB,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC5B,OAAO,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC,CAAC;QAEF,MAAM,MAAM,GAAG,CAAC,KAAsB,EAAE,EAAE;YACxC,MAAM,CAAC,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACtE,CAAC,CAAC;QACF,MAAM,QAAQ,GAAG,GAAuB,EAAE;YACxC,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxB,OAAO,SAAS,CAAC;YACnB,CAAC;YACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAChD,CAAC,CAAC;QAEF,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QACvC,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAExC,qEAAqE;QACrE,0EAA0E;QAC1E,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC;QAC9D,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;QAEhB,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACzB,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QACvB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAE3B,IAAI,eAAe,IAAI,KAAK,IAAK,KAAkB,CAAC,aAAa,EAAE,CAAC;YAClE,KAAK,EAAE,CAAC;QACV,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -1,8 +1,19 @@
1
1
  import type { AgentAdapter } from "./types.js";
2
+ /**
3
+ * Canonical sessionStart command for Cursor (home-local runtime, not `npx`).
4
+ * Always includes `--json` so stdout matches Cursor's `additional_context` contract.
5
+ * @see {@link peekHookCommand}
6
+ */
7
+ export declare function cursorPeekHookCommand(homeDir?: string, extraArgs?: readonly string[]): string;
2
8
  export declare function cursorCommandsDir(homeDir?: string): string;
9
+ /** Absolute path to Cursor's shared hooks config (`~/.cursor/hooks.json`). */
10
+ export declare function cursorHooksJsonPath(homeDir?: string): string;
3
11
  export declare function grounderNoteCommandPath(homeDir?: string): string;
12
+ export declare function grounderPlanCommandPath(homeDir?: string): string;
4
13
  export declare function grounderTaskHandoffCommandPath(homeDir?: string): string;
5
14
  export declare function grounderTaskCommandPath(homeDir?: string): string;
6
15
  export declare function expectedArtifacts(homeDir?: string): string[];
16
+ /** Paths of hook config this adapter touches — currently just `hooks.json`. */
17
+ export declare function expectedHookArtifacts(homeDir?: string): string[];
7
18
  export declare const cursor: AgentAdapter;
8
19
  //# sourceMappingURL=cursor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/agents/cursor.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,YAAY,EAIb,MAAM,YAAY,CAAC;AAOpB,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAgB,8BAA8B,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAE5D;AAkBD,eAAO,MAAM,MAAM,EAAE,YAkBpB,CAAC"}
1
+ {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/agents/cursor.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EACV,YAAY,EAIb,MAAM,YAAY,CAAC;AAYpB;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,CAAC,EAAE,MAAM,EAChB,SAAS,GAAE,SAAS,MAAM,EAAe,GACxC,MAAM,CAER;AAED,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAgB,8BAA8B,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvE;AAED,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAE5D;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAEhE;AAkLD,eAAO,MAAM,MAAM,EAAE,YAyCpB,CAAC"}
@@ -1,17 +1,40 @@
1
- import { copyFile, mkdir } from "node:fs/promises";
1
+ import { readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { resolveHomeDir } from "../connector/home.js";
5
5
  import { fileExists } from "../util/fs.js";
6
+ import { mergeJsonFile } from "../util/merge-json.js";
7
+ import { installHookRuntime, isGrounderPeekHookCommand, isHookRuntimeStale, peekHookCommand, } from "./hook-runtime.js";
8
+ import { installCommandFile, recordCommandFileHashes } from "./install-command.js";
6
9
  const pkgRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
7
10
  const templateDir = path.join(pkgRoot, "templates", "agents", "cursor", "commands");
8
- const COMMANDS = ["grounder-note.md", "grounder-task-handoff.md", "grounder-task.md"];
11
+ const COMMANDS = [
12
+ "grounder-note.md",
13
+ "grounder-plan.md",
14
+ "grounder-task-handoff.md",
15
+ "grounder-task.md",
16
+ ];
17
+ /**
18
+ * Canonical sessionStart command for Cursor (home-local runtime, not `npx`).
19
+ * Always includes `--json` so stdout matches Cursor's `additional_context` contract.
20
+ * @see {@link peekHookCommand}
21
+ */
22
+ export function cursorPeekHookCommand(homeDir, extraArgs = ["--json"]) {
23
+ return peekHookCommand(homeDir, extraArgs);
24
+ }
9
25
  export function cursorCommandsDir(homeDir) {
10
26
  return path.join(resolveHomeDir(homeDir), ".cursor", "commands");
11
27
  }
28
+ /** Absolute path to Cursor's shared hooks config (`~/.cursor/hooks.json`). */
29
+ export function cursorHooksJsonPath(homeDir) {
30
+ return path.join(resolveHomeDir(homeDir), ".cursor", "hooks.json");
31
+ }
12
32
  export function grounderNoteCommandPath(homeDir) {
13
33
  return path.join(cursorCommandsDir(homeDir), "grounder-note.md");
14
34
  }
35
+ export function grounderPlanCommandPath(homeDir) {
36
+ return path.join(cursorCommandsDir(homeDir), "grounder-plan.md");
37
+ }
15
38
  export function grounderTaskHandoffCommandPath(homeDir) {
16
39
  return path.join(cursorCommandsDir(homeDir), "grounder-task-handoff.md");
17
40
  }
@@ -21,30 +44,202 @@ export function grounderTaskCommandPath(homeDir) {
21
44
  export function expectedArtifacts(homeDir) {
22
45
  return COMMANDS.map((filename) => path.join(cursorCommandsDir(homeDir), filename));
23
46
  }
24
- async function installCommand(filename, opts) {
25
- const dest = path.join(cursorCommandsDir(opts.homeDir), filename);
26
- const existed = await fileExists(dest);
27
- if (existed && !opts.force) {
28
- return { dest, status: "skipped" };
47
+ /** Paths of hook config this adapter touches — currently just `hooks.json`. */
48
+ export function expectedHookArtifacts(homeDir) {
49
+ return [cursorHooksJsonPath(homeDir)];
50
+ }
51
+ // ---------------------------------------------------------------------------
52
+ // Session-start hook install (~/.cursor/hooks.json)
53
+ //
54
+ // Cursor hooks.json is a shared JSON object. Grounder only owns one entry in
55
+ // `hooks.sessionStart`; unrelated keys (other hook events, version, etc.) must
56
+ // survive merge. Relevant shape after install (path varies by home / Node):
57
+ //
58
+ // {
59
+ // "version": 1,
60
+ // "hooks": {
61
+ // "sessionStart": [
62
+ // { "command": "'/path/to/node' '/path/to/.grounder/runtime/dist/cli.js' handoff peek --json" }
63
+ // ]
64
+ // }
65
+ // }
66
+ //
67
+ // Terminology used below:
68
+ // - hooks root → the whole hooks.json object
69
+ // - hooks → hooksRoot.hooks (map of event name → hook entries)
70
+ // - sessionStart → hooks.sessionStart (flat array of { command } objects)
71
+ // - hook entry → { command: string }
72
+ //
73
+ // Unlike Claude Code, Cursor has no matcher groups — sessionStart is a flat
74
+ // array. Idempotency: {@link isGrounderPeekHookCommand} (runtime path or legacy npx).
75
+ // ---------------------------------------------------------------------------
76
+ function findPeekHookIndex(sessionStart) {
77
+ return sessionStart.findIndex((item) => item &&
78
+ typeof item === "object" &&
79
+ !Array.isArray(item) &&
80
+ isGrounderPeekHookCommand(item.command));
81
+ }
82
+ /** Whether `hooks.json` already lists any Grounder peek command (for status labeling). */
83
+ async function peekHookHadGrounderEntry(filePath) {
84
+ try {
85
+ const parsed = JSON.parse(await readFile(filePath, "utf8"));
86
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
87
+ return false;
88
+ }
89
+ const hooks = parsed.hooks;
90
+ if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) {
91
+ return false;
92
+ }
93
+ const sessionStart = hooks.sessionStart;
94
+ return Array.isArray(sessionStart) && findPeekHookIndex(sessionStart) >= 0;
95
+ }
96
+ catch {
97
+ return false;
98
+ }
99
+ }
100
+ /**
101
+ * Skip only when the canonical command is already present *and* the runtime is
102
+ * current for the running grounder version/source. Legacy `npx` entries or a
103
+ * stale runtime (missing, or symlinked/copied from a different source) always
104
+ * refresh — no `--force` required to migrate or to pick up an upgrade.
105
+ */
106
+ async function peekHookUpToDate(filePath, homeDir) {
107
+ if (!(await fileExists(filePath))) {
108
+ return false;
109
+ }
110
+ if (await isHookRuntimeStale(homeDir)) {
111
+ return false;
112
+ }
113
+ try {
114
+ const parsed = JSON.parse(await readFile(filePath, "utf8"));
115
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
116
+ return false;
117
+ }
118
+ const hooks = parsed.hooks;
119
+ if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) {
120
+ return false;
121
+ }
122
+ const sessionStart = hooks.sessionStart;
123
+ if (!Array.isArray(sessionStart)) {
124
+ return false;
125
+ }
126
+ const idx = findPeekHookIndex(sessionStart);
127
+ if (idx < 0) {
128
+ return false;
129
+ }
130
+ const command = sessionStart[idx].command;
131
+ return command === cursorPeekHookCommand(homeDir);
132
+ }
133
+ catch {
134
+ return false;
135
+ }
136
+ }
137
+ /**
138
+ * Deep-merge Grounder's sessionStart hook into an existing hooks.json object.
139
+ *
140
+ * Preserves every key except the nested path it owns. Strategy:
141
+ * 1. If a Grounder peek hook already exists (runtime or legacy npx) → replace in place.
142
+ * 2. Else → append Grounder's entry to `hooks.sessionStart`.
143
+ *
144
+ * On a brand-new file (`isFreshFile`), also sets `version: 1` when absent.
145
+ * Existing files keep whatever `version` (or lack of it) they already have.
146
+ *
147
+ * @param current - Parsed hooks.json root (object). Other top-level keys untouched.
148
+ * @param isFreshFile - True when the file did not exist before this write
149
+ * @param homeDir - Home override for the canonical command path
150
+ * @returns New hooks object with `hooks.sessionStart` updated
151
+ */
152
+ function mergeCursorHooks(current, isFreshFile, homeDir) {
153
+ const next = { ...current };
154
+ if (isFreshFile && next.version === undefined) {
155
+ next.version = 1;
156
+ }
157
+ const hooks = next.hooks && typeof next.hooks === "object" && !Array.isArray(next.hooks)
158
+ ? { ...next.hooks }
159
+ : {};
160
+ const existing = Array.isArray(hooks.sessionStart) ? [...hooks.sessionStart] : [];
161
+ const entry = { command: cursorPeekHookCommand(homeDir) };
162
+ const idx = findPeekHookIndex(existing);
163
+ if (idx >= 0) {
164
+ // Path 1: refresh existing Grounder hook entry in place
165
+ existing[idx] = entry;
166
+ }
167
+ else {
168
+ // Path 2: append — first install, or file had other sessionStart hooks only
169
+ existing.push(entry);
170
+ }
171
+ next.hooks = { ...hooks, sessionStart: existing };
172
+ return next;
173
+ }
174
+ /**
175
+ * Install (or refresh) Grounder's sessionStart teaser hook into `~/.cursor/hooks.json`.
176
+ *
177
+ * Also materializes `~/.grounder/runtime` (see {@link installHookRuntime}).
178
+ *
179
+ * Force semantics:
180
+ * - Up-to-date canonical entry + fresh runtime and `force` false → skip
181
+ * - Otherwise → refresh runtime + merge host config
182
+ *
183
+ * Unparseable hooks.json: {@link mergeJsonFile} backs off and this throws (never clobbers).
184
+ */
185
+ async function installHooks(opts) {
186
+ const dest = cursorHooksJsonPath(opts.homeDir);
187
+ const upToDate = await peekHookUpToDate(dest, opts.homeDir);
188
+ if (upToDate && !opts.force) {
189
+ return { artifacts: { [dest]: "skipped" } };
190
+ }
191
+ if (opts.dryRun) {
192
+ const fileExisted = await fileExists(dest);
193
+ const hadGrounderEntry = fileExisted && (await peekHookHadGrounderEntry(dest));
194
+ const status = hadGrounderEntry ? "overwritten" : "created";
195
+ return { artifacts: { [dest]: status } };
196
+ }
197
+ await installHookRuntime({ homeDir: opts.homeDir });
198
+ const fileExisted = await fileExists(dest);
199
+ const hadGrounderEntry = fileExisted && (await peekHookHadGrounderEntry(dest));
200
+ const result = await mergeJsonFile(dest, (current) => mergeCursorHooks(current, !fileExisted, opts.homeDir));
201
+ if (!result.ok) {
202
+ throw new Error(result.message);
29
203
  }
30
- await mkdir(path.dirname(dest), { recursive: true });
31
- await copyFile(path.join(templateDir, filename), dest);
32
- return { dest, status: existed ? "overwritten" : "created" };
204
+ const status = hadGrounderEntry ? "overwritten" : "created";
205
+ return { artifacts: { [dest]: status } };
33
206
  }
34
207
  export const cursor = {
35
208
  id: "cursor",
36
209
  name: "Cursor",
210
+ commandsSchema: 1,
211
+ hooksSchema: 1,
37
212
  async isInstalled() {
38
213
  return fileExists(path.join(resolveHomeDir(), ".cursor"));
39
214
  },
40
215
  expectedArtifacts,
216
+ expectedHookArtifacts,
41
217
  async install(opts) {
42
218
  const artifacts = {};
219
+ const files = {};
43
220
  for (const filename of COMMANDS) {
44
- const { dest, status } = await installCommand(filename, opts);
221
+ const { dest, status, hash } = await installCommandFile({
222
+ ...opts,
223
+ agentId: cursor.id,
224
+ commandsSchema: cursor.commandsSchema,
225
+ templateDir,
226
+ commandsDir: cursorCommandsDir(opts.homeDir),
227
+ filename,
228
+ });
45
229
  artifacts[dest] = status;
230
+ if (hash) {
231
+ files[dest] = { schema: cursor.commandsSchema, hash };
232
+ }
46
233
  }
234
+ await recordCommandFileHashes({
235
+ agentId: cursor.id,
236
+ commandsSchema: cursor.commandsSchema,
237
+ files,
238
+ homeDir: opts.homeDir,
239
+ dryRun: opts.dryRun,
240
+ });
47
241
  return { artifacts };
48
242
  },
243
+ installHooks,
49
244
  };
50
245
  //# sourceMappingURL=cursor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/agents/cursor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/agents/cursor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Home-local runtime shared by session hooks *and* slash commands (originally
3
+ * "Issue 2 — replace `npx grounder` in session hooks"; later extended to cover
4
+ * slash-command templates too, since they had the identical problem).
5
+ *
6
+ * ## Why
7
+ * Both surfaces used to shell out via `npx grounder …`. `npx <pkg>` (no
8
+ * version specifier) matches against whatever `grounder` version exists in
9
+ * the *current project's own dependencies*; only when the project doesn't
10
+ * declare `grounder` as a dependency does it fall back to fetching
11
+ * `grounder@latest` from the registry. Session hooks and slash commands both
12
+ * run from arbitrary linked projects, which normally have no reason to depend
13
+ * on `grounder` themselves, so contributors (and anyone ahead of the last
14
+ * publish, or deliberately pinned to an older version) get the wrong binary.
15
+ * Global `pnpm link` / `pnpm add -g` does not change this fallback — see
16
+ * [npm/cli#9244](https://github.com/npm/cli/issues/9244).
17
+ *
18
+ * ## Design
19
+ * On `vault init`, materialize this package's `dist/` at
20
+ * `~/.grounder/runtime/dist/` and point both host hook configs *and* the
21
+ * commands copied into `~/.cursor/commands/` / `~/.claude/commands/` at:
22
+ * `process.execPath` + `~/.grounder/runtime/dist/cli.js` + `<subcommand> …`
23
+ * No global install and no registry fetch at invocation time, for either
24
+ * surface. This mirrors how Corepack (packageManager shims) and Husky/lint-staged
25
+ * (moved off bare `npx` for the same reason, see the strapi/strapi#27006 fix)
26
+ * solve the identical "ambient resolution isn't stable" problem.
27
+ *
28
+ * How it's materialized depends on where grounder is running from:
29
+ * - **Durable source** (monorepo checkout, global install, linked devDependency) →
30
+ * **symlink** `dist/` straight to the source. `pnpm build` / upgrading the
31
+ * global install overwrites that same path in place, so hooks and commands
32
+ * pick up new code immediately — no re-run of `vault init` ever needed.
33
+ * - **Ephemeral source** (bare `npx grounder …`, no install — each invocation
34
+ * resolves to an immutable, version-keyed npx cache dir that can be evicted
35
+ * or swapped out from under a symlink) → **copy** `dist/`. Re-run
36
+ * `grounder vault init <vault>` after upgrading to refresh; this is an
37
+ * inherent limitation of using npx with no install for something that must
38
+ * persist, not something we can engineer around.
39
+ *
40
+ * This mirrors husky/pre-commit hook shims: staying current is an explicit,
41
+ * idempotent step (never silent self-healing inside a hook itself — session
42
+ * hooks must stay fast and side-effect-free), and the step is a no-op unless
43
+ * something has actually changed.
44
+ *
45
+ * ## REVERT
46
+ * To restore the previous `npx grounder …` behavior for hooks and/or commands:
47
+ * 1. Delete this file and its tests (`test/agents/hook-runtime.test.ts`).
48
+ * 2. In `cursor.ts` / `claude.ts`, set `*_PEEK_HOOK_COMMAND` back to
49
+ * `"npx grounder handoff peek"` and `installCommand` back to a plain
50
+ * `copyFile` (drop the `{{GROUNDER_CLI}}` template substitution), dropping
51
+ * calls to {@link installHookRuntime} / {@link peekHookCommand} /
52
+ * {@link runtimeInvocation} / {@link extractRuntimeNodePath} /
53
+ * {@link findRuntimeNodePathsInText} /
54
+ * {@link collectGrounderPeekHookCommands} / {@link hookFileGrounderPeekCommands} /
55
+ * {@link isGrounderPeekHookCommand} / {@link isHookRuntimeStale}.
56
+ * 3. Remove runtime paths from `expectedHookArtifacts` and docs that mention
57
+ * `~/.grounder/runtime`, and revert templates' `{{GROUNDER_CLI}}` back to
58
+ * literal `npx grounder`.
59
+ */
60
+ import type { ArtifactStatus } from "./types.js";
61
+ /** Marker written next to the materialized runtime for debugging / staleness checks. */
62
+ export interface HookRuntimeManifest {
63
+ mode: "symlink" | "copy";
64
+ version: string;
65
+ sourcePackageRoot: string;
66
+ installedAt: string;
67
+ }
68
+ /** `~/.grounder/runtime` — private materialization of the CLI used only by session hooks. */
69
+ export declare function grounderRuntimeDir(homeDir?: string): string;
70
+ /** Absolute path to the materialized CLI entry (`…/runtime/dist/cli.js`). */
71
+ export declare function runtimeCliPath(homeDir?: string): string;
72
+ export declare function runtimeManifestPath(homeDir?: string): string;
73
+ /**
74
+ * POSIX-safe single-quote for embedding paths in hook `command` strings
75
+ * (Cursor/Claude run hooks via a shell on macOS/Linux and Git Bash on Windows).
76
+ */
77
+ export declare function shellQuote(value: string): string;
78
+ /**
79
+ * Extract the baked Node interpreter path from a home-runtime invocation
80
+ * string (`'<abs node>' '<abs …/runtime/dist/cli.js>' …`).
81
+ *
82
+ * Returns `null` when the string is not that exact shape — including legacy
83
+ * `npx grounder …` entries, which have no absolute interpreter to validate.
84
+ */
85
+ export declare function extractRuntimeNodePath(command: unknown): string | null;
86
+ /**
87
+ * Find every baked Node interpreter path embedded in free-form text (slash-command
88
+ * markdown, etc.). Scans for the same `'<abs node>' '<abs …/cli.js>'` shape as
89
+ * {@link extractRuntimeNodePath}, including mid-line / backtick-wrapped uses.
90
+ */
91
+ export declare function findRuntimeNodePathsInText(text: string): string[];
92
+ /**
93
+ * Quoted `<node> <runtime cli.js>` prefix, shared by every home-runtime
94
+ * invocation (session hooks and slash-command templates alike). Append
95
+ * subcommand args to build a full command string.
96
+ */
97
+ export declare function runtimeInvocation(homeDir?: string): string;
98
+ /**
99
+ * Build the sessionStart / SessionStart hook command for the home runtime.
100
+ *
101
+ * @param homeDir - Override home (tests / `GROUNDER_HOME`)
102
+ * @param extraArgs - Extra CLI args after `handoff peek` (e.g. `--json` for Cursor)
103
+ */
104
+ export declare function peekHookCommand(homeDir?: string, extraArgs?: readonly string[]): string;
105
+ /**
106
+ * True when `command` is Grounder's peek hook — current home-runtime form or
107
+ * legacy `npx grounder handoff peek` (with optional trailing flags).
108
+ * Used so upgrades replace the old entry instead of appending a duplicate.
109
+ */
110
+ export declare function isGrounderPeekHookCommand(command: unknown): boolean;
111
+ /** Collect Grounder peek hook `command` strings nested anywhere in parsed JSON. */
112
+ export declare function collectGrounderPeekHookCommands(value: unknown): string[];
113
+ /** True when any nested `command` field in parsed JSON is Grounder's peek hook. */
114
+ export declare function jsonContainsGrounderPeekCommand(value: unknown): boolean;
115
+ /** Grounder peek hook command strings in a hooks/settings JSON file (empty if absent/unreadable). */
116
+ export declare function hookFileGrounderPeekCommands(filePath: string): Promise<string[]>;
117
+ /** True when a hooks/settings JSON file contains Grounder's peek hook entry. */
118
+ export declare function hookFileHasGrounderEntry(filePath: string): Promise<boolean>;
119
+ /**
120
+ * True when the materialized runtime is missing or out of date for the given
121
+ * source package:
122
+ * - **Symlink mode** (durable source): stale iff `dist/` isn't currently a
123
+ * symlink resolving to this source's `dist/` (cheap `realpath` compare — no
124
+ * staleness window, since a matching symlink is *always* current).
125
+ * - **Copy mode** (ephemeral `npx` source): stale iff the manifest is
126
+ * missing/unreadable or its recorded version differs from this source's.
127
+ *
128
+ * @param packageRoot - Source package root to compare against (defaults to the
129
+ * currently running package — override only in tests)
130
+ */
131
+ export declare function isHookRuntimeStale(homeDir?: string, packageRoot?: string): Promise<boolean>;
132
+ /**
133
+ * Materialize this package's `dist/` at `~/.grounder/runtime/dist/` — symlinked
134
+ * when the source is durable, copied when it's an ephemeral `npx` cache — and
135
+ * write a manifest recording how.
136
+ *
137
+ * Callers should gate on {@link isHookRuntimeStale} (or `force`) before calling —
138
+ * this always replaces whatever is currently at the destination, staging the
139
+ * new materialization first so a failed symlink/cp leaves the live runtime intact.
140
+ *
141
+ * @param options.packageRoot - Source package root to materialize (defaults to
142
+ * the currently running package — override only in tests)
143
+ */
144
+ export declare function installHookRuntime(options: {
145
+ homeDir?: string;
146
+ packageRoot?: string;
147
+ }): Promise<{
148
+ cliPath: string;
149
+ status: ArtifactStatus;
150
+ mode: "symlink" | "copy";
151
+ }>;
152
+ //# sourceMappingURL=hook-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-runtime.d.ts","sourceRoot":"","sources":["../../src/agents/hook-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAkBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAKjD,wFAAwF;AACxF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,6FAA6F;AAC7F,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AAgCD;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAyBtE;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAcjE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,SAAS,GAAE,SAAS,MAAM,EAAO,GAAG,MAAM,CAE3F;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAanE;AAED,mFAAmF;AACnF,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAqBxE;AAED,mFAAmF;AACnF,wBAAgB,+BAA+B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEvE;AAED,qGAAqG;AACrG,wBAAsB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAUtF;AAED,gFAAgF;AAChF,wBAAsB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEjF;AA2DD;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,GAAE,MAA2B,GACvC,OAAO,CAAC,OAAO,CAAC,CAkBlB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAAA;CAAE,CAAC,CAkEjF"}