@memoryrelay/mcp-server 0.1.9 → 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.
@@ -0,0 +1,796 @@
1
+ // src/config.ts
2
+ import { z } from "zod";
3
+ var configSchema = z.object({
4
+ apiKey: z.string().startsWith("mem_", { message: 'API key must start with "mem_"' }).min(20, { message: "API key appears to be invalid (too short)" }),
5
+ apiUrl: z.string().url({ message: "API URL must be a valid URL" }).default("https://api.memoryrelay.net"),
6
+ agentId: z.string().optional().describe("Agent identifier - auto-detected if not provided"),
7
+ timeout: z.number().positive({ message: "Timeout must be positive" }).default(3e4),
8
+ logLevel: z.enum(["debug", "info", "warn", "error"]).default("info")
9
+ });
10
+ var TOOL_GROUPS = {
11
+ core: [
12
+ "memory_store",
13
+ "memory_search",
14
+ "memory_list",
15
+ "memory_get",
16
+ "memory_update",
17
+ "memory_delete",
18
+ "entity_create",
19
+ "entity_link",
20
+ "entity_list",
21
+ "entity_graph",
22
+ "memory_batch_store",
23
+ "memory_context",
24
+ "agent_list",
25
+ "agent_create",
26
+ "agent_get",
27
+ "memory_health"
28
+ ],
29
+ sessions: ["session_start", "session_end", "session_recall", "session_list"],
30
+ decisions: ["decision_record", "decision_list", "decision_supersede", "decision_check"],
31
+ patterns: ["pattern_create", "pattern_search", "pattern_adopt", "pattern_suggest"],
32
+ projects: ["project_register", "project_list", "project_info"],
33
+ relationships: [
34
+ "project_add_relationship",
35
+ "project_dependencies",
36
+ "project_dependents",
37
+ "project_related",
38
+ "project_impact",
39
+ "project_shared_patterns"
40
+ ],
41
+ context: ["project_context", "memory_promote"]
42
+ };
43
+ function getEnabledTools() {
44
+ const raw = process.env.MEMORYRELAY_TOOLS;
45
+ if (!raw || raw.trim().toLowerCase() === "all") {
46
+ return null;
47
+ }
48
+ const groups = raw.split(",").map((g) => g.trim().toLowerCase());
49
+ const enabled = /* @__PURE__ */ new Set();
50
+ for (const group of groups) {
51
+ const tools = TOOL_GROUPS[group];
52
+ if (tools) {
53
+ for (const tool of tools) {
54
+ enabled.add(tool);
55
+ }
56
+ }
57
+ }
58
+ return enabled;
59
+ }
60
+ function loadConfig() {
61
+ try {
62
+ const config = configSchema.parse({
63
+ apiKey: process.env.MEMORYRELAY_API_KEY,
64
+ apiUrl: process.env.MEMORYRELAY_API_URL,
65
+ agentId: process.env.MEMORYRELAY_AGENT_ID,
66
+ timeout: process.env.MEMORYRELAY_TIMEOUT ? parseInt(process.env.MEMORYRELAY_TIMEOUT, 10) : void 0,
67
+ logLevel: process.env.MEMORYRELAY_LOG_LEVEL
68
+ });
69
+ return config;
70
+ } catch (error) {
71
+ if (error instanceof z.ZodError) {
72
+ const issues = error.issues.map(
73
+ (issue) => ` - ${issue.path.join(".")}: ${issue.message}`
74
+ ).join("\n");
75
+ throw new Error(
76
+ `Configuration validation failed:
77
+ ${issues}
78
+
79
+ Please check your environment variables:
80
+ - MEMORYRELAY_API_KEY (required, starts with "mem_")
81
+ - MEMORYRELAY_API_URL (optional, default: https://api.memoryrelay.net)
82
+ - MEMORYRELAY_AGENT_ID (optional, auto-detected)
83
+ - MEMORYRELAY_TIMEOUT (optional, default: 30000)
84
+ - MEMORYRELAY_LOG_LEVEL (optional, default: info)`,
85
+ { cause: error }
86
+ );
87
+ }
88
+ throw error;
89
+ }
90
+ }
91
+ function getAgentId(config) {
92
+ if (config.agentId) {
93
+ return config.agentId;
94
+ }
95
+ const openclawAgent = process.env.OPENCLAW_AGENT_NAME;
96
+ if (openclawAgent) {
97
+ return openclawAgent.slice(0, 32);
98
+ }
99
+ const hostname = process.env.HOSTNAME || process.env.COMPUTERNAME || "unknown";
100
+ const user = process.env.USER || process.env.USERNAME || "";
101
+ return user ? `${user}-${hostname}`.slice(0, 32) : `mcp-${hostname}`.slice(0, 32);
102
+ }
103
+
104
+ // src/logger.ts
105
+ var LOG_LEVELS = {
106
+ debug: 0,
107
+ info: 1,
108
+ warn: 2,
109
+ error: 3
110
+ };
111
+ var Logger = class {
112
+ minLevel;
113
+ constructor(level = "info") {
114
+ this.minLevel = LOG_LEVELS[level];
115
+ }
116
+ /**
117
+ * Mask sensitive data in log messages
118
+ * - API keys starting with "mem_" are masked
119
+ * - Internal paths are sanitized
120
+ */
121
+ sanitize(message) {
122
+ let sanitized = message;
123
+ sanitized = sanitized.replace(/mem_[a-zA-Z0-9_-]+/g, "mem_****");
124
+ sanitized = sanitized.replace(/\/[a-zA-Z0-9_\-./]+\.(ts|js|json)/g, "<file>");
125
+ sanitized = sanitized.replace(/at\s+[^\s]+\s+\([^)]+\)/g, "at <location>");
126
+ return sanitized;
127
+ }
128
+ /**
129
+ * Format log message with timestamp and level
130
+ */
131
+ format(level, message, data) {
132
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
133
+ const sanitizedMessage = this.sanitize(message);
134
+ let output = `[${timestamp}] [${level.toUpperCase()}] ${sanitizedMessage}`;
135
+ if (data !== void 0) {
136
+ const sanitizedData = this.sanitize(JSON.stringify(data, null, 2));
137
+ output += `
138
+ ${sanitizedData}`;
139
+ }
140
+ return output;
141
+ }
142
+ debug(message, data) {
143
+ if (this.minLevel <= LOG_LEVELS.debug) {
144
+ console.error(this.format("debug", message, data));
145
+ }
146
+ }
147
+ info(message, data) {
148
+ if (this.minLevel <= LOG_LEVELS.info) {
149
+ console.error(this.format("info", message, data));
150
+ }
151
+ }
152
+ warn(message, data) {
153
+ if (this.minLevel <= LOG_LEVELS.warn) {
154
+ console.error(this.format("warn", message, data));
155
+ }
156
+ }
157
+ error(message, data) {
158
+ if (this.minLevel <= LOG_LEVELS.error) {
159
+ console.error(this.format("error", message, data));
160
+ }
161
+ }
162
+ };
163
+ var logger;
164
+ function initLogger(level = "info") {
165
+ logger = new Logger(level);
166
+ return logger;
167
+ }
168
+ function getLogger() {
169
+ if (!logger) {
170
+ logger = new Logger();
171
+ }
172
+ return logger;
173
+ }
174
+
175
+ // src/client.ts
176
+ var MAX_RETRIES = 3;
177
+ var INITIAL_DELAY_MS = 1e3;
178
+ var MAX_CONTENT_SIZE = 50 * 1024;
179
+ async function withRetry(fn, retries = MAX_RETRIES) {
180
+ let lastError;
181
+ for (let attempt = 0; attempt <= retries; attempt++) {
182
+ try {
183
+ return await fn();
184
+ } catch (error) {
185
+ lastError = error instanceof Error ? error : new Error(String(error));
186
+ if (lastError.message.includes("401") || lastError.message.includes("403") || lastError.message.includes("404") || lastError.message.includes("400")) {
187
+ throw lastError;
188
+ }
189
+ if (attempt === retries) {
190
+ throw lastError;
191
+ }
192
+ const delay = INITIAL_DELAY_MS * Math.pow(2, attempt);
193
+ const jitter = Math.random() * 0.3 * delay;
194
+ await new Promise((resolve) => setTimeout(resolve, delay + jitter));
195
+ }
196
+ }
197
+ throw lastError || new Error("Retry failed");
198
+ }
199
+ function maskApiKey(message, apiKey) {
200
+ if (!apiKey) return message;
201
+ const maskedKey = apiKey.substring(0, 8) + "***";
202
+ return message.replace(new RegExp(apiKey, "g"), maskedKey);
203
+ }
204
+ var MemoryRelayClient = class {
205
+ config;
206
+ logger = getLogger();
207
+ constructor(config) {
208
+ this.config = config;
209
+ this.logger.info("MemoryRelay client initialized", {
210
+ apiUrl: config.apiUrl,
211
+ agentId: config.agentId
212
+ });
213
+ }
214
+ /**
215
+ * Make authenticated HTTP request to MemoryRelay API with retry logic
216
+ */
217
+ async request(method, path, body) {
218
+ return withRetry(async () => {
219
+ const url = `${this.config.apiUrl}${path}`;
220
+ this.logger.debug(`API request: ${method} ${path}`);
221
+ const controller = new AbortController();
222
+ const timeout = setTimeout(() => controller.abort(), this.config.timeout);
223
+ try {
224
+ const response = await fetch(url, {
225
+ method,
226
+ headers: {
227
+ "Content-Type": "application/json",
228
+ "Authorization": `Bearer ${this.config.apiKey}`,
229
+ "User-Agent": "@memoryrelay/mcp-server"
230
+ },
231
+ body: body ? JSON.stringify(body) : void 0,
232
+ signal: controller.signal
233
+ });
234
+ if (!response.ok) {
235
+ if (response.status === 429) {
236
+ const retryAfter = response.headers.get("Retry-After");
237
+ const waitMs = retryAfter ? parseInt(retryAfter) * 1e3 : 5e3;
238
+ this.logger.warn(`Rate limited, waiting ${waitMs}ms`);
239
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
240
+ throw new Error(`Rate limited: 429 - Retry after ${waitMs}ms`);
241
+ }
242
+ const errorData = await response.json().catch(() => ({}));
243
+ const errorMsg = `API request failed: ${response.status} ${response.statusText}` + (errorData.message ? ` - ${errorData.message}` : "");
244
+ throw new Error(maskApiKey(errorMsg, this.config.apiKey));
245
+ }
246
+ const data = await response.json();
247
+ this.logger.debug(`API response: ${method} ${path}`, { status: response.status });
248
+ return data;
249
+ } catch (error) {
250
+ if (error instanceof Error) {
251
+ if (error.name === "AbortError") {
252
+ throw new Error(`Request timeout after ${this.config.timeout}ms`, { cause: error });
253
+ }
254
+ error.message = maskApiKey(error.message, this.config.apiKey);
255
+ }
256
+ throw error;
257
+ } finally {
258
+ clearTimeout(timeout);
259
+ }
260
+ });
261
+ }
262
+ /**
263
+ * Validate content size
264
+ */
265
+ validateContentSize(content) {
266
+ if (content.length > MAX_CONTENT_SIZE) {
267
+ throw new Error(`Content exceeds maximum size of ${MAX_CONTENT_SIZE} bytes`);
268
+ }
269
+ }
270
+ /**
271
+ * Store a new memory
272
+ */
273
+ async storeMemory(content, metadata, deduplicate, dedupThreshold, project, importance, tier) {
274
+ this.validateContentSize(content);
275
+ const body = {
276
+ content,
277
+ metadata,
278
+ agent_id: this.config.agentId
279
+ };
280
+ if (deduplicate) {
281
+ body.deduplicate = true;
282
+ }
283
+ if (dedupThreshold !== void 0) {
284
+ body.dedup_threshold = dedupThreshold;
285
+ }
286
+ if (project) {
287
+ body.project = project;
288
+ }
289
+ if (importance !== void 0) {
290
+ body.importance = importance;
291
+ }
292
+ if (tier) {
293
+ body.tier = tier;
294
+ }
295
+ return this.request("POST", "/v1/memories", body);
296
+ }
297
+ /**
298
+ * Search memories using semantic search
299
+ * @param agentId - Optional agent ID override. If omitted, uses config agentId. Pass null for cross-agent search.
300
+ * @param includeConfidential - Include confidential memories in results
301
+ * @param includeArchived - Include archived memories in results
302
+ * @param project - Optional project slug to filter by
303
+ */
304
+ async searchMemories(query, limit = 10, threshold = 0.5, agentId, includeConfidential = false, includeArchived = false, compress = false, maxContextTokens, project, tier, minImportance) {
305
+ this.validateContentSize(query);
306
+ const effectiveAgentId = agentId === null ? void 0 : agentId ?? this.config.agentId;
307
+ const body = { query, limit, threshold };
308
+ if (effectiveAgentId) {
309
+ body.agent_id = effectiveAgentId;
310
+ }
311
+ if (includeConfidential) {
312
+ body.include_confidential = true;
313
+ }
314
+ if (includeArchived) {
315
+ body.include_archived = true;
316
+ }
317
+ if (compress) {
318
+ body.compress = true;
319
+ }
320
+ if (maxContextTokens !== void 0) {
321
+ body.max_context_tokens = maxContextTokens;
322
+ }
323
+ if (project) {
324
+ body.project = project;
325
+ }
326
+ if (tier) {
327
+ body.tier = tier;
328
+ }
329
+ if (minImportance !== void 0) {
330
+ body.min_importance = minImportance;
331
+ }
332
+ const response = await this.request(
333
+ "POST",
334
+ "/v1/memories/search",
335
+ body
336
+ );
337
+ return response.data;
338
+ }
339
+ /**
340
+ * List recent memories with pagination
341
+ */
342
+ async listMemories(limit = 20, offset = 0) {
343
+ return this.request(
344
+ "GET",
345
+ `/v1/memories?limit=${limit}&offset=${offset}`
346
+ );
347
+ }
348
+ /**
349
+ * Get a specific memory by ID
350
+ */
351
+ async getMemory(id) {
352
+ return this.request("GET", `/v1/memories/${id}`);
353
+ }
354
+ /**
355
+ * Update an existing memory
356
+ */
357
+ async updateMemory(id, content, metadata) {
358
+ this.validateContentSize(content);
359
+ return this.request("PATCH", `/v1/memories/${id}`, {
360
+ content,
361
+ metadata
362
+ });
363
+ }
364
+ /**
365
+ * Delete a memory
366
+ */
367
+ async deleteMemory(id) {
368
+ await this.request("DELETE", `/v1/memories/${id}`);
369
+ }
370
+ /**
371
+ * Create a named entity
372
+ */
373
+ async createEntity(name, type, metadata) {
374
+ this.validateContentSize(name);
375
+ return this.request("POST", "/v1/entities", {
376
+ name,
377
+ type,
378
+ metadata
379
+ });
380
+ }
381
+ /**
382
+ * Link an entity to a memory
383
+ */
384
+ async linkEntity(entityId, memoryId, relationship = "mentioned_in") {
385
+ await this.request("POST", "/v1/entities/links", {
386
+ entity_id: entityId,
387
+ memory_id: memoryId,
388
+ relationship
389
+ });
390
+ }
391
+ /**
392
+ * Get an entity by ID
393
+ */
394
+ async getEntity(id) {
395
+ return this.request("GET", `/v1/entities/${id}`);
396
+ }
397
+ /**
398
+ * List entities with pagination
399
+ */
400
+ async listEntities(limit = 20, offset = 0) {
401
+ return this.request(
402
+ "GET",
403
+ `/v1/entities?limit=${limit}&offset=${offset}`
404
+ );
405
+ }
406
+ /**
407
+ * Get entity neighborhood (ego-centric subgraph).
408
+ * Returns the entity's 1-hop or 2-hop neighbors and relationships.
409
+ */
410
+ async getEntityNeighborhood(entityId, depth = 1, maxNeighbors = 50) {
411
+ return this.request(
412
+ "GET",
413
+ `/v1/entities/${entityId}/neighborhood?depth=${depth}&max_neighbors=${maxNeighbors}`
414
+ );
415
+ }
416
+ /**
417
+ * Delete an entity
418
+ */
419
+ async deleteEntity(id) {
420
+ await this.request("DELETE", `/v1/entities/${id}`);
421
+ }
422
+ /**
423
+ * List agents with pagination
424
+ */
425
+ async listAgents(limit = 20, offset = 0) {
426
+ return this.request(
427
+ "GET",
428
+ `/v1/agents?limit=${limit}&offset=${offset}`
429
+ );
430
+ }
431
+ /**
432
+ * Create a new agent
433
+ */
434
+ async createAgent(name, description, metadata) {
435
+ const body = { name };
436
+ if (description) body.description = description;
437
+ if (metadata) body.metadata = metadata;
438
+ return this.request("POST", "/v1/agents", body);
439
+ }
440
+ /**
441
+ * Get an agent by ID
442
+ */
443
+ async getAgent(id) {
444
+ return this.request("GET", `/v1/agents/${id}`);
445
+ }
446
+ /**
447
+ * Batch store multiple memories in a single API call.
448
+ * Uses the /v1/memories/batch endpoint.
449
+ */
450
+ async batchStoreMemories(items) {
451
+ if (items.length === 0) {
452
+ return { success: true, total: 0, succeeded: 0, failed: 0, skipped: 0, results: [] };
453
+ }
454
+ if (items.length > 100) {
455
+ throw new Error("Batch size exceeds maximum of 100 memories");
456
+ }
457
+ const memories = items.map((item) => ({
458
+ content: item.content,
459
+ metadata: item.metadata || {},
460
+ agent_id: item.agent_id || this.config.agentId
461
+ }));
462
+ return this.request("POST", "/v1/memories/batch", { memories });
463
+ }
464
+ /**
465
+ * Build a context string from search results.
466
+ * Searches for relevant memories, formats them, and returns
467
+ * a single string ready for prompt injection.
468
+ */
469
+ async buildContext(query, limit = 10, threshold = 0.5, maxTokens) {
470
+ const results = await this.searchMemories(
471
+ query,
472
+ limit,
473
+ threshold,
474
+ void 0,
475
+ // use default agent
476
+ false,
477
+ // no confidential
478
+ false,
479
+ // no archived
480
+ !!maxTokens,
481
+ // compress if token budget provided
482
+ maxTokens
483
+ );
484
+ if (results.length === 0) {
485
+ return { context: "", memories_used: 0, total_chars: 0 };
486
+ }
487
+ const lines = [];
488
+ for (const result of results) {
489
+ const score = (result.score * 100).toFixed(0);
490
+ lines.push(`[${score}%] ${result.memory.content}`);
491
+ }
492
+ const context = lines.join("\n\n");
493
+ let finalContext = context;
494
+ if (maxTokens) {
495
+ const charBudget = maxTokens * 4;
496
+ if (finalContext.length > charBudget) {
497
+ finalContext = finalContext.slice(0, charBudget) + "\n\n[...truncated]";
498
+ }
499
+ }
500
+ return {
501
+ context: finalContext,
502
+ memories_used: results.length,
503
+ total_chars: finalContext.length
504
+ };
505
+ }
506
+ /**
507
+ * Start a new session
508
+ */
509
+ async startSession(title, project, metadata) {
510
+ const body = {};
511
+ if (this.config.agentId) body.agent_id = this.config.agentId;
512
+ if (title) body.title = title;
513
+ if (project) body.project = project;
514
+ if (metadata) body.metadata = metadata;
515
+ return this.request("POST", "/v1/sessions", body);
516
+ }
517
+ /**
518
+ * End an active session
519
+ */
520
+ async endSession(sessionId, summary) {
521
+ const body = {};
522
+ if (summary) body.summary = summary;
523
+ return this.request("PUT", `/v1/sessions/${sessionId}/end`, body);
524
+ }
525
+ /**
526
+ * Get a session by ID with its memories
527
+ */
528
+ async getSession(sessionId) {
529
+ return this.request(
530
+ "GET",
531
+ `/v1/sessions/${sessionId}?include_memories=true`
532
+ );
533
+ }
534
+ /**
535
+ * List sessions with optional filters
536
+ */
537
+ async listSessions(limit = 20, agentId, project, status) {
538
+ const params = new URLSearchParams();
539
+ params.set("limit", String(limit));
540
+ const effectiveAgentId = agentId ?? this.config.agentId;
541
+ if (effectiveAgentId) params.set("agent_id", effectiveAgentId);
542
+ if (project) params.set("project", project);
543
+ if (status) params.set("status", status);
544
+ return this.request(
545
+ "GET",
546
+ `/v1/sessions?${params.toString()}`
547
+ );
548
+ }
549
+ /**
550
+ * Record a new decision
551
+ */
552
+ async recordDecision(title, rationale, alternatives, project, tags, status, metadata) {
553
+ const body = { title, rationale };
554
+ if (this.config.agentId) body.agent_id = this.config.agentId;
555
+ if (alternatives) body.alternatives = alternatives;
556
+ if (project) body.project_slug = project;
557
+ if (tags) body.tags = tags;
558
+ if (status) body.status = status;
559
+ if (metadata) body.metadata = metadata;
560
+ return this.request("POST", "/v1/decisions", body);
561
+ }
562
+ /**
563
+ * List decisions with optional filters
564
+ */
565
+ async listDecisions(limit, project, status, tags) {
566
+ const params = new URLSearchParams();
567
+ if (limit) params.set("limit", String(limit));
568
+ if (project) params.set("project", project);
569
+ if (status) params.set("status", status);
570
+ if (tags) params.set("tags", tags);
571
+ return this.request(
572
+ "GET",
573
+ `/v1/decisions?${params.toString()}`
574
+ );
575
+ }
576
+ /**
577
+ * Supersede a decision with a new one
578
+ */
579
+ async supersedeDecision(decisionId, title, rationale, alternatives, tags) {
580
+ const body = { title, rationale };
581
+ if (alternatives) body.alternatives = alternatives;
582
+ if (tags) body.tags = tags;
583
+ return this.request(
584
+ "POST",
585
+ `/v1/decisions/${decisionId}/supersede`,
586
+ body
587
+ );
588
+ }
589
+ /**
590
+ * Check for existing decisions about a topic (semantic search)
591
+ */
592
+ async checkDecisions(query, project, limit, threshold, includeSuperseded) {
593
+ const params = new URLSearchParams();
594
+ params.set("query", query);
595
+ if (limit) params.set("limit", String(limit));
596
+ if (threshold !== void 0) params.set("threshold", String(threshold));
597
+ if (project) params.set("project", project);
598
+ if (includeSuperseded) params.set("include_superseded", "true");
599
+ return this.request(
600
+ "GET",
601
+ `/v1/decisions/check?${params.toString()}`
602
+ );
603
+ }
604
+ /**
605
+ * Register a new project
606
+ */
607
+ async createProject(slug, name, description, stack, repo_url, metadata) {
608
+ const body = { slug, name };
609
+ if (description) body.description = description;
610
+ if (stack) body.stack = stack;
611
+ if (repo_url) body.repo_url = repo_url;
612
+ if (metadata) body.metadata = metadata;
613
+ return this.request("POST", "/v1/projects", body);
614
+ }
615
+ /**
616
+ * List projects with optional pagination
617
+ */
618
+ async listProjects(limit = 20, cursor) {
619
+ const params = new URLSearchParams();
620
+ params.set("limit", String(limit));
621
+ if (cursor) params.set("cursor", cursor);
622
+ return this.request(
623
+ "GET",
624
+ `/v1/projects?${params.toString()}`
625
+ );
626
+ }
627
+ /**
628
+ * Get a project by slug
629
+ */
630
+ async getProject(slug) {
631
+ return this.request("GET", `/v1/projects/${slug}`);
632
+ }
633
+ /**
634
+ * Create a reusable pattern
635
+ */
636
+ async createPattern(title, description, category, example_code, scope, tags, source_project, metadata) {
637
+ const body = { title, description };
638
+ if (category) body.category = category;
639
+ if (example_code) body.example_code = example_code;
640
+ if (scope) body.scope = scope;
641
+ if (tags) body.tags = tags;
642
+ if (source_project) body.source_project = source_project;
643
+ if (metadata) body.metadata = metadata;
644
+ return this.request("POST", "/v1/patterns", body);
645
+ }
646
+ /**
647
+ * Search patterns using semantic search
648
+ */
649
+ async searchPatterns(query, category, project, limit, threshold) {
650
+ const params = new URLSearchParams();
651
+ params.set("query", query);
652
+ if (category) params.set("category", category);
653
+ if (project) params.set("project", project);
654
+ if (limit) params.set("limit", String(limit));
655
+ if (threshold !== void 0) params.set("threshold", String(threshold));
656
+ return this.request(
657
+ "GET",
658
+ `/v1/patterns/search?${params.toString()}`
659
+ );
660
+ }
661
+ /**
662
+ * Adopt a pattern for a project
663
+ */
664
+ async adoptPattern(patternId, project) {
665
+ return this.request(
666
+ "POST",
667
+ `/v1/patterns/${patternId}/adopt`,
668
+ { project }
669
+ );
670
+ }
671
+ /**
672
+ * Suggest patterns for a project
673
+ */
674
+ async suggestPatterns(project, limit) {
675
+ const params = new URLSearchParams();
676
+ params.set("project", project);
677
+ if (limit) params.set("limit", String(limit));
678
+ return this.request(
679
+ "GET",
680
+ `/v1/patterns/suggest?${params.toString()}`
681
+ );
682
+ }
683
+ // ── Project Relationships (Issue #186) ──
684
+ /**
685
+ * Add a relationship between two projects
686
+ */
687
+ async addProjectRelationship(sourceSlug, targetProject, relationshipType, metadata) {
688
+ const body = {
689
+ target_project: targetProject,
690
+ relationship_type: relationshipType
691
+ };
692
+ if (metadata) body.metadata = metadata;
693
+ return this.request(
694
+ "POST",
695
+ `/v1/projects/${sourceSlug}/relationships`,
696
+ body
697
+ );
698
+ }
699
+ /**
700
+ * Get what this project depends on
701
+ */
702
+ async getProjectDependencies(slug) {
703
+ return this.request(
704
+ "GET",
705
+ `/v1/projects/${slug}/dependencies`
706
+ );
707
+ }
708
+ /**
709
+ * Get what depends on this project
710
+ */
711
+ async getProjectDependents(slug) {
712
+ return this.request(
713
+ "GET",
714
+ `/v1/projects/${slug}/dependents`
715
+ );
716
+ }
717
+ /**
718
+ * Get all related projects
719
+ */
720
+ async getProjectRelated(slug) {
721
+ return this.request(
722
+ "GET",
723
+ `/v1/projects/${slug}/related`
724
+ );
725
+ }
726
+ /**
727
+ * Run impact analysis for a project change
728
+ */
729
+ async projectImpactAnalysis(project, changeDescription) {
730
+ return this.request(
731
+ "POST",
732
+ "/v1/projects/impact-analysis",
733
+ { project, change_description: changeDescription }
734
+ );
735
+ }
736
+ /**
737
+ * Find patterns shared between two projects
738
+ */
739
+ async getSharedPatterns(slugA, slugB) {
740
+ return this.request(
741
+ "GET",
742
+ `/v1/projects/shared-patterns?a=${encodeURIComponent(slugA)}&b=${encodeURIComponent(slugB)}`
743
+ );
744
+ }
745
+ /**
746
+ * Get full project context (hot memories, decisions, patterns, formatted text)
747
+ */
748
+ async getProjectContext(slug) {
749
+ return this.request(
750
+ "GET",
751
+ `/v1/projects/${encodeURIComponent(slug)}/context`
752
+ );
753
+ }
754
+ /**
755
+ * Promote/demote a memory by updating its importance and tier
756
+ */
757
+ async promoteMemory(memoryId, importance, tier) {
758
+ const body = { importance };
759
+ if (tier) {
760
+ body.tier = tier;
761
+ }
762
+ return this.request(
763
+ "PUT",
764
+ `/v1/memories/${encodeURIComponent(memoryId)}/importance`,
765
+ body
766
+ );
767
+ }
768
+ /**
769
+ * Health check - verify API connectivity
770
+ */
771
+ async healthCheck() {
772
+ try {
773
+ await this.request("GET", "/v1/health");
774
+ return {
775
+ status: "healthy",
776
+ message: "API connection successful"
777
+ };
778
+ } catch (error) {
779
+ const errorMsg = error instanceof Error ? error.message : "Unknown error";
780
+ return {
781
+ status: "unhealthy",
782
+ message: `API connection failed: ${errorMsg}`
783
+ };
784
+ }
785
+ }
786
+ };
787
+
788
+ export {
789
+ getEnabledTools,
790
+ loadConfig,
791
+ getAgentId,
792
+ initLogger,
793
+ getLogger,
794
+ MemoryRelayClient
795
+ };
796
+ //# sourceMappingURL=chunk-P6TZEH6O.js.map