@hailer/mcp 0.1.8 → 0.1.9

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 (135) hide show
  1. package/.claude/agents/agent-dmitri-activity-crud.md +3 -1
  2. package/.claude/agents/agent-giuseppe-app-builder.md +11 -12
  3. package/.claude/agents/agent-kenji-data-reader.md +5 -3
  4. package/.claude/skills/hailer-app-builder/SKILL.md +506 -0
  5. package/.claude/skills/publish-hailer-app/SKILL.md +169 -0
  6. package/.claude/skills/tool-parameter-usage/SKILL.md +112 -0
  7. package/CLAUDE.md +6 -2
  8. package/REFACTOR_STATUS.md +127 -0
  9. package/dist/cli.js +0 -0
  10. package/dist/client/agents/base.d.ts +202 -0
  11. package/dist/client/agents/base.js +737 -0
  12. package/dist/client/agents/definitions.d.ts +53 -0
  13. package/dist/client/agents/definitions.js +178 -0
  14. package/dist/client/agents/orchestrator.d.ts +119 -0
  15. package/dist/client/agents/orchestrator.js +760 -0
  16. package/dist/client/agents/specialist.d.ts +86 -0
  17. package/dist/client/agents/specialist.js +340 -0
  18. package/dist/client/bot-manager.d.ts +44 -0
  19. package/dist/client/bot-manager.js +173 -0
  20. package/dist/client/chat-agent-daemon.d.ts +464 -0
  21. package/dist/client/chat-agent-daemon.js +1774 -0
  22. package/dist/client/daemon-factory.d.ts +106 -0
  23. package/dist/client/daemon-factory.js +301 -0
  24. package/dist/client/factory.d.ts +107 -0
  25. package/dist/client/factory.js +304 -0
  26. package/dist/client/index.d.ts +17 -0
  27. package/dist/client/index.js +38 -0
  28. package/dist/client/multi-bot-manager.d.ts +18 -0
  29. package/dist/client/multi-bot-manager.js +88 -1
  30. package/dist/client/orchestrator-daemon.d.ts +87 -0
  31. package/dist/client/orchestrator-daemon.js +444 -0
  32. package/dist/client/services/agent-registry.d.ts +108 -0
  33. package/dist/client/services/agent-registry.js +630 -0
  34. package/dist/client/services/conversation-manager.d.ts +50 -0
  35. package/dist/client/services/conversation-manager.js +136 -0
  36. package/dist/client/services/mcp-client.d.ts +48 -0
  37. package/dist/client/services/mcp-client.js +105 -0
  38. package/dist/client/services/message-classifier.d.ts +37 -0
  39. package/dist/client/services/message-classifier.js +187 -0
  40. package/dist/client/services/message-formatter.d.ts +84 -0
  41. package/dist/client/services/message-formatter.js +353 -0
  42. package/dist/client/services/session-logger.d.ts +106 -0
  43. package/dist/client/services/session-logger.js +446 -0
  44. package/dist/client/services/tool-executor.d.ts +41 -0
  45. package/dist/client/services/tool-executor.js +169 -0
  46. package/dist/client/services/workspace-schema-cache.d.ts +149 -0
  47. package/dist/client/services/workspace-schema-cache.js +732 -0
  48. package/dist/client/specialist-daemon.d.ts +77 -0
  49. package/dist/client/specialist-daemon.js +197 -0
  50. package/dist/client/specialists.d.ts +53 -0
  51. package/dist/client/specialists.js +178 -0
  52. package/dist/client/tool-schema-loader.d.ts +4 -3
  53. package/dist/client/tool-schema-loader.js +54 -8
  54. package/dist/client/types.d.ts +283 -55
  55. package/dist/client/types.js +113 -2
  56. package/dist/config.d.ts +1 -1
  57. package/dist/config.js +1 -1
  58. package/dist/core.d.ts +10 -2
  59. package/dist/core.js +43 -27
  60. package/dist/lib/logger.js +15 -3
  61. package/dist/mcp/UserContextCache.js +2 -2
  62. package/dist/mcp/hailer-clients.js +5 -5
  63. package/dist/mcp/signal-handler.js +27 -5
  64. package/dist/mcp/tools/activity.js +137 -65
  65. package/dist/mcp/tools/app-core.js +4 -140
  66. package/dist/mcp/tools/app-marketplace.js +15 -260
  67. package/dist/mcp/tools/app-member.js +2 -73
  68. package/dist/mcp/tools/app-scaffold.js +146 -87
  69. package/dist/mcp/tools/discussion.js +348 -73
  70. package/dist/mcp/tools/insight.js +74 -190
  71. package/dist/mcp/tools/workflow.js +20 -94
  72. package/dist/mcp/utils/hailer-api-client.d.ts +4 -2
  73. package/dist/mcp/utils/hailer-api-client.js +24 -10
  74. package/dist/mcp-server.d.ts +4 -0
  75. package/dist/mcp-server.js +24 -4
  76. package/dist/routes/agents.d.ts +44 -0
  77. package/dist/routes/agents.js +311 -0
  78. package/dist/services/agent-credential-store.d.ts +73 -0
  79. package/dist/services/agent-credential-store.js +212 -0
  80. package/lineup-manager/dist/assets/index-8ce6041d.css +1 -0
  81. package/lineup-manager/dist/assets/index-e168f265.js +600 -0
  82. package/lineup-manager/dist/index.html +15 -0
  83. package/lineup-manager/dist/manifest.json +17 -0
  84. package/lineup-manager/dist/vite.svg +1 -0
  85. package/package.json +1 -1
  86. package/dist/client/adaptive-documentation-bot.d.ts +0 -106
  87. package/dist/client/adaptive-documentation-bot.js +0 -464
  88. package/dist/client/adaptive-documentation-types.d.ts +0 -66
  89. package/dist/client/adaptive-documentation-types.js +0 -9
  90. package/dist/client/agent-activity-bot.d.ts +0 -51
  91. package/dist/client/agent-activity-bot.js +0 -166
  92. package/dist/client/agent-tracker.d.ts +0 -499
  93. package/dist/client/agent-tracker.js +0 -659
  94. package/dist/client/description-updater.d.ts +0 -56
  95. package/dist/client/description-updater.js +0 -259
  96. package/dist/client/log-parser.d.ts +0 -72
  97. package/dist/client/log-parser.js +0 -387
  98. package/dist/client/mcp-assistant.d.ts +0 -21
  99. package/dist/client/mcp-assistant.js +0 -58
  100. package/dist/client/mcp-client.d.ts +0 -50
  101. package/dist/client/mcp-client.js +0 -538
  102. package/dist/client/message-processor.d.ts +0 -35
  103. package/dist/client/message-processor.js +0 -357
  104. package/dist/client/providers/anthropic-provider.d.ts +0 -19
  105. package/dist/client/providers/anthropic-provider.js +0 -645
  106. package/dist/client/providers/assistant-provider.d.ts +0 -17
  107. package/dist/client/providers/assistant-provider.js +0 -51
  108. package/dist/client/providers/llm-provider.d.ts +0 -47
  109. package/dist/client/providers/llm-provider.js +0 -367
  110. package/dist/client/providers/openai-provider.d.ts +0 -23
  111. package/dist/client/providers/openai-provider.js +0 -630
  112. package/dist/client/simple-llm-caller.d.ts +0 -19
  113. package/dist/client/simple-llm-caller.js +0 -100
  114. package/dist/client/skill-generator.d.ts +0 -81
  115. package/dist/client/skill-generator.js +0 -386
  116. package/dist/client/test-adaptive-bot.d.ts +0 -9
  117. package/dist/client/test-adaptive-bot.js +0 -82
  118. package/dist/client/token-pricing.d.ts +0 -38
  119. package/dist/client/token-pricing.js +0 -127
  120. package/dist/client/token-tracker.d.ts +0 -232
  121. package/dist/client/token-tracker.js +0 -457
  122. package/dist/client/token-usage-bot.d.ts +0 -53
  123. package/dist/client/token-usage-bot.js +0 -153
  124. package/dist/client/tool-executor.d.ts +0 -69
  125. package/dist/client/tool-executor.js +0 -159
  126. package/dist/lib/materialize.d.ts +0 -3
  127. package/dist/lib/materialize.js +0 -101
  128. package/dist/lib/normalizedName.d.ts +0 -7
  129. package/dist/lib/normalizedName.js +0 -48
  130. package/dist/lib/terminal-prompt.d.ts +0 -9
  131. package/dist/lib/terminal-prompt.js +0 -108
  132. package/dist/mcp/tools/skill.d.ts +0 -10
  133. package/dist/mcp/tools/skill.js +0 -279
  134. package/dist/mcp/tools/workflow-template.d.ts +0 -19
  135. package/dist/mcp/tools/workflow-template.js +0 -822
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Agent Routes and Connection Manager
3
+ *
4
+ * REST API endpoints for enabling/disabling agents in workspaces
5
+ * and managing their persistent connections.
6
+ */
7
+ import { Router } from 'express';
8
+ import { HailerClient } from '../mcp/hailer-clients';
9
+ import { AgentCredentialStore, AgentCredentials } from '../services/agent-credential-store';
10
+ /**
11
+ * Manages persistent connections for enabled agents.
12
+ * Handles connection lifecycle, reconnection, and signal handling.
13
+ */
14
+ export declare class AgentConnectionManager {
15
+ private credentialStore;
16
+ private connections;
17
+ private logger;
18
+ constructor(credentialStore: AgentCredentialStore);
19
+ /**
20
+ * Connect an agent using stored credentials
21
+ */
22
+ connect(credentials: AgentCredentials): Promise<HailerClient>;
23
+ /**
24
+ * Disconnect an agent
25
+ */
26
+ disconnect(workspaceId: string, agentType: string): Promise<void>;
27
+ /**
28
+ * Check if agent is connected
29
+ */
30
+ isConnected(workspaceId: string, agentType: string): boolean;
31
+ /**
32
+ * Reconnect all agents on server startup
33
+ */
34
+ reconnectAll(): Promise<void>;
35
+ /**
36
+ * Handle @mention signals (placeholder for agent logic)
37
+ */
38
+ private handleMentionSignal;
39
+ }
40
+ /**
41
+ * Create Express router for agent management endpoints
42
+ */
43
+ export declare function createAgentRouter(credentialStore: AgentCredentialStore, connectionManager: AgentConnectionManager): Router;
44
+ //# sourceMappingURL=agents.d.ts.map
@@ -0,0 +1,311 @@
1
+ "use strict";
2
+ /**
3
+ * Agent Routes and Connection Manager
4
+ *
5
+ * REST API endpoints for enabling/disabling agents in workspaces
6
+ * and managing their persistent connections.
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.AgentConnectionManager = void 0;
13
+ exports.createAgentRouter = createAgentRouter;
14
+ const express_1 = require("express");
15
+ const crypto_1 = __importDefault(require("crypto"));
16
+ const zod_1 = require("zod");
17
+ const cli_1 = require("@hailer/cli");
18
+ const hailer_clients_1 = require("../mcp/hailer-clients");
19
+ const config_1 = require("../config");
20
+ const logger_1 = require("../lib/logger");
21
+ // ================================================================================
22
+ // AGENT CONNECTION MANAGER
23
+ // ================================================================================
24
+ /**
25
+ * Manages persistent connections for enabled agents.
26
+ * Handles connection lifecycle, reconnection, and signal handling.
27
+ */
28
+ class AgentConnectionManager {
29
+ credentialStore;
30
+ connections = new Map();
31
+ logger = (0, logger_1.createLogger)({ component: 'agent-connection-manager' });
32
+ constructor(credentialStore) {
33
+ this.credentialStore = credentialStore;
34
+ }
35
+ /**
36
+ * Connect an agent using stored credentials
37
+ */
38
+ async connect(credentials) {
39
+ const connectionKey = `${credentials.workspaceId}:${credentials.agentType}`;
40
+ // Check existing connection
41
+ const existing = this.connections.get(connectionKey);
42
+ if (existing && existing.isConnected()) {
43
+ return existing.getClient();
44
+ }
45
+ // Clean stale connection
46
+ if (existing) {
47
+ existing.disconnect();
48
+ this.connections.delete(connectionKey);
49
+ }
50
+ // Create new connection
51
+ const password = this.credentialStore.decryptPassword(credentials.encryptedPassword);
52
+ const clientManager = new hailer_clients_1.HailerClientManager(config_1.environment.HAILER_API_URL, credentials.email, password);
53
+ await clientManager.connect();
54
+ this.connections.set(connectionKey, clientManager);
55
+ // Set up @mention listener
56
+ clientManager.onSignal('messenger.new', (data) => {
57
+ this.handleMentionSignal(credentials, data);
58
+ });
59
+ this.logger.info('Agent connected', {
60
+ agentType: credentials.agentType,
61
+ workspaceId: credentials.workspaceId
62
+ });
63
+ return clientManager.getClient();
64
+ }
65
+ /**
66
+ * Disconnect an agent
67
+ */
68
+ async disconnect(workspaceId, agentType) {
69
+ const connectionKey = `${workspaceId}:${agentType}`;
70
+ const clientManager = this.connections.get(connectionKey);
71
+ if (clientManager) {
72
+ clientManager.disconnect();
73
+ this.connections.delete(connectionKey);
74
+ this.logger.info('Agent disconnected', { agentType, workspaceId });
75
+ }
76
+ }
77
+ /**
78
+ * Check if agent is connected
79
+ */
80
+ isConnected(workspaceId, agentType) {
81
+ const connectionKey = `${workspaceId}:${agentType}`;
82
+ const clientManager = this.connections.get(connectionKey);
83
+ return clientManager?.isConnected() ?? false;
84
+ }
85
+ /**
86
+ * Reconnect all agents on server startup
87
+ */
88
+ async reconnectAll() {
89
+ const allCredentials = await this.credentialStore.listAll();
90
+ const activeCredentials = allCredentials.filter(c => c.status === 'active');
91
+ this.logger.info('Reconnecting agents', { count: activeCredentials.length });
92
+ for (const credentials of activeCredentials) {
93
+ try {
94
+ await this.connect(credentials);
95
+ }
96
+ catch (error) {
97
+ this.logger.error('Failed to reconnect agent', error, {
98
+ agentType: credentials.agentType,
99
+ workspaceId: credentials.workspaceId
100
+ });
101
+ // Update status to error
102
+ await this.credentialStore.save({
103
+ ...credentials,
104
+ status: 'error',
105
+ lastError: error instanceof Error ? error.message : String(error)
106
+ });
107
+ }
108
+ }
109
+ }
110
+ /**
111
+ * Handle @mention signals (placeholder for agent logic)
112
+ */
113
+ handleMentionSignal(credentials, data) {
114
+ this.logger.debug('Mention signal received', {
115
+ agentType: credentials.agentType,
116
+ workspaceId: credentials.workspaceId,
117
+ data
118
+ });
119
+ // TODO: Implement agent response logic
120
+ }
121
+ }
122
+ exports.AgentConnectionManager = AgentConnectionManager;
123
+ // ================================================================================
124
+ // REQUEST VALIDATION SCHEMAS
125
+ // ================================================================================
126
+ const enableAgentSchema = zod_1.z.object({
127
+ invite_key: zod_1.z.string().min(1, 'invite_key is required'),
128
+ email: zod_1.z.string().email('Invalid email format'),
129
+ agentType: zod_1.z.string().min(1, 'agentType is required'),
130
+ displayName: zod_1.z.string().min(1, 'displayName is required'),
131
+ workspaceId: zod_1.z.string().length(24, 'workspaceId must be 24 characters'),
132
+ ownerId: zod_1.z.string().length(24, 'ownerId must be 24 characters'),
133
+ });
134
+ const disableAgentSchema = zod_1.z.object({
135
+ agentType: zod_1.z.string().min(1, 'agentType is required'),
136
+ workspaceId: zod_1.z.string().length(24, 'workspaceId must be 24 characters'),
137
+ });
138
+ // ================================================================================
139
+ // ROUTER FACTORY
140
+ // ================================================================================
141
+ /**
142
+ * Create Express router for agent management endpoints
143
+ */
144
+ function createAgentRouter(credentialStore, connectionManager) {
145
+ const router = (0, express_1.Router)();
146
+ const logger = (0, logger_1.createLogger)({ component: 'agent-routes' });
147
+ // POST /enable - Enable an agent in a workspace
148
+ router.post('/enable', async (req, res) => {
149
+ try {
150
+ // Validate request
151
+ const validation = enableAgentSchema.safeParse(req.body);
152
+ if (!validation.success) {
153
+ return res.status(400).json({
154
+ error: 'Validation failed',
155
+ details: validation.error.flatten().fieldErrors
156
+ });
157
+ }
158
+ const { invite_key, email, agentType, displayName, workspaceId, ownerId } = validation.data;
159
+ // Check if already enabled
160
+ const existing = await credentialStore.get(workspaceId, agentType);
161
+ if (existing) {
162
+ return res.status(409).json({
163
+ error: 'Agent already enabled',
164
+ agent: {
165
+ agentType: existing.agentType,
166
+ displayName: existing.displayName,
167
+ status: existing.status,
168
+ enabledAt: existing.enabledAt
169
+ }
170
+ });
171
+ }
172
+ // Generate secure password
173
+ const password = crypto_1.default.randomBytes(32).toString('hex');
174
+ // Create temp client for public invite registration (unauthenticated)
175
+ logger.info('Registering agent via invite', { agentType, workspaceId });
176
+ const tempClient = await cli_1.Client.create({ host: config_1.environment.HAILER_API_URL });
177
+ try {
178
+ await tempClient.request('v2.user.invite.register', [{
179
+ firstname: displayName,
180
+ lastname: 'Agent',
181
+ password: password
182
+ }, invite_key]);
183
+ }
184
+ finally {
185
+ tempClient.disconnect();
186
+ }
187
+ // Store encrypted credentials
188
+ const credentials = {
189
+ workspaceId,
190
+ agentType,
191
+ email,
192
+ encryptedPassword: credentialStore.encryptPassword(password),
193
+ displayName,
194
+ enabledBy: ownerId,
195
+ enabledAt: new Date().toISOString(),
196
+ status: 'pending'
197
+ };
198
+ await credentialStore.save(credentials);
199
+ // Connect the agent
200
+ try {
201
+ await connectionManager.connect(credentials);
202
+ credentials.status = 'active';
203
+ credentials.lastConnectedAt = new Date().toISOString();
204
+ await credentialStore.save(credentials);
205
+ }
206
+ catch (connectError) {
207
+ credentials.status = 'error';
208
+ credentials.lastError = connectError instanceof Error ? connectError.message : String(connectError);
209
+ await credentialStore.save(credentials);
210
+ logger.error('Agent registered but connection failed', connectError, { agentType, workspaceId });
211
+ return res.status(207).json({
212
+ success: true,
213
+ warning: 'Agent registered but connection failed',
214
+ agent: {
215
+ agentType,
216
+ displayName,
217
+ workspaceId,
218
+ status: 'error',
219
+ error: credentials.lastError
220
+ }
221
+ });
222
+ }
223
+ logger.info('Agent enabled successfully', { agentType, workspaceId, displayName });
224
+ res.json({
225
+ success: true,
226
+ agent: {
227
+ agentType,
228
+ displayName,
229
+ workspaceId,
230
+ status: 'active'
231
+ }
232
+ });
233
+ }
234
+ catch (error) {
235
+ logger.error('Failed to enable agent', error);
236
+ res.status(500).json({
237
+ error: 'Failed to enable agent',
238
+ details: error instanceof Error ? error.message : String(error)
239
+ });
240
+ }
241
+ });
242
+ // POST /disable - Disable an agent in a workspace
243
+ router.post('/disable', async (req, res) => {
244
+ try {
245
+ const validation = disableAgentSchema.safeParse(req.body);
246
+ if (!validation.success) {
247
+ return res.status(400).json({
248
+ error: 'Validation failed',
249
+ details: validation.error.flatten().fieldErrors
250
+ });
251
+ }
252
+ const { agentType, workspaceId } = validation.data;
253
+ // Check if exists
254
+ const existing = await credentialStore.get(workspaceId, agentType);
255
+ if (!existing) {
256
+ return res.status(404).json({
257
+ error: 'Agent not found',
258
+ agentType,
259
+ workspaceId
260
+ });
261
+ }
262
+ // Disconnect and remove
263
+ await connectionManager.disconnect(workspaceId, agentType);
264
+ await credentialStore.delete(workspaceId, agentType);
265
+ logger.info('Agent disabled', { agentType, workspaceId });
266
+ res.json({
267
+ success: true,
268
+ message: `Agent ${agentType} disabled in workspace ${workspaceId}`
269
+ });
270
+ }
271
+ catch (error) {
272
+ logger.error('Failed to disable agent', error);
273
+ res.status(500).json({
274
+ error: 'Failed to disable agent',
275
+ details: error instanceof Error ? error.message : String(error)
276
+ });
277
+ }
278
+ });
279
+ // GET /list/:workspaceId - List enabled agents for a workspace
280
+ router.get('/list/:workspaceId', async (req, res) => {
281
+ try {
282
+ const { workspaceId } = req.params;
283
+ if (!workspaceId || workspaceId.length !== 24) {
284
+ return res.status(400).json({
285
+ error: 'Invalid workspaceId - must be 24 characters'
286
+ });
287
+ }
288
+ const credentials = await credentialStore.listByWorkspace(workspaceId);
289
+ const agents = credentials.map(cred => ({
290
+ agentType: cred.agentType,
291
+ displayName: cred.displayName,
292
+ status: cred.status,
293
+ enabledAt: cred.enabledAt,
294
+ enabledBy: cred.enabledBy,
295
+ connected: connectionManager.isConnected(workspaceId, cred.agentType),
296
+ lastConnectedAt: cred.lastConnectedAt,
297
+ lastError: cred.lastError
298
+ }));
299
+ res.json({ agents });
300
+ }
301
+ catch (error) {
302
+ logger.error('Failed to list agents', error);
303
+ res.status(500).json({
304
+ error: 'Failed to list agents',
305
+ details: error instanceof Error ? error.message : String(error)
306
+ });
307
+ }
308
+ });
309
+ return router;
310
+ }
311
+ //# sourceMappingURL=agents.js.map
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Agent Credential Store
3
+ *
4
+ * Encrypted JSON file storage for agent credentials.
5
+ * Uses AES-256-GCM encryption for password storage.
6
+ */
7
+ export interface AgentCredentials {
8
+ workspaceId: string;
9
+ agentType: string;
10
+ email: string;
11
+ encryptedPassword: string;
12
+ userId?: string;
13
+ displayName: string;
14
+ enabledBy: string;
15
+ enabledAt: string;
16
+ status: 'pending' | 'active' | 'disconnected' | 'error';
17
+ lastConnectedAt?: string;
18
+ lastError?: string;
19
+ }
20
+ export declare class AgentCredentialStore {
21
+ private dataDir;
22
+ private filePath;
23
+ private encryptionKey;
24
+ constructor(dataDir?: string);
25
+ /**
26
+ * Generate storage key from workspaceId and agentType
27
+ */
28
+ private getStorageKey;
29
+ /**
30
+ * Ensure data directory exists
31
+ */
32
+ private ensureDataDir;
33
+ /**
34
+ * Read credentials file with graceful error handling
35
+ */
36
+ private readStorage;
37
+ /**
38
+ * Write credentials file atomically
39
+ */
40
+ private writeStorage;
41
+ /**
42
+ * Encrypt password using AES-256-GCM
43
+ * Format: iv:authTag:ciphertext (all hex-encoded)
44
+ */
45
+ encryptPassword(plaintext: string): string;
46
+ /**
47
+ * Decrypt password from AES-256-GCM format
48
+ * Expects format: iv:authTag:ciphertext (all hex-encoded)
49
+ */
50
+ decryptPassword(ciphertext: string): string;
51
+ /**
52
+ * Save or update agent credentials
53
+ */
54
+ save(credentials: AgentCredentials): Promise<void>;
55
+ /**
56
+ * Get agent credentials by workspace and type
57
+ */
58
+ get(workspaceId: string, agentType: string): Promise<AgentCredentials | null>;
59
+ /**
60
+ * Delete agent credentials
61
+ * Returns true if credentials were found and deleted
62
+ */
63
+ delete(workspaceId: string, agentType: string): Promise<boolean>;
64
+ /**
65
+ * List all credentials for a workspace
66
+ */
67
+ listByWorkspace(workspaceId: string): Promise<AgentCredentials[]>;
68
+ /**
69
+ * List all stored credentials
70
+ */
71
+ listAll(): Promise<AgentCredentials[]>;
72
+ }
73
+ //# sourceMappingURL=agent-credential-store.d.ts.map
@@ -0,0 +1,212 @@
1
+ "use strict";
2
+ /**
3
+ * Agent Credential Store
4
+ *
5
+ * Encrypted JSON file storage for agent credentials.
6
+ * Uses AES-256-GCM encryption for password storage.
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.AgentCredentialStore = void 0;
13
+ const crypto_1 = __importDefault(require("crypto"));
14
+ const promises_1 = __importDefault(require("fs/promises"));
15
+ const path_1 = __importDefault(require("path"));
16
+ const config_1 = require("../config");
17
+ const logger_1 = require("../lib/logger");
18
+ const logger = (0, logger_1.createLogger)({ component: 'agent-credential-store' });
19
+ // ================================================================================
20
+ // ENCRYPTION HELPERS
21
+ // ================================================================================
22
+ const ALGORITHM = 'aes-256-gcm';
23
+ const IV_LENGTH = 12; // GCM standard
24
+ const AUTH_TAG_LENGTH = 16;
25
+ /**
26
+ * Get encryption key from environment or generate dev fallback
27
+ */
28
+ function getEncryptionKey() {
29
+ if (config_1.environment.CREDENTIAL_ENCRYPTION_KEY) {
30
+ return Buffer.from(config_1.environment.CREDENTIAL_ENCRYPTION_KEY, 'hex');
31
+ }
32
+ // Development fallback - deterministic key from a fixed seed
33
+ // WARNING: This is NOT secure for production
34
+ if (config_1.environment.NODE_ENV === 'development' || config_1.environment.NODE_ENV === 'test') {
35
+ logger.warn('Using development fallback encryption key - NOT SECURE FOR PRODUCTION');
36
+ // Generate deterministic key from seed phrase
37
+ return crypto_1.default.createHash('sha256').update('hailer-mcp-dev-key-DO-NOT-USE-IN-PRODUCTION').digest();
38
+ }
39
+ throw new Error('CREDENTIAL_ENCRYPTION_KEY must be set in production');
40
+ }
41
+ // ================================================================================
42
+ // AGENT CREDENTIAL STORE CLASS
43
+ // ================================================================================
44
+ class AgentCredentialStore {
45
+ dataDir;
46
+ filePath;
47
+ encryptionKey;
48
+ constructor(dataDir) {
49
+ // Default to 'data/' relative to project root
50
+ this.dataDir = dataDir || path_1.default.join(process.cwd(), 'data');
51
+ this.filePath = path_1.default.join(this.dataDir, 'agent-credentials.json');
52
+ this.encryptionKey = getEncryptionKey();
53
+ }
54
+ /**
55
+ * Generate storage key from workspaceId and agentType
56
+ */
57
+ getStorageKey(workspaceId, agentType) {
58
+ return `${workspaceId}:${agentType}`;
59
+ }
60
+ /**
61
+ * Ensure data directory exists
62
+ */
63
+ async ensureDataDir() {
64
+ try {
65
+ await promises_1.default.mkdir(this.dataDir, { recursive: true });
66
+ }
67
+ catch (error) {
68
+ // Directory might already exist
69
+ if (error.code !== 'EEXIST') {
70
+ throw error;
71
+ }
72
+ }
73
+ }
74
+ /**
75
+ * Read credentials file with graceful error handling
76
+ */
77
+ async readStorage() {
78
+ try {
79
+ const content = await promises_1.default.readFile(this.filePath, 'utf-8');
80
+ const storage = JSON.parse(content);
81
+ // Validate structure
82
+ if (typeof storage !== 'object' || !storage.credentials) {
83
+ throw new Error('Invalid storage format');
84
+ }
85
+ return storage;
86
+ }
87
+ catch (error) {
88
+ if (error.code === 'ENOENT') {
89
+ // File doesn't exist - return empty storage
90
+ return { version: 1, credentials: {} };
91
+ }
92
+ // JSON parsing error or other issue - log and return empty
93
+ logger.error('Failed to read credentials file, initializing empty storage', error);
94
+ return { version: 1, credentials: {} };
95
+ }
96
+ }
97
+ /**
98
+ * Write credentials file atomically
99
+ */
100
+ async writeStorage(storage) {
101
+ await this.ensureDataDir();
102
+ const content = JSON.stringify(storage, null, 2);
103
+ const tempPath = `${this.filePath}.tmp.${Date.now()}`;
104
+ try {
105
+ // Write to temp file first
106
+ await promises_1.default.writeFile(tempPath, content, 'utf-8');
107
+ // Atomic rename
108
+ await promises_1.default.rename(tempPath, this.filePath);
109
+ }
110
+ catch (error) {
111
+ // Clean up temp file if it exists
112
+ try {
113
+ await promises_1.default.unlink(tempPath);
114
+ }
115
+ catch {
116
+ // Ignore cleanup errors
117
+ }
118
+ throw error;
119
+ }
120
+ }
121
+ /**
122
+ * Encrypt password using AES-256-GCM
123
+ * Format: iv:authTag:ciphertext (all hex-encoded)
124
+ */
125
+ encryptPassword(plaintext) {
126
+ const iv = crypto_1.default.randomBytes(IV_LENGTH);
127
+ const cipher = crypto_1.default.createCipheriv(ALGORITHM, this.encryptionKey, iv);
128
+ let encrypted = cipher.update(plaintext, 'utf8', 'hex');
129
+ encrypted += cipher.final('hex');
130
+ const authTag = cipher.getAuthTag();
131
+ // Format: iv:authTag:ciphertext
132
+ return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
133
+ }
134
+ /**
135
+ * Decrypt password from AES-256-GCM format
136
+ * Expects format: iv:authTag:ciphertext (all hex-encoded)
137
+ */
138
+ decryptPassword(ciphertext) {
139
+ const parts = ciphertext.split(':');
140
+ if (parts.length !== 3) {
141
+ throw new Error('Invalid encrypted password format');
142
+ }
143
+ const [ivHex, authTagHex, encryptedHex] = parts;
144
+ const iv = Buffer.from(ivHex, 'hex');
145
+ const authTag = Buffer.from(authTagHex, 'hex');
146
+ const encrypted = Buffer.from(encryptedHex, 'hex');
147
+ if (iv.length !== IV_LENGTH) {
148
+ throw new Error(`Invalid IV length: expected ${IV_LENGTH}, got ${iv.length}`);
149
+ }
150
+ if (authTag.length !== AUTH_TAG_LENGTH) {
151
+ throw new Error(`Invalid auth tag length: expected ${AUTH_TAG_LENGTH}, got ${authTag.length}`);
152
+ }
153
+ const decipher = crypto_1.default.createDecipheriv(ALGORITHM, this.encryptionKey, iv);
154
+ decipher.setAuthTag(authTag);
155
+ let decrypted = decipher.update(encrypted);
156
+ decrypted = Buffer.concat([decrypted, decipher.final()]);
157
+ return decrypted.toString('utf8');
158
+ }
159
+ /**
160
+ * Save or update agent credentials
161
+ */
162
+ async save(credentials) {
163
+ const storage = await this.readStorage();
164
+ const key = this.getStorageKey(credentials.workspaceId, credentials.agentType);
165
+ storage.credentials[key] = credentials;
166
+ await this.writeStorage(storage);
167
+ logger.info('Saved agent credentials', {
168
+ workspaceId: credentials.workspaceId,
169
+ agentType: credentials.agentType,
170
+ status: credentials.status,
171
+ });
172
+ }
173
+ /**
174
+ * Get agent credentials by workspace and type
175
+ */
176
+ async get(workspaceId, agentType) {
177
+ const storage = await this.readStorage();
178
+ const key = this.getStorageKey(workspaceId, agentType);
179
+ return storage.credentials[key] || null;
180
+ }
181
+ /**
182
+ * Delete agent credentials
183
+ * Returns true if credentials were found and deleted
184
+ */
185
+ async delete(workspaceId, agentType) {
186
+ const storage = await this.readStorage();
187
+ const key = this.getStorageKey(workspaceId, agentType);
188
+ if (!(key in storage.credentials)) {
189
+ return false;
190
+ }
191
+ delete storage.credentials[key];
192
+ await this.writeStorage(storage);
193
+ logger.info('Deleted agent credentials', { workspaceId, agentType });
194
+ return true;
195
+ }
196
+ /**
197
+ * List all credentials for a workspace
198
+ */
199
+ async listByWorkspace(workspaceId) {
200
+ const storage = await this.readStorage();
201
+ return Object.values(storage.credentials).filter((cred) => cred.workspaceId === workspaceId);
202
+ }
203
+ /**
204
+ * List all stored credentials
205
+ */
206
+ async listAll() {
207
+ const storage = await this.readStorage();
208
+ return Object.values(storage.credentials);
209
+ }
210
+ }
211
+ exports.AgentCredentialStore = AgentCredentialStore;
212
+ //# sourceMappingURL=agent-credential-store.js.map
@@ -0,0 +1 @@
1
+ @import"https://fonts.googleapis.com/css?family=Nunito+Sans:200,400,400i,600,700,800";:root[data-theme=light]{--chakra-colors-chakra-body-bg: var(--chakra-colors-white) !important}body{margin:0;min-width:320px;min-height:100vh}