@prmichaelsen/remember-mcp 3.19.3 → 4.0.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 (140) hide show
  1. package/AGENT.md +10 -3
  2. package/CHANGELOG.md +38 -0
  3. package/README.md +1 -1
  4. package/agent/commands/acp.artifact-glossary.md +530 -0
  5. package/agent/commands/acp.artifact-reference.md +591 -0
  6. package/agent/commands/acp.artifact-research.md +594 -0
  7. package/agent/commands/acp.audit.md +345 -0
  8. package/agent/commands/acp.clarification-address.md +185 -88
  9. package/agent/commands/acp.clarification-capture.md +44 -44
  10. package/agent/commands/acp.clarification-create.md +41 -42
  11. package/agent/commands/acp.command-create.md +49 -49
  12. package/agent/commands/acp.design-create.md +53 -35
  13. package/agent/commands/acp.design-reference.md +42 -42
  14. package/agent/commands/acp.handoff.md +35 -35
  15. package/agent/commands/acp.index.md +47 -47
  16. package/agent/commands/acp.init.md +105 -69
  17. package/agent/commands/acp.package-create.md +41 -41
  18. package/agent/commands/acp.package-info.md +40 -40
  19. package/agent/commands/acp.package-install.md +48 -48
  20. package/agent/commands/acp.package-list.md +40 -40
  21. package/agent/commands/acp.package-publish.md +62 -62
  22. package/agent/commands/acp.package-remove.md +41 -41
  23. package/agent/commands/acp.package-search.md +48 -48
  24. package/agent/commands/acp.package-update.md +50 -50
  25. package/agent/commands/acp.package-validate.md +52 -52
  26. package/agent/commands/acp.pattern-create.md +61 -43
  27. package/agent/commands/acp.plan.md +70 -47
  28. package/agent/commands/acp.proceed.md +188 -66
  29. package/agent/commands/acp.project-create.md +42 -42
  30. package/agent/commands/acp.project-info.md +46 -46
  31. package/agent/commands/acp.project-list.md +41 -41
  32. package/agent/commands/acp.project-remove.md +36 -36
  33. package/agent/commands/acp.project-set.md +33 -33
  34. package/agent/commands/acp.project-update.md +57 -57
  35. package/agent/commands/acp.projects-restore.md +37 -37
  36. package/agent/commands/acp.projects-sync.md +39 -39
  37. package/agent/commands/acp.report.md +50 -50
  38. package/agent/commands/acp.resume.md +36 -36
  39. package/agent/commands/acp.sessions.md +46 -46
  40. package/agent/commands/acp.status.md +43 -43
  41. package/agent/commands/acp.sync.md +109 -56
  42. package/agent/commands/acp.task-create.md +51 -49
  43. package/agent/commands/acp.update.md +66 -45
  44. package/agent/commands/acp.validate.md +110 -52
  45. package/agent/commands/acp.version-check-for-updates.md +40 -40
  46. package/agent/commands/acp.version-check.md +36 -36
  47. package/agent/commands/acp.version-update.md +43 -43
  48. package/agent/commands/command.template.md +40 -40
  49. package/agent/commands/git.commit.md +28 -28
  50. package/agent/commands/git.init.md +48 -48
  51. package/agent/design/design.template.md +9 -9
  52. package/agent/design/local.admin-debugging-tools.md +242 -0
  53. package/agent/design/requirements.template.md +8 -8
  54. package/agent/index/.gitkeep +0 -0
  55. package/agent/index/acp.core.yaml +137 -0
  56. package/agent/index/local.main.template.yaml +37 -0
  57. package/agent/index/local.main.yaml +48 -0
  58. package/agent/manifest.yaml +64 -0
  59. package/agent/milestones/milestone-1-{title}.template.md +8 -8
  60. package/agent/milestones/milestone-22-admin-debugging-tools.md +61 -0
  61. package/agent/milestones/milestone-23-trust-level-protection.md +122 -0
  62. package/agent/patterns/pattern.template.md +22 -22
  63. package/agent/progress.template.yaml +13 -3
  64. package/agent/progress.yaml +173 -3
  65. package/agent/schemas/package.schema.yaml +276 -0
  66. package/agent/scripts/acp.project-update.sh +5 -6
  67. package/agent/tasks/milestone-22-admin-debugging-tools/task-520-admin-gate-infrastructure.md +99 -0
  68. package/agent/tasks/milestone-22-admin-debugging-tools/task-521-schema-and-collection-tools.md +108 -0
  69. package/agent/tasks/milestone-22-admin-debugging-tools/task-522-memory-inspection-tools.md +120 -0
  70. package/agent/tasks/milestone-22-admin-debugging-tools/task-523-user-inspection-tools.md +126 -0
  71. package/agent/tasks/milestone-22-admin-debugging-tools/task-524-health-and-drift-tools.md +120 -0
  72. package/agent/tasks/milestone-23-trust-level-protection/task-525-remove-trust-from-create-update.md +69 -0
  73. package/agent/tasks/milestone-23-trust-level-protection/task-526-add-request-set-trust-level-tool.md +108 -0
  74. package/agent/tasks/milestone-23-trust-level-protection/task-527-update-confirm-deny-secret-token.md +60 -0
  75. package/agent/tasks/milestone-23-trust-level-protection/task-528-update-trust-scale-references.md +73 -0
  76. package/agent/tasks/milestone-23-trust-level-protection/task-529-version-bump-and-release.md +87 -0
  77. package/agent/tasks/task-1-{title}.template.md +18 -18
  78. package/dist/server-factory.js +779 -87
  79. package/dist/server.js +141 -41
  80. package/dist/services/trust-validator.d.ts +16 -14
  81. package/dist/tools/admin-collection-stats.d.ts +24 -0
  82. package/dist/tools/admin-detect-weaviate-drift.d.ts +26 -0
  83. package/dist/tools/admin-get-weaviate-schema.d.ts +24 -0
  84. package/dist/tools/admin-health-drift.spec.d.ts +5 -0
  85. package/dist/tools/admin-health.d.ts +15 -0
  86. package/dist/tools/admin-inspect-memory.d.ts +29 -0
  87. package/dist/tools/admin-inspect-user.d.ts +73 -0
  88. package/dist/tools/admin-inspect-user.spec.d.ts +5 -0
  89. package/dist/tools/admin-list-collections.d.ts +23 -0
  90. package/dist/tools/admin-memory-inspection.spec.d.ts +7 -0
  91. package/dist/tools/admin-schema-collection.spec.d.ts +8 -0
  92. package/dist/tools/admin-search-across-users.d.ts +42 -0
  93. package/dist/tools/confirm.d.ts +1 -0
  94. package/dist/tools/confirm.spec.d.ts +5 -0
  95. package/dist/tools/create-internal-memory.d.ts +0 -7
  96. package/dist/tools/create-memory.d.ts +0 -7
  97. package/dist/tools/deny.d.ts +1 -0
  98. package/dist/tools/deny.spec.d.ts +5 -0
  99. package/dist/tools/query-memory.d.ts +2 -0
  100. package/dist/tools/request-set-trust-level.d.ts +32 -0
  101. package/dist/tools/request-set-trust-level.spec.d.ts +2 -0
  102. package/dist/tools/search-memory.d.ts +2 -0
  103. package/dist/tools/update-internal-memory.d.ts +0 -6
  104. package/dist/tools/update-memory.d.ts +0 -7
  105. package/dist/utils/admin.d.ts +21 -0
  106. package/dist/utils/admin.spec.d.ts +2 -0
  107. package/package.json +2 -2
  108. package/src/server-factory.ts +137 -42
  109. package/src/server.ts +6 -0
  110. package/src/services/trust-validator.spec.ts +57 -51
  111. package/src/services/trust-validator.ts +28 -26
  112. package/src/tools/admin-collection-stats.ts +67 -0
  113. package/src/tools/admin-detect-weaviate-drift.ts +110 -0
  114. package/src/tools/admin-get-weaviate-schema.ts +68 -0
  115. package/src/tools/admin-health-drift.spec.ts +193 -0
  116. package/src/tools/admin-health.ts +88 -0
  117. package/src/tools/admin-inspect-memory.ts +86 -0
  118. package/src/tools/admin-inspect-user.spec.ts +130 -0
  119. package/src/tools/admin-inspect-user.ts +148 -0
  120. package/src/tools/admin-list-collections.ts +73 -0
  121. package/src/tools/admin-memory-inspection.spec.ts +206 -0
  122. package/src/tools/admin-schema-collection.spec.ts +167 -0
  123. package/src/tools/admin-search-across-users.ts +104 -0
  124. package/src/tools/confirm.spec.ts +108 -0
  125. package/src/tools/confirm.ts +24 -1
  126. package/src/tools/create-internal-memory.ts +0 -3
  127. package/src/tools/create-memory.spec.ts +6 -2
  128. package/src/tools/create-memory.ts +1 -9
  129. package/src/tools/deny.spec.ts +59 -0
  130. package/src/tools/deny.ts +6 -1
  131. package/src/tools/ghost-config.ts +19 -19
  132. package/src/tools/query-memory.ts +4 -2
  133. package/src/tools/request-set-trust-level.spec.ts +87 -0
  134. package/src/tools/request-set-trust-level.ts +107 -0
  135. package/src/tools/search-memory.ts +4 -2
  136. package/src/tools/update-internal-memory.ts +0 -3
  137. package/src/tools/update-memory.ts +0 -8
  138. package/src/types/memory.ts +1 -1
  139. package/src/utils/admin.spec.ts +70 -0
  140. package/src/utils/admin.ts +27 -0
@@ -0,0 +1,86 @@
1
+ /**
2
+ * remember_admin_inspect_memory tool
3
+ * Fetches a raw memory object by UUID using the Firestore index for collection resolution.
4
+ */
5
+
6
+ import { handleToolError } from '../utils/error-handler.js';
7
+ import { createDebugLogger } from '../utils/debug.js';
8
+ import type { AuthContext } from '../types/auth.js';
9
+ import { isAdmin, adminPermissionError } from '../utils/admin.js';
10
+ import { getWeaviateClient } from '../weaviate/client.js';
11
+ import { MemoryIndexService, createLogger } from '@prmichaelsen/remember-core';
12
+
13
+ const indexService = new MemoryIndexService(createLogger('info'));
14
+
15
+ export const adminInspectMemoryTool = {
16
+ name: 'remember_admin_inspect_memory',
17
+ description: `[Admin] Fetch a raw memory object by UUID — all fields including internal metadata.
18
+
19
+ Uses the Firestore index to resolve which collection the memory lives in.
20
+ Optionally includes the vector embedding.
21
+ Requires admin access (ADMIN_USER_IDS).`,
22
+ inputSchema: {
23
+ type: 'object',
24
+ properties: {
25
+ memory_id: {
26
+ type: 'string',
27
+ description: 'Memory UUID',
28
+ },
29
+ include_vector: {
30
+ type: 'boolean',
31
+ description: 'Include the vector embedding in the response. Default: false',
32
+ },
33
+ },
34
+ required: ['memory_id'],
35
+ },
36
+ };
37
+
38
+ export interface AdminInspectMemoryArgs {
39
+ memory_id: string;
40
+ include_vector?: boolean;
41
+ }
42
+
43
+ export async function handleAdminInspectMemory(
44
+ args: AdminInspectMemoryArgs,
45
+ userId: string,
46
+ _authContext?: AuthContext
47
+ ): Promise<string> {
48
+ const debug = createDebugLogger({ tool: 'remember_admin_inspect_memory', userId, operation: 'inspect memory' });
49
+ try {
50
+ if (!isAdmin(userId)) {
51
+ return JSON.stringify(adminPermissionError());
52
+ }
53
+
54
+ debug.info('Tool invoked', { memory_id: args.memory_id, include_vector: args.include_vector });
55
+
56
+ // Resolve collection from index
57
+ const collectionName = await indexService.lookup(args.memory_id);
58
+ if (!collectionName) {
59
+ return JSON.stringify({ error: `Memory not found in index: ${args.memory_id}` });
60
+ }
61
+
62
+ // Fetch raw object from Weaviate
63
+ const client = getWeaviateClient();
64
+ const collection = client.collections.get(collectionName);
65
+ const result = await collection.query.fetchObjectById(args.memory_id, {
66
+ includeVector: args.include_vector ?? false,
67
+ });
68
+
69
+ if (!result) {
70
+ return JSON.stringify({
71
+ error: `Memory indexed in ${collectionName} but not found in Weaviate: ${args.memory_id}`,
72
+ collection_name: collectionName,
73
+ });
74
+ }
75
+
76
+ return JSON.stringify({
77
+ id: result.uuid,
78
+ collection_name: collectionName,
79
+ properties: result.properties,
80
+ vectors: args.include_vector ? result.vectors : undefined,
81
+ metadata: result.metadata,
82
+ }, null, 2);
83
+ } catch (error) {
84
+ return handleToolError(error, { toolName: 'remember_admin_inspect_memory', userId, operation: 'inspect memory' });
85
+ }
86
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Tests for admin user inspection tools.
3
+ */
4
+
5
+ const mockPreferencesGet = jest.fn();
6
+ const mockGetGhostConfig = jest.fn();
7
+ const mockQueryDocuments = jest.fn();
8
+
9
+ jest.mock('../core-services.js', () => ({
10
+ createCoreServices: () => ({
11
+ preferences: { getPreferences: mockPreferencesGet },
12
+ }),
13
+ }));
14
+
15
+ jest.mock('../services/ghost-config.service.js', () => ({
16
+ getGhostConfig: mockGetGhostConfig,
17
+ }));
18
+
19
+ jest.mock('../firestore/init.js', () => ({
20
+ queryDocuments: mockQueryDocuments,
21
+ }));
22
+
23
+ import {
24
+ handleAdminInspectUserPreferences,
25
+ handleAdminInspectUserGhostConfigs,
26
+ handleAdminInspectUserEscalationRecords,
27
+ handleAdminInspectUserApiTokens,
28
+ } from './admin-inspect-user.js';
29
+
30
+ describe('admin user inspection tools', () => {
31
+ const originalEnv = process.env.ADMIN_USER_IDS;
32
+
33
+ beforeEach(() => {
34
+ process.env.ADMIN_USER_IDS = 'admin_user';
35
+ jest.clearAllMocks();
36
+ });
37
+
38
+ afterEach(() => {
39
+ if (originalEnv !== undefined) {
40
+ process.env.ADMIN_USER_IDS = originalEnv;
41
+ } else {
42
+ delete process.env.ADMIN_USER_IDS;
43
+ }
44
+ });
45
+
46
+ describe('remember_admin_inspect_user_preferences', () => {
47
+ it('returns preferences for admin user', async () => {
48
+ mockPreferencesGet.mockResolvedValue({ templates: { auto_suggest: true } });
49
+ const result = await handleAdminInspectUserPreferences({ user_id: 'target' }, 'admin_user');
50
+ const parsed = JSON.parse(result);
51
+ expect(parsed.user_id).toBe('target');
52
+ expect(parsed.preferences.templates.auto_suggest).toBe(true);
53
+ });
54
+
55
+ it('returns permission error for non-admin', async () => {
56
+ const result = await handleAdminInspectUserPreferences({ user_id: 'target' }, 'regular_user');
57
+ const parsed = JSON.parse(result);
58
+ expect(parsed.isError).toBe(true);
59
+ });
60
+ });
61
+
62
+ describe('remember_admin_inspect_user_ghost_configs', () => {
63
+ it('returns ghost config for admin user', async () => {
64
+ mockGetGhostConfig.mockResolvedValue({ enabled: true, trust_mode: 'query' });
65
+ const result = await handleAdminInspectUserGhostConfigs({ user_id: 'target' }, 'admin_user');
66
+ const parsed = JSON.parse(result);
67
+ expect(parsed.user_id).toBe('target');
68
+ expect(parsed.ghost_config.enabled).toBe(true);
69
+ });
70
+
71
+ it('returns permission error for non-admin', async () => {
72
+ const result = await handleAdminInspectUserGhostConfigs({ user_id: 'target' }, 'regular_user');
73
+ const parsed = JSON.parse(result);
74
+ expect(parsed.isError).toBe(true);
75
+ });
76
+ });
77
+
78
+ describe('remember_admin_inspect_user_escalation_records', () => {
79
+ it('returns escalation records for admin user', async () => {
80
+ mockQueryDocuments.mockResolvedValue([
81
+ { id: 'esc-1', data: { accessor_id: 'user2', attempts: 3 } },
82
+ ]);
83
+ const result = await handleAdminInspectUserEscalationRecords({ user_id: 'target' }, 'admin_user');
84
+ const parsed = JSON.parse(result);
85
+ expect(parsed.user_id).toBe('target');
86
+ expect(parsed.escalation_records).toHaveLength(1);
87
+ expect(parsed.escalation_records[0].accessor_id).toBe('user2');
88
+ });
89
+
90
+ it('returns empty array for user with no records', async () => {
91
+ mockQueryDocuments.mockResolvedValue([]);
92
+ const result = await handleAdminInspectUserEscalationRecords({ user_id: 'target' }, 'admin_user');
93
+ const parsed = JSON.parse(result);
94
+ expect(parsed.escalation_records).toEqual([]);
95
+ });
96
+
97
+ it('returns permission error for non-admin', async () => {
98
+ const result = await handleAdminInspectUserEscalationRecords({ user_id: 'target' }, 'regular_user');
99
+ const parsed = JSON.parse(result);
100
+ expect(parsed.isError).toBe(true);
101
+ });
102
+ });
103
+
104
+ describe('remember_admin_inspect_user_api_tokens', () => {
105
+ it('returns token metadata without hashes', async () => {
106
+ mockQueryDocuments.mockResolvedValue([
107
+ { id: 'tok-1', data: { name: 'MacBook', token_hash: 'secret_hash', created_at: '2026-01-01' } },
108
+ ]);
109
+ const result = await handleAdminInspectUserApiTokens({ user_id: 'target' }, 'admin_user');
110
+ const parsed = JSON.parse(result);
111
+ expect(parsed.user_id).toBe('target');
112
+ expect(parsed.api_tokens).toHaveLength(1);
113
+ expect(parsed.api_tokens[0].name).toBe('MacBook');
114
+ expect(parsed.api_tokens[0].token_hash).toBeUndefined();
115
+ });
116
+
117
+ it('returns empty array for user with no tokens', async () => {
118
+ mockQueryDocuments.mockResolvedValue([]);
119
+ const result = await handleAdminInspectUserApiTokens({ user_id: 'target' }, 'admin_user');
120
+ const parsed = JSON.parse(result);
121
+ expect(parsed.api_tokens).toEqual([]);
122
+ });
123
+
124
+ it('returns permission error for non-admin', async () => {
125
+ const result = await handleAdminInspectUserApiTokens({ user_id: 'target' }, 'regular_user');
126
+ const parsed = JSON.parse(result);
127
+ expect(parsed.isError).toBe(true);
128
+ });
129
+ });
130
+ });
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Admin user inspection tools — granular Firestore data access per data type.
3
+ *
4
+ * - remember_admin_inspect_user_preferences
5
+ * - remember_admin_inspect_user_ghost_configs
6
+ * - remember_admin_inspect_user_escalation_records
7
+ * - remember_admin_inspect_user_api_tokens
8
+ */
9
+
10
+ import { handleToolError } from '../utils/error-handler.js';
11
+ import { createDebugLogger } from '../utils/debug.js';
12
+ import type { AuthContext } from '../types/auth.js';
13
+ import { isAdmin, adminPermissionError } from '../utils/admin.js';
14
+ import { createCoreServices } from '../core-services.js';
15
+ import { getGhostConfig } from '../services/ghost-config.service.js';
16
+ import { queryDocuments } from '../firestore/init.js';
17
+ import { BASE } from '../firestore/paths.js';
18
+
19
+ // ── Tool Definitions ────────────────────────────────────────────────────
20
+
21
+ const userIdSchema = {
22
+ type: 'object' as const,
23
+ properties: {
24
+ user_id: { type: 'string' as const, description: 'User ID to inspect' },
25
+ },
26
+ required: ['user_id'] as const,
27
+ };
28
+
29
+ export const adminInspectUserPreferencesTool = {
30
+ name: 'remember_admin_inspect_user_preferences',
31
+ description: `[Admin] Inspect a user's preferences from Firestore. Requires admin access.`,
32
+ inputSchema: userIdSchema,
33
+ };
34
+
35
+ export const adminInspectUserGhostConfigsTool = {
36
+ name: 'remember_admin_inspect_user_ghost_configs',
37
+ description: `[Admin] Inspect a user's ghost configurations from Firestore. Requires admin access.`,
38
+ inputSchema: userIdSchema,
39
+ };
40
+
41
+ export const adminInspectUserEscalationRecordsTool = {
42
+ name: 'remember_admin_inspect_user_escalation_records',
43
+ description: `[Admin] Inspect a user's trust escalation records from Firestore. Requires admin access.`,
44
+ inputSchema: userIdSchema,
45
+ };
46
+
47
+ export const adminInspectUserApiTokensTool = {
48
+ name: 'remember_admin_inspect_user_api_tokens',
49
+ description: `[Admin] Inspect a user's API token metadata from Firestore (no hashes). Requires admin access.`,
50
+ inputSchema: userIdSchema,
51
+ };
52
+
53
+ // ── Shared Types ────────────────────────────────────────────────────────
54
+
55
+ export interface AdminInspectUserArgs {
56
+ user_id: string;
57
+ }
58
+
59
+ // ── Handlers ────────────────────────────────────────────────────────────
60
+
61
+ export async function handleAdminInspectUserPreferences(
62
+ args: AdminInspectUserArgs,
63
+ userId: string,
64
+ _authContext?: AuthContext
65
+ ): Promise<string> {
66
+ const debug = createDebugLogger({ tool: 'remember_admin_inspect_user_preferences', userId, operation: 'inspect preferences' });
67
+ try {
68
+ if (!isAdmin(userId)) return JSON.stringify(adminPermissionError());
69
+ debug.info('Tool invoked', { target_user_id: args.user_id });
70
+
71
+ const services = createCoreServices(args.user_id);
72
+ const prefs = await services.preferences.getPreferences(args.user_id);
73
+
74
+ return JSON.stringify({ user_id: args.user_id, preferences: prefs }, null, 2);
75
+ } catch (error) {
76
+ return handleToolError(error, { toolName: 'remember_admin_inspect_user_preferences', userId });
77
+ }
78
+ }
79
+
80
+ export async function handleAdminInspectUserGhostConfigs(
81
+ args: AdminInspectUserArgs,
82
+ userId: string,
83
+ _authContext?: AuthContext
84
+ ): Promise<string> {
85
+ const debug = createDebugLogger({ tool: 'remember_admin_inspect_user_ghost_configs', userId, operation: 'inspect ghost configs' });
86
+ try {
87
+ if (!isAdmin(userId)) return JSON.stringify(adminPermissionError());
88
+ debug.info('Tool invoked', { target_user_id: args.user_id });
89
+
90
+ const config = await getGhostConfig(args.user_id);
91
+
92
+ return JSON.stringify({ user_id: args.user_id, ghost_config: config }, null, 2);
93
+ } catch (error) {
94
+ return handleToolError(error, { toolName: 'remember_admin_inspect_user_ghost_configs', userId });
95
+ }
96
+ }
97
+
98
+ export async function handleAdminInspectUserEscalationRecords(
99
+ args: AdminInspectUserArgs,
100
+ userId: string,
101
+ _authContext?: AuthContext
102
+ ): Promise<string> {
103
+ const debug = createDebugLogger({ tool: 'remember_admin_inspect_user_escalation_records', userId, operation: 'inspect escalation records' });
104
+ try {
105
+ if (!isAdmin(userId)) return JSON.stringify(adminPermissionError());
106
+ debug.info('Tool invoked', { target_user_id: args.user_id });
107
+
108
+ // Escalation records stored at: {BASE}.users/{user_id}/escalations
109
+ const collectionPath = `${BASE}.users/${args.user_id}/escalations`;
110
+ const records = await queryDocuments(collectionPath);
111
+
112
+ return JSON.stringify({
113
+ user_id: args.user_id,
114
+ escalation_records: records.map(r => ({ id: r.id, ...r.data })),
115
+ }, null, 2);
116
+ } catch (error) {
117
+ return handleToolError(error, { toolName: 'remember_admin_inspect_user_escalation_records', userId });
118
+ }
119
+ }
120
+
121
+ export async function handleAdminInspectUserApiTokens(
122
+ args: AdminInspectUserArgs,
123
+ userId: string,
124
+ _authContext?: AuthContext
125
+ ): Promise<string> {
126
+ const debug = createDebugLogger({ tool: 'remember_admin_inspect_user_api_tokens', userId, operation: 'inspect api tokens' });
127
+ try {
128
+ if (!isAdmin(userId)) return JSON.stringify(adminPermissionError());
129
+ debug.info('Tool invoked', { target_user_id: args.user_id });
130
+
131
+ // API tokens stored at: {BASE}.users/{user_id}/api_tokens
132
+ const collectionPath = `${BASE}.users/${args.user_id}/api_tokens`;
133
+ const tokens = await queryDocuments(collectionPath);
134
+
135
+ // Strip token hashes — only return metadata
136
+ const sanitized = tokens.map((token: any) => {
137
+ const { token_hash, ...metadata } = token.data;
138
+ return { id: token.id, ...metadata };
139
+ });
140
+
141
+ return JSON.stringify({
142
+ user_id: args.user_id,
143
+ api_tokens: sanitized,
144
+ }, null, 2);
145
+ } catch (error) {
146
+ return handleToolError(error, { toolName: 'remember_admin_inspect_user_api_tokens', userId });
147
+ }
148
+ }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * remember_admin_list_collections tool
3
+ * Lists all Weaviate collections with type categorization.
4
+ */
5
+
6
+ import { handleToolError } from '../utils/error-handler.js';
7
+ import { createDebugLogger } from '../utils/debug.js';
8
+ import type { AuthContext } from '../types/auth.js';
9
+ import { isAdmin, adminPermissionError } from '../utils/admin.js';
10
+ import { getWeaviateClient } from '../weaviate/client.js';
11
+
12
+ export const adminListCollectionsTool = {
13
+ name: 'remember_admin_list_collections',
14
+ description: `[Admin] List all Weaviate collections with type categorization (user, space, group).
15
+
16
+ Optionally filter by prefix (e.g. "Memory_users_", "Memory_spaces_").
17
+ Requires admin access (ADMIN_USER_IDS).`,
18
+ inputSchema: {
19
+ type: 'object',
20
+ properties: {
21
+ filter: {
22
+ type: 'string',
23
+ description: 'Optional prefix filter (e.g. "Memory_users_", "Memory_spaces_")',
24
+ },
25
+ },
26
+ },
27
+ };
28
+
29
+ export interface AdminListCollectionsArgs {
30
+ filter?: string;
31
+ }
32
+
33
+ function categorizeCollection(name: string): string {
34
+ if (name.startsWith('Memory_users_')) return 'user';
35
+ if (name.startsWith('Memory_spaces_')) return 'space';
36
+ if (name.startsWith('Memory_groups_')) return 'group';
37
+ if (name.startsWith('Memory_friends_')) return 'friends';
38
+ return 'other';
39
+ }
40
+
41
+ export async function handleAdminListCollections(
42
+ args: AdminListCollectionsArgs,
43
+ userId: string,
44
+ _authContext?: AuthContext
45
+ ): Promise<string> {
46
+ const debug = createDebugLogger({ tool: 'remember_admin_list_collections', userId, operation: 'list collections' });
47
+ try {
48
+ if (!isAdmin(userId)) {
49
+ return JSON.stringify(adminPermissionError());
50
+ }
51
+
52
+ debug.info('Tool invoked', { filter: args.filter });
53
+
54
+ const client = getWeaviateClient();
55
+ const allCollections = await client.collections.listAll();
56
+
57
+ let collections = allCollections.map((c: any) => ({
58
+ name: c.name,
59
+ type: categorizeCollection(c.name),
60
+ }));
61
+
62
+ if (args.filter) {
63
+ collections = collections.filter((c: any) => c.name.startsWith(args.filter!));
64
+ }
65
+
66
+ return JSON.stringify({
67
+ total: collections.length,
68
+ collections,
69
+ }, null, 2);
70
+ } catch (error) {
71
+ return handleToolError(error, { toolName: 'remember_admin_list_collections', userId, operation: 'list collections' });
72
+ }
73
+ }
@@ -0,0 +1,206 @@
1
+ /**
2
+ * Tests for admin memory inspection tools:
3
+ * - remember_admin_inspect_memory
4
+ * - remember_admin_search_across_users
5
+ */
6
+
7
+ // Must declare mocks before imports due to jest.mock hoisting
8
+ const mockLookup = jest.fn();
9
+ const mockFetchObjectById = jest.fn();
10
+ const mockSearch = jest.fn();
11
+
12
+ jest.mock('@prmichaelsen/remember-core', () => ({
13
+ MemoryIndexService: jest.fn().mockImplementation(() => ({
14
+ lookup: mockLookup,
15
+ })),
16
+ createLogger: () => ({ info: jest.fn(), debug: jest.fn(), warn: jest.fn(), error: jest.fn() }),
17
+ }));
18
+
19
+ jest.mock('../weaviate/client.js', () => ({
20
+ getWeaviateClient: () => ({
21
+ collections: {
22
+ get: () => ({
23
+ query: { fetchObjectById: mockFetchObjectById },
24
+ }),
25
+ },
26
+ }),
27
+ }));
28
+
29
+ jest.mock('../core-services.js', () => ({
30
+ createCoreServices: () => ({
31
+ memory: { search: mockSearch },
32
+ }),
33
+ }));
34
+
35
+ import { handleAdminInspectMemory } from './admin-inspect-memory.js';
36
+ import { handleAdminSearchAcrossUsers } from './admin-search-across-users.js';
37
+
38
+ describe('remember_admin_inspect_memory', () => {
39
+ const originalEnv = process.env.ADMIN_USER_IDS;
40
+
41
+ beforeEach(() => {
42
+ process.env.ADMIN_USER_IDS = 'admin_user';
43
+ jest.clearAllMocks();
44
+ });
45
+
46
+ afterEach(() => {
47
+ if (originalEnv !== undefined) {
48
+ process.env.ADMIN_USER_IDS = originalEnv;
49
+ } else {
50
+ delete process.env.ADMIN_USER_IDS;
51
+ }
52
+ });
53
+
54
+ it('returns raw memory object for admin user', async () => {
55
+ mockLookup.mockResolvedValue('Memory_users_abc123');
56
+ mockFetchObjectById.mockResolvedValue({
57
+ uuid: 'mem-uuid-1',
58
+ properties: { content: 'test memory', weight: 0.8 },
59
+ metadata: { creationTime: '2026-01-01' },
60
+ });
61
+
62
+ const result = await handleAdminInspectMemory(
63
+ { memory_id: 'mem-uuid-1' },
64
+ 'admin_user'
65
+ );
66
+ const parsed = JSON.parse(result);
67
+ expect(parsed.id).toBe('mem-uuid-1');
68
+ expect(parsed.collection_name).toBe('Memory_users_abc123');
69
+ expect(parsed.properties.content).toBe('test memory');
70
+ expect(parsed.vectors).toBeUndefined();
71
+ });
72
+
73
+ it('includes vector when requested', async () => {
74
+ mockLookup.mockResolvedValue('Memory_users_abc123');
75
+ mockFetchObjectById.mockResolvedValue({
76
+ uuid: 'mem-uuid-1',
77
+ properties: { content: 'test' },
78
+ vectors: { default: [0.1, 0.2, 0.3] },
79
+ metadata: {},
80
+ });
81
+
82
+ const result = await handleAdminInspectMemory(
83
+ { memory_id: 'mem-uuid-1', include_vector: true },
84
+ 'admin_user'
85
+ );
86
+ const parsed = JSON.parse(result);
87
+ expect(parsed.vectors).toEqual({ default: [0.1, 0.2, 0.3] });
88
+ });
89
+
90
+ it('returns error when memory not in index', async () => {
91
+ mockLookup.mockResolvedValue(null);
92
+
93
+ const result = await handleAdminInspectMemory(
94
+ { memory_id: 'nonexistent' },
95
+ 'admin_user'
96
+ );
97
+ const parsed = JSON.parse(result);
98
+ expect(parsed.error).toContain('not found in index');
99
+ });
100
+
101
+ it('returns error when memory in index but not in Weaviate', async () => {
102
+ mockLookup.mockResolvedValue('Memory_users_abc123');
103
+ mockFetchObjectById.mockResolvedValue(null);
104
+
105
+ const result = await handleAdminInspectMemory(
106
+ { memory_id: 'orphaned-id' },
107
+ 'admin_user'
108
+ );
109
+ const parsed = JSON.parse(result);
110
+ expect(parsed.error).toContain('not found in Weaviate');
111
+ expect(parsed.collection_name).toBe('Memory_users_abc123');
112
+ });
113
+
114
+ it('returns permission error for non-admin user', async () => {
115
+ const result = await handleAdminInspectMemory(
116
+ { memory_id: 'mem-uuid-1' },
117
+ 'regular_user'
118
+ );
119
+ const parsed = JSON.parse(result);
120
+ expect(parsed.isError).toBe(true);
121
+ expect(parsed.content[0].text).toContain('Permission denied');
122
+ });
123
+ });
124
+
125
+ describe('remember_admin_search_across_users', () => {
126
+ const originalEnv = process.env.ADMIN_USER_IDS;
127
+
128
+ beforeEach(() => {
129
+ process.env.ADMIN_USER_IDS = 'admin_user';
130
+ jest.clearAllMocks();
131
+ });
132
+
133
+ afterEach(() => {
134
+ if (originalEnv !== undefined) {
135
+ process.env.ADMIN_USER_IDS = originalEnv;
136
+ } else {
137
+ delete process.env.ADMIN_USER_IDS;
138
+ }
139
+ });
140
+
141
+ it('searches across multiple users and includes user_id', async () => {
142
+ mockSearch.mockResolvedValue({
143
+ memories: [
144
+ { id: 'mem1', content: 'memory from user' },
145
+ ],
146
+ });
147
+
148
+ const result = await handleAdminSearchAcrossUsers(
149
+ { user_ids: ['user1', 'user2'], query: 'test' },
150
+ 'admin_user'
151
+ );
152
+ const parsed = JSON.parse(result);
153
+ expect(parsed.total).toBe(2);
154
+ expect(parsed.results[0].user_id).toBe('user1');
155
+ expect(parsed.results[1].user_id).toBe('user2');
156
+ });
157
+
158
+ it('applies limit across merged results', async () => {
159
+ mockSearch.mockResolvedValue({
160
+ memories: [
161
+ { id: 'mem1', content: 'a' },
162
+ { id: 'mem2', content: 'b' },
163
+ ],
164
+ });
165
+
166
+ const result = await handleAdminSearchAcrossUsers(
167
+ { user_ids: ['user1', 'user2'], query: 'test', limit: 3 },
168
+ 'admin_user'
169
+ );
170
+ const parsed = JSON.parse(result);
171
+ expect(parsed.total).toBe(3);
172
+ });
173
+
174
+ it('returns validation error for empty user_ids', async () => {
175
+ const result = await handleAdminSearchAcrossUsers(
176
+ { user_ids: [], query: 'test' },
177
+ 'admin_user'
178
+ );
179
+ const parsed = JSON.parse(result);
180
+ expect(parsed.error).toContain('user_ids');
181
+ });
182
+
183
+ it('warns on failed user search', async () => {
184
+ mockSearch
185
+ .mockResolvedValueOnce({ memories: [{ id: 'mem1', content: 'ok' }] })
186
+ .mockRejectedValueOnce(new Error('Collection not found'));
187
+
188
+ const result = await handleAdminSearchAcrossUsers(
189
+ { user_ids: ['user1', 'bad_user'], query: 'test' },
190
+ 'admin_user'
191
+ );
192
+ const parsed = JSON.parse(result);
193
+ expect(parsed.total).toBe(1);
194
+ expect(parsed.warnings).toHaveLength(1);
195
+ expect(parsed.warnings[0]).toContain('bad_user');
196
+ });
197
+
198
+ it('returns permission error for non-admin user', async () => {
199
+ const result = await handleAdminSearchAcrossUsers(
200
+ { user_ids: ['user1'], query: 'test' },
201
+ 'regular_user'
202
+ );
203
+ const parsed = JSON.parse(result);
204
+ expect(parsed.isError).toBe(true);
205
+ });
206
+ });