@iamem/amem 0.1.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 (104) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +534 -0
  3. package/dist/activity.d.ts +27 -0
  4. package/dist/activity.js +202 -0
  5. package/dist/api/routes.d.ts +31 -0
  6. package/dist/api/routes.js +1345 -0
  7. package/dist/attest.d.ts +52 -0
  8. package/dist/attest.js +192 -0
  9. package/dist/backup-schedule.d.ts +25 -0
  10. package/dist/backup-schedule.js +216 -0
  11. package/dist/capture.d.ts +34 -0
  12. package/dist/capture.js +257 -0
  13. package/dist/cli.d.ts +2 -0
  14. package/dist/cli.js +1007 -0
  15. package/dist/context.d.ts +46 -0
  16. package/dist/context.js +334 -0
  17. package/dist/crypto.d.ts +39 -0
  18. package/dist/crypto.js +166 -0
  19. package/dist/db.d.ts +192 -0
  20. package/dist/db.js +666 -0
  21. package/dist/draft-quality.d.ts +19 -0
  22. package/dist/draft-quality.js +85 -0
  23. package/dist/embed.d.ts +76 -0
  24. package/dist/embed.js +331 -0
  25. package/dist/estimate.d.ts +32 -0
  26. package/dist/estimate.js +69 -0
  27. package/dist/freshness.d.ts +15 -0
  28. package/dist/freshness.js +93 -0
  29. package/dist/hook.d.ts +16 -0
  30. package/dist/hook.js +177 -0
  31. package/dist/hygiene-schedule.d.ts +28 -0
  32. package/dist/hygiene-schedule.js +221 -0
  33. package/dist/hygiene.d.ts +61 -0
  34. package/dist/hygiene.js +196 -0
  35. package/dist/install/claude.d.ts +6 -0
  36. package/dist/install/claude.js +69 -0
  37. package/dist/install/cursor.d.ts +7 -0
  38. package/dist/install/cursor.js +80 -0
  39. package/dist/install/hosts.d.ts +21 -0
  40. package/dist/install/hosts.js +186 -0
  41. package/dist/install/skills.d.ts +8 -0
  42. package/dist/install/skills.js +67 -0
  43. package/dist/it-pack.d.ts +20 -0
  44. package/dist/it-pack.js +84 -0
  45. package/dist/kinds.d.ts +18 -0
  46. package/dist/kinds.js +106 -0
  47. package/dist/license.d.ts +48 -0
  48. package/dist/license.js +172 -0
  49. package/dist/mcp.d.ts +40 -0
  50. package/dist/mcp.js +435 -0
  51. package/dist/paths.d.ts +11 -0
  52. package/dist/paths.js +55 -0
  53. package/dist/personal.d.ts +7 -0
  54. package/dist/personal.js +44 -0
  55. package/dist/platforms.d.ts +11 -0
  56. package/dist/platforms.js +32 -0
  57. package/dist/policy.d.ts +46 -0
  58. package/dist/policy.js +254 -0
  59. package/dist/prefs.d.ts +6 -0
  60. package/dist/prefs.js +11 -0
  61. package/dist/proposal.d.ts +90 -0
  62. package/dist/proposal.js +376 -0
  63. package/dist/publish.d.ts +28 -0
  64. package/dist/publish.js +57 -0
  65. package/dist/remember-contract.d.ts +23 -0
  66. package/dist/remember-contract.js +117 -0
  67. package/dist/repo-identity.d.ts +15 -0
  68. package/dist/repo-identity.js +82 -0
  69. package/dist/rules-sync.d.ts +7 -0
  70. package/dist/rules-sync.js +47 -0
  71. package/dist/savings-export.d.ts +49 -0
  72. package/dist/savings-export.js +141 -0
  73. package/dist/scan.d.ts +16 -0
  74. package/dist/scan.js +109 -0
  75. package/dist/search.d.ts +25 -0
  76. package/dist/search.js +150 -0
  77. package/dist/service.d.ts +20 -0
  78. package/dist/service.js +254 -0
  79. package/dist/shop.d.ts +9 -0
  80. package/dist/shop.js +15 -0
  81. package/dist/ui/server.d.ts +21 -0
  82. package/dist/ui/server.js +268 -0
  83. package/dist/vault.d.ts +25 -0
  84. package/dist/vault.js +42 -0
  85. package/dist/workspace-setup.d.ts +8 -0
  86. package/dist/workspace-setup.js +55 -0
  87. package/docs/agent-install-prompt.md +41 -0
  88. package/docs/backlog.md +59 -0
  89. package/docs/enterprise-endpoint.md +98 -0
  90. package/docs/license.md +54 -0
  91. package/docs/npm-release.md +38 -0
  92. package/docs/remember-contract.md +42 -0
  93. package/package.json +64 -0
  94. package/scripts/mdm-offboard.sh +14 -0
  95. package/skills/amem-bootstrap/SKILL.md +83 -0
  96. package/skills/amem-update-working-memory/SKILL.md +54 -0
  97. package/templates/cursor-rule.mdc +23 -0
  98. package/templates/mdm/co.amem.managed.plist +33 -0
  99. package/templates/policy.deny-default.toml +20 -0
  100. package/templates/policy.example.toml +22 -0
  101. package/ui-static/app.js +3966 -0
  102. package/ui-static/index.html +178 -0
  103. package/ui-static/orbit.js +389 -0
  104. package/ui-static/styles.css +2411 -0
package/dist/mcp.js ADDED
@@ -0,0 +1,435 @@
1
+ import { existsSync } from "node:fs";
2
+ import { handleApi } from "./api/routes.js";
3
+ import { getRepoByName } from "./db.js";
4
+ const PROTOCOL_VERSIONS = new Set(["2024-11-05", "2025-03-26", "2025-06-18"]);
5
+ const TOOLS = [
6
+ {
7
+ name: "amem_context",
8
+ description: "Retrieve compact local amem memory for a query. Use before exploring files or calling a remote LLM with a large prompt. Pass workspace for named app memory, not only git repos.",
9
+ inputSchema: {
10
+ type: "object",
11
+ properties: {
12
+ query: { type: "string" },
13
+ workspace: { type: "string", description: "Named workspace (not a git repo)" },
14
+ platform: { type: "string" },
15
+ sessionId: { type: "string" },
16
+ },
17
+ required: ["query"],
18
+ },
19
+ },
20
+ {
21
+ name: "amem_recipe",
22
+ description: "Return the generic amem remember contract: when to call amem_context vs amem_remember. Use if you are unsure whether to read or write memory. Same recipe for every MCP host.",
23
+ inputSchema: { type: "object", properties: {} },
24
+ },
25
+ {
26
+ name: "amem_remember",
27
+ description: "Store a durable local fact in amem (stays on this machine).",
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {
31
+ text: { type: "string" },
32
+ workspace: { type: "string" },
33
+ kind: { type: "string" },
34
+ anchors: { type: "array", items: { type: "string" } },
35
+ },
36
+ required: ["text"],
37
+ },
38
+ },
39
+ {
40
+ name: "amem_repos",
41
+ description: "List what amem is monitoring. Each item has kind git or workspace. Workspaces also have a stable slug (MCP id); the display name can differ. Use when asked which projects, workspaces, or repos amem knows about.",
42
+ inputSchema: {
43
+ type: "object",
44
+ properties: {
45
+ kind: {
46
+ type: "string",
47
+ description: "all (default), git, or workspace",
48
+ },
49
+ },
50
+ },
51
+ },
52
+ {
53
+ name: "amem_stats",
54
+ description: "Usage and savings stats (local lookup time, estimated tokens/ms saved, hit rate) for one workspace/repo or everything. Use when asked how much amem helped, how often it hit, or usage over time.",
55
+ inputSchema: {
56
+ type: "object",
57
+ properties: {
58
+ workspace: { type: "string" },
59
+ days: { type: "number", description: "Lookback window (default 30)" },
60
+ scope: {
61
+ type: "string",
62
+ description: "all (every tracked repo/workspace) or current (the given workspace / cwd)",
63
+ },
64
+ },
65
+ },
66
+ },
67
+ {
68
+ name: "amem_graph",
69
+ description: "Dump stored facts for a workspace or git repo: claims, components, flows, and recent activity. Use when asked what amem remembers about a project. Prefer amem_context for a ranked packet for the current question.",
70
+ inputSchema: {
71
+ type: "object",
72
+ properties: {
73
+ workspace: { type: "string" },
74
+ days: { type: "number", description: "Activity lookback (default 30)" },
75
+ },
76
+ },
77
+ },
78
+ {
79
+ name: "amem_status",
80
+ description: "Binding, policy, and counts for one workspace/repo, plus the full monitored inventory. Omit workspace for a machine-wide overview.",
81
+ inputSchema: {
82
+ type: "object",
83
+ properties: { workspace: { type: "string" } },
84
+ },
85
+ },
86
+ ];
87
+ function textResult(text, isError = false) {
88
+ return {
89
+ content: [{ type: "text", text }],
90
+ isError,
91
+ };
92
+ }
93
+ function defaultWorkspace(explicit, fallback) {
94
+ if (typeof explicit === "string" && explicit.trim())
95
+ return explicit.trim();
96
+ if (fallback?.trim())
97
+ return fallback.trim();
98
+ return process.env.AMEM_WORKSPACE || undefined;
99
+ }
100
+ function api(method, pathname, body, opts) {
101
+ const searchParams = new URLSearchParams();
102
+ if (opts?.workspace)
103
+ searchParams.set("workspace", opts.workspace);
104
+ for (const [key, value] of Object.entries(opts?.query ?? {})) {
105
+ if (value)
106
+ searchParams.set(key, value);
107
+ }
108
+ return handleApi({
109
+ method,
110
+ pathname,
111
+ searchParams,
112
+ body,
113
+ cwd: process.cwd(),
114
+ });
115
+ }
116
+ function jsonResult(value, isError = false) {
117
+ return textResult(JSON.stringify(value, null, 2), isError);
118
+ }
119
+ function apiResult(result) {
120
+ if (result.status >= 400)
121
+ return jsonResult(result.body, true);
122
+ return jsonResult(result.body);
123
+ }
124
+ function parseDays(value, fallback = 30) {
125
+ const n = typeof value === "number" ? value : typeof value === "string" ? Number(value) : fallback;
126
+ if (!Number.isFinite(n) || n <= 0)
127
+ return String(fallback);
128
+ return String(Math.min(365, Math.floor(n)));
129
+ }
130
+ function compactGraph(body) {
131
+ if (!body || typeof body !== "object")
132
+ return body;
133
+ const g = body;
134
+ const claims = (g.claims ?? []).slice(0, 80).map((c) => ({
135
+ id: c.id,
136
+ kind: c.kind,
137
+ text: c.text,
138
+ anchors: (() => {
139
+ try {
140
+ return JSON.parse(c.code_anchors);
141
+ }
142
+ catch {
143
+ return c.code_anchors;
144
+ }
145
+ })(),
146
+ }));
147
+ return {
148
+ claims,
149
+ claimCount: (g.claims ?? []).length,
150
+ truncated: (g.claims ?? []).length > claims.length,
151
+ components: (g.components ?? []).map((c) => ({
152
+ id: c.id,
153
+ name: c.name,
154
+ anchor: c.code_anchor,
155
+ })),
156
+ flows: g.flows ?? [],
157
+ recentClaimIds: g.recentClaimIds ?? [],
158
+ activity: g.activity,
159
+ };
160
+ }
161
+ function compactStats(body) {
162
+ if (!body || typeof body !== "object")
163
+ return body;
164
+ const s = body;
165
+ return {
166
+ scope: s.scope,
167
+ days: s.days,
168
+ aggregate: s.aggregate,
169
+ repos: s.repos,
170
+ };
171
+ }
172
+ function callTool(name, args, fallbackWorkspace) {
173
+ const workspace = defaultWorkspace(args.workspace, fallbackWorkspace);
174
+ if (name === "amem_context") {
175
+ const query = typeof args.query === "string" ? args.query : "";
176
+ if (!query.trim())
177
+ return textResult("query is required", true);
178
+ const result = api("POST", "/api/context", {
179
+ query,
180
+ workspace,
181
+ platform: typeof args.platform === "string" ? args.platform : "mcp",
182
+ sessionId: typeof args.sessionId === "string" ? args.sessionId : undefined,
183
+ }, { workspace });
184
+ if (result.status >= 400) {
185
+ return textResult(JSON.stringify(result.body), true);
186
+ }
187
+ const body = result.body;
188
+ return textResult(body.markdown || JSON.stringify(result.body));
189
+ }
190
+ if (name === "amem_recipe") {
191
+ const result = api("GET", "/api/recipe", null);
192
+ if (result.status >= 400)
193
+ return apiResult(result);
194
+ const contract = result.body;
195
+ return textResult(contract.paste || contract.markdown || JSON.stringify(result.body));
196
+ }
197
+ if (name === "amem_remember") {
198
+ const text = typeof args.text === "string" ? args.text : "";
199
+ if (!text.trim())
200
+ return textResult("text is required", true);
201
+ const result = api("POST", "/api/remember", {
202
+ text,
203
+ workspace,
204
+ kind: typeof args.kind === "string" ? args.kind : "session",
205
+ anchors: args.anchors,
206
+ source: "mcp",
207
+ }, { workspace });
208
+ if (result.status >= 400)
209
+ return textResult(JSON.stringify(result.body), true);
210
+ return textResult(JSON.stringify(result.body));
211
+ }
212
+ if (name === "amem_repos") {
213
+ const result = api("GET", "/api/repos", null);
214
+ if (result.status >= 400)
215
+ return apiResult(result);
216
+ const kind = typeof args.kind === "string" ? args.kind.trim().toLowerCase() : "all";
217
+ const repos = (result.body.repos ?? []).filter((r) => {
218
+ if (kind === "git" || kind === "workspace")
219
+ return r.kind === kind;
220
+ return true;
221
+ });
222
+ return jsonResult({
223
+ count: repos.length,
224
+ kind: kind === "git" || kind === "workspace" ? kind : "all",
225
+ repos,
226
+ });
227
+ }
228
+ if (name === "amem_stats") {
229
+ const scope = typeof args.scope === "string" && args.scope.trim()
230
+ ? args.scope.trim()
231
+ : workspace
232
+ ? "current"
233
+ : "all";
234
+ const result = api("GET", "/api/usage", null, {
235
+ workspace,
236
+ query: { scope, days: parseDays(args.days) },
237
+ });
238
+ if (result.status >= 400)
239
+ return apiResult(result);
240
+ return jsonResult(compactStats(result.body));
241
+ }
242
+ if (name === "amem_graph") {
243
+ const result = api("GET", "/api/graph", null, {
244
+ workspace,
245
+ query: { days: parseDays(args.days) },
246
+ });
247
+ if (result.status >= 400)
248
+ return apiResult(result);
249
+ return jsonResult(compactGraph(result.body));
250
+ }
251
+ if (name === "amem_status") {
252
+ if (workspace && !getRepoByName(workspace)) {
253
+ return textResult(`Workspace not found: ${workspace}`, true);
254
+ }
255
+ const result = api("GET", "/api/status", null, { workspace });
256
+ return apiResult(result);
257
+ }
258
+ return textResult(`Unknown tool: ${name}`, true);
259
+ }
260
+ function rpcResult(id, result) {
261
+ return { jsonrpc: "2.0", id, result };
262
+ }
263
+ function rpcError(id, message, code = -32601) {
264
+ return { jsonrpc: "2.0", id, error: { code, message } };
265
+ }
266
+ export function isJsonRpcMessage(body) {
267
+ if (!body || typeof body !== "object" || Array.isArray(body))
268
+ return false;
269
+ const msg = body;
270
+ return msg.jsonrpc === "2.0";
271
+ }
272
+ /** Handle one JSON-RPC message. Notifications (no id) return null. */
273
+ export function dispatchMcp(msg, fallbackWorkspace) {
274
+ const { id, method, params } = msg;
275
+ if (!method)
276
+ return null;
277
+ if (id === undefined || id === null)
278
+ return null;
279
+ if (method === "initialize") {
280
+ const requested = params && typeof params.protocolVersion === "string" ? params.protocolVersion : "";
281
+ return rpcResult(id, {
282
+ protocolVersion: PROTOCOL_VERSIONS.has(requested) ? requested : "2024-11-05",
283
+ capabilities: {
284
+ tools: { listChanged: false },
285
+ resources: { listChanged: false },
286
+ prompts: { listChanged: false },
287
+ },
288
+ serverInfo: { name: "amem", version: "0.1.0" },
289
+ });
290
+ }
291
+ if (method === "ping")
292
+ return rpcResult(id, {});
293
+ if (method === "tools/list") {
294
+ return rpcResult(id, { tools: TOOLS });
295
+ }
296
+ if (method === "resources/list")
297
+ return rpcResult(id, { resources: [] });
298
+ if (method === "prompts/list")
299
+ return rpcResult(id, { prompts: [] });
300
+ if (method === "tools/call") {
301
+ const name = typeof params?.name === "string" ? params.name : "";
302
+ const args = params?.arguments && typeof params.arguments === "object"
303
+ ? params.arguments
304
+ : {};
305
+ return rpcResult(id, callTool(name, args, fallbackWorkspace));
306
+ }
307
+ return rpcError(id, `Method not found: ${method}`);
308
+ }
309
+ export function handleMcpHttpBody(body, fallbackWorkspace) {
310
+ if (Array.isArray(body)) {
311
+ const results = body
312
+ .filter(isJsonRpcMessage)
313
+ .map((msg) => dispatchMcp(msg, fallbackWorkspace))
314
+ .filter((r) => r !== null);
315
+ return { status: 200, body: results };
316
+ }
317
+ if (!isJsonRpcMessage(body)) {
318
+ return {
319
+ status: 400,
320
+ body: { jsonrpc: "2.0", error: { code: -32600, message: "Invalid JSON-RPC" } },
321
+ };
322
+ }
323
+ const result = dispatchMcp(body, fallbackWorkspace);
324
+ if (!result)
325
+ return { status: 202, body: null };
326
+ return { status: 200, body: result };
327
+ }
328
+ function encodeMessage(msg, framing) {
329
+ const payload = Buffer.from(JSON.stringify(msg), "utf8");
330
+ if (framing === "ndjson") {
331
+ return Buffer.concat([payload, Buffer.from("\n")]);
332
+ }
333
+ const header = Buffer.from(`Content-Length: ${payload.length}\r\n\r\n`, "utf8");
334
+ return Buffer.concat([header, payload]);
335
+ }
336
+ function headerEndIndex(buf) {
337
+ const crlf = buf.indexOf("\r\n\r\n");
338
+ if (crlf !== -1)
339
+ return { index: crlf, size: 4 };
340
+ const lf = buf.indexOf("\n\n");
341
+ if (lf !== -1)
342
+ return { index: lf, size: 2 };
343
+ return null;
344
+ }
345
+ function looksLikeJsonStart(buf) {
346
+ for (let i = 0; i < buf.length; i++) {
347
+ const c = buf[i];
348
+ if (c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0d)
349
+ continue;
350
+ return c === 0x7b; // {
351
+ }
352
+ return false;
353
+ }
354
+ function parseStdio(buf) {
355
+ const items = [];
356
+ let rest = buf;
357
+ while (rest.length) {
358
+ if (looksLikeJsonStart(rest)) {
359
+ const nl = rest.indexOf("\n");
360
+ if (nl === -1)
361
+ break;
362
+ const line = rest.slice(0, nl).toString("utf8").trim();
363
+ rest = rest.slice(nl + 1);
364
+ if (!line)
365
+ continue;
366
+ try {
367
+ items.push({ msg: JSON.parse(line), framing: "ndjson" });
368
+ }
369
+ catch {
370
+ // skip malformed line
371
+ }
372
+ continue;
373
+ }
374
+ const headerEnd = headerEndIndex(rest);
375
+ if (!headerEnd)
376
+ break;
377
+ const header = rest.slice(0, headerEnd.index).toString("utf8");
378
+ const lenMatch = /Content-Length:\s*(\d+)/i.exec(header);
379
+ if (!lenMatch) {
380
+ rest = rest.slice(headerEnd.index + headerEnd.size);
381
+ continue;
382
+ }
383
+ const len = Number(lenMatch[1]);
384
+ const start = headerEnd.index + headerEnd.size;
385
+ if (rest.length < start + len)
386
+ break;
387
+ const raw = rest.slice(start, start + len).toString("utf8");
388
+ rest = rest.slice(start + len);
389
+ try {
390
+ items.push({ msg: JSON.parse(raw), framing: "lsp" });
391
+ }
392
+ catch {
393
+ // skip malformed
394
+ }
395
+ }
396
+ return { rest, items };
397
+ }
398
+ export function stdioMcpLaunch(workspace) {
399
+ const script = process.argv[1];
400
+ const useNode = Boolean(script && existsSync(script));
401
+ return {
402
+ command: useNode ? process.execPath : "amem",
403
+ args: useNode ? [script, "mcp"] : ["mcp"],
404
+ env: workspace ? { AMEM_WORKSPACE: workspace } : {},
405
+ };
406
+ }
407
+ export function mcpClientConfig(workspace = "my-app", port = 7843) {
408
+ const stdio = stdioMcpLaunch(workspace);
409
+ const httpUrl = `http://127.0.0.1:${port}/mcp?workspace=${encodeURIComponent(workspace)}`;
410
+ return {
411
+ mcpServers: {
412
+ amem: {
413
+ command: stdio.command,
414
+ args: stdio.args,
415
+ env: stdio.env,
416
+ },
417
+ },
418
+ http: { url: httpUrl },
419
+ };
420
+ }
421
+ export async function runMcpServer() {
422
+ let buf = Buffer.alloc(0);
423
+ let framing = "ndjson";
424
+ for await (const chunk of process.stdin) {
425
+ buf = Buffer.concat([buf, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]);
426
+ const parsed = parseStdio(buf);
427
+ buf = Buffer.from(parsed.rest);
428
+ for (const item of parsed.items) {
429
+ framing = item.framing;
430
+ const response = dispatchMcp(item.msg);
431
+ if (response)
432
+ process.stdout.write(encodeMessage(response, framing));
433
+ }
434
+ }
435
+ }
@@ -0,0 +1,11 @@
1
+ export declare function tryEnsureDir(dir: string): {
2
+ ok: true;
3
+ } | {
4
+ ok: false;
5
+ message: string;
6
+ };
7
+ /** Memory is in SQLite. A missing placeholder folder must not block workspace create. */
8
+ export declare function amemHomeWriteIssue(): string | null;
9
+ export declare function amemHome(): string;
10
+ export declare function ensureAmemHome(): string;
11
+ export declare function dbPath(): string;
package/dist/paths.js ADDED
@@ -0,0 +1,55 @@
1
+ import { chmodSync, existsSync, mkdirSync, rmdirSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ export function tryEnsureDir(dir) {
5
+ try {
6
+ mkdirSync(dir, { recursive: true });
7
+ return { ok: true };
8
+ }
9
+ catch (error) {
10
+ const err = error;
11
+ if (err.code === "EPERM" || err.code === "EACCES") {
12
+ return { ok: false, message: err.message };
13
+ }
14
+ throw error;
15
+ }
16
+ }
17
+ /** Memory is in SQLite. A missing placeholder folder must not block workspace create. */
18
+ export function amemHomeWriteIssue() {
19
+ const probe = join(amemHome(), "workspaces", `.write-probe-${process.pid}`);
20
+ try {
21
+ mkdirSync(probe, { recursive: true });
22
+ rmdirSync(probe);
23
+ return null;
24
+ }
25
+ catch (error) {
26
+ const err = error;
27
+ if (err.code === "EPERM" || err.code === "EACCES") {
28
+ return "Cannot create folders under ~/.amem (this process is likely sandboxed — e.g. amem ui started from Cursor). Quit it and run `amem ui` in Terminal, or `amem service install` so it starts at login.";
29
+ }
30
+ return null;
31
+ }
32
+ }
33
+ export function amemHome() {
34
+ return process.env.AMEM_HOME ?? join(homedir(), ".amem");
35
+ }
36
+ export function ensureAmemHome() {
37
+ const home = amemHome();
38
+ if (!existsSync(home)) {
39
+ mkdirSync(home, { recursive: true, mode: 0o700 });
40
+ }
41
+ try {
42
+ chmodSync(home, 0o700);
43
+ }
44
+ catch {
45
+ // Best-effort on platforms that ignore chmod
46
+ }
47
+ const sessions = join(home, "sessions");
48
+ if (!existsSync(sessions)) {
49
+ mkdirSync(sessions, { recursive: true, mode: 0o700 });
50
+ }
51
+ return home;
52
+ }
53
+ export function dbPath() {
54
+ return join(ensureAmemHome(), "graph.db");
55
+ }
@@ -0,0 +1,7 @@
1
+ import { type ClaimRow, type RepoRow } from "./db.js";
2
+ export declare const PERSONAL_SLUG = "personal";
3
+ export declare function personalRoot(): string;
4
+ export declare function ensurePersonalWorkspace(platform?: string): RepoRow;
5
+ export declare function isPersonalRepo(repo: RepoRow | null | undefined): boolean;
6
+ /** Active personal claims for blending into another repo's context. */
7
+ export declare function listPersonalClaims(): ClaimRow[];
@@ -0,0 +1,44 @@
1
+ import { join } from "node:path";
2
+ import { applyProposal } from "./proposal.js";
3
+ import { getRepoByName, listClaims, upsertRepo, } from "./db.js";
4
+ import { amemHome, tryEnsureDir } from "./paths.js";
5
+ import { workspaceIdentity } from "./repo-identity.js";
6
+ export const PERSONAL_SLUG = "personal";
7
+ export function personalRoot() {
8
+ const dir = join(amemHome(), "workspaces", PERSONAL_SLUG);
9
+ tryEnsureDir(dir);
10
+ return dir;
11
+ }
12
+ export function ensurePersonalWorkspace(platform = "app") {
13
+ const existing = getRepoByName(PERSONAL_SLUG);
14
+ if (existing)
15
+ return existing;
16
+ const identity = workspaceIdentity(PERSONAL_SLUG, personalRoot());
17
+ // Force display name
18
+ identity.repoName = "Personal prefs";
19
+ const repo = upsertRepo(identity, platform);
20
+ applyProposal(repo.id, {
21
+ claims: [
22
+ {
23
+ id: "claim.personal_scope",
24
+ kind: "structure",
25
+ text: "This is your cross-repo personal amem space (how you work). It can inject into any project context without becoming a shared company wiki.",
26
+ code_anchors: [PERSONAL_SLUG],
27
+ source_ref: "personal-setup",
28
+ },
29
+ ],
30
+ });
31
+ return getRepoByName(PERSONAL_SLUG) ?? repo;
32
+ }
33
+ export function isPersonalRepo(repo) {
34
+ if (!repo)
35
+ return false;
36
+ return (repo.remote_url || "").includes(`amem://workspace/${PERSONAL_SLUG}`) || repo.repo_name === PERSONAL_SLUG;
37
+ }
38
+ /** Active personal claims for blending into another repo's context. */
39
+ export function listPersonalClaims() {
40
+ const personal = getRepoByName(PERSONAL_SLUG);
41
+ if (!personal)
42
+ return [];
43
+ return listClaims(personal.id);
44
+ }
@@ -0,0 +1,11 @@
1
+ export type KnownPlatform = {
2
+ id: string;
3
+ label: string;
4
+ hint: string;
5
+ installs?: "cursor" | "claude" | "host";
6
+ };
7
+ /** Clients the Setup picker can record. Cursor/Claude also get local installers. */
8
+ export declare const KNOWN_PLATFORMS: KnownPlatform[];
9
+ export declare function isKnownPlatform(id: string): boolean;
10
+ export declare function normalizePlatforms(raw: unknown): string[];
11
+ export declare const HOST_INSTALL_IDS: Set<string>;
@@ -0,0 +1,32 @@
1
+ /** Clients the Setup picker can record. Cursor/Claude also get local installers. */
2
+ export const KNOWN_PLATFORMS = [
3
+ { id: "cursor", label: "Cursor", hint: "Rules, skills, hooks", installs: "cursor" },
4
+ { id: "claude", label: "Claude Code", hint: "Skills and settings hooks", installs: "claude" },
5
+ { id: "copilot", label: "GitHub Copilot", hint: "MCP / HTTP API" },
6
+ { id: "codex", label: "ChatGPT / Codex", hint: "MCP / HTTP API" },
7
+ { id: "gemini", label: "Gemini", hint: "MCP / HTTP API" },
8
+ { id: "windsurf", label: "Windsurf", hint: "MCP config installer", installs: "host" },
9
+ { id: "continue", label: "Continue", hint: "MCP config installer", installs: "host" },
10
+ { id: "aider", label: "Aider", hint: "CLI hint file in repo", installs: "host" },
11
+ { id: "zed", label: "Zed", hint: "context_servers MCP", installs: "host" },
12
+ { id: "grok", label: "Grok", hint: "MCP / HTTP API" },
13
+ ];
14
+ const KNOWN_IDS = new Set(KNOWN_PLATFORMS.map((p) => p.id));
15
+ export function isKnownPlatform(id) {
16
+ return KNOWN_IDS.has(id);
17
+ }
18
+ export function normalizePlatforms(raw) {
19
+ if (!Array.isArray(raw))
20
+ return [];
21
+ const out = [];
22
+ for (const value of raw) {
23
+ if (typeof value !== "string")
24
+ continue;
25
+ const id = value.trim().toLowerCase();
26
+ if (!KNOWN_IDS.has(id) || out.includes(id))
27
+ continue;
28
+ out.push(id);
29
+ }
30
+ return out;
31
+ }
32
+ export const HOST_INSTALL_IDS = new Set(KNOWN_PLATFORMS.filter((p) => p.installs === "host").map((p) => p.id));
@@ -0,0 +1,46 @@
1
+ export type AmemPolicy = {
2
+ telemetry: boolean;
3
+ ui_enabled: boolean;
4
+ ui_bind: string;
5
+ allow_export: boolean;
6
+ allowed_platforms: string[];
7
+ allowed_remote_hosts: string[];
8
+ deny_claim_patterns: string[];
9
+ /** Draft kinds that may auto-apply without Memory approve (still local). */
10
+ auto_apply_kinds: string[];
11
+ };
12
+ export type PolicySource = {
13
+ path: string;
14
+ role: "defaults" | "user" | "system" | "env";
15
+ applied: boolean;
16
+ error?: string;
17
+ };
18
+ export type LoadedPolicy = {
19
+ policy: AmemPolicy;
20
+ sources: PolicySource[];
21
+ };
22
+ /** Built-in secret hygiene — always active; policy patterns extend these. */
23
+ export declare const BUILTIN_DENY_CLAIM_PATTERNS: string[];
24
+ export declare const DEFAULT_POLICY: AmemPolicy;
25
+ export declare function clearPolicyCache(): void;
26
+ export declare function systemPolicyPath(): string;
27
+ export declare function userPolicyPath(): string;
28
+ /**
29
+ * Minimal TOML subset for amem policy files:
30
+ * booleans, quoted strings, and string arrays. Comments (#) and blank lines ok.
31
+ */
32
+ export declare function parsePolicyToml(raw: string): Partial<AmemPolicy>;
33
+ /**
34
+ * Load effective policy.
35
+ * Precedence (later wins): defaults → user (~/.amem) → system (/etc/amem) → AMEM_POLICY_PATH.
36
+ * MDM/system always overrides user for enterprise lock-down.
37
+ */
38
+ export declare function loadPolicy(forceReload?: boolean): LoadedPolicy;
39
+ export declare function effectiveDenyPatterns(policy?: AmemPolicy): string[];
40
+ export declare function compiledDenyPatterns(policy?: AmemPolicy): RegExp[];
41
+ export declare function assertPlatformAllowed(platform: string, policy?: AmemPolicy): void;
42
+ export declare function assertRemoteAllowed(remoteUrl: string | null, policy?: AmemPolicy): void;
43
+ export declare function assertExportAllowed(policy?: AmemPolicy): void;
44
+ export declare function assertUiAllowed(policy?: AmemPolicy): void;
45
+ /** Unused today — kept so attest / docs can prove the knob exists and is forced off. */
46
+ export declare function telemetryEnabled(policy?: AmemPolicy): boolean;