@hailer/mcp 0.0.1

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 (163) hide show
  1. package/.claude/commands/tool-builder.md +37 -0
  2. package/.claude/commands/ws-pull.md +44 -0
  3. package/.claude/settings.json +8 -0
  4. package/.claude/settings.local.json +49 -0
  5. package/.claude/skills/activity-api/SKILL.md +96 -0
  6. package/.claude/skills/activity-api/references/activity-endpoints.md +845 -0
  7. package/.claude/skills/add-app-member-skill/SKILL.md +977 -0
  8. package/.claude/skills/agent-building/SKILL.md +243 -0
  9. package/.claude/skills/agent-building/references/architecture-patterns.md +446 -0
  10. package/.claude/skills/agent-building/references/code-examples.md +587 -0
  11. package/.claude/skills/agent-building/references/implementation-guide.md +619 -0
  12. package/.claude/skills/app-api/SKILL.md +219 -0
  13. package/.claude/skills/app-api/references/app-endpoints.md +759 -0
  14. package/.claude/skills/building-hailer-apps-skill/SKILL.md +548 -0
  15. package/.claude/skills/create-app-skill/SKILL.md +1101 -0
  16. package/.claude/skills/create-insight-skill/SKILL.md +1317 -0
  17. package/.claude/skills/get-insight-data-skill/SKILL.md +1053 -0
  18. package/.claude/skills/hailer-api/SKILL.md +283 -0
  19. package/.claude/skills/hailer-api/references/activities.md +620 -0
  20. package/.claude/skills/hailer-api/references/authentication.md +216 -0
  21. package/.claude/skills/hailer-api/references/datasets.md +437 -0
  22. package/.claude/skills/hailer-api/references/files.md +301 -0
  23. package/.claude/skills/hailer-api/references/insights.md +469 -0
  24. package/.claude/skills/hailer-api/references/workflows.md +720 -0
  25. package/.claude/skills/hailer-api/references/workspaces-users.md +445 -0
  26. package/.claude/skills/insight-api/SKILL.md +185 -0
  27. package/.claude/skills/insight-api/references/insight-endpoints.md +514 -0
  28. package/.claude/skills/install-workflow-skill/SKILL.md +1056 -0
  29. package/.claude/skills/list-apps-skill/SKILL.md +1010 -0
  30. package/.claude/skills/list-workflows-minimal-skill/SKILL.md +992 -0
  31. package/.claude/skills/local-first-skill/SKILL.md +570 -0
  32. package/.claude/skills/mcp-tools/SKILL.md +419 -0
  33. package/.claude/skills/mcp-tools/references/api-endpoints.md +499 -0
  34. package/.claude/skills/mcp-tools/references/data-structures.md +554 -0
  35. package/.claude/skills/mcp-tools/references/implementation-patterns.md +717 -0
  36. package/.claude/skills/preview-insight-skill/SKILL.md +1290 -0
  37. package/.claude/skills/publish-hailer-app-skill/SKILL.md +453 -0
  38. package/.claude/skills/remove-app-member-skill/SKILL.md +671 -0
  39. package/.claude/skills/remove-app-skill/SKILL.md +985 -0
  40. package/.claude/skills/remove-insight-skill/SKILL.md +1011 -0
  41. package/.claude/skills/remove-workflow-skill/SKILL.md +920 -0
  42. package/.claude/skills/scaffold-hailer-app-skill/SKILL.md +1034 -0
  43. package/.claude/skills/skill-testing/README.md +137 -0
  44. package/.claude/skills/skill-testing/SKILL.md +348 -0
  45. package/.claude/skills/skill-testing/references/test-patterns.md +705 -0
  46. package/.claude/skills/skill-testing/references/testing-guide.md +603 -0
  47. package/.claude/skills/skill-testing/references/validation-checklist.md +537 -0
  48. package/.claude/skills/tool-builder/SKILL.md +328 -0
  49. package/.claude/skills/update-app-skill/SKILL.md +970 -0
  50. package/.claude/skills/update-workflow-field-skill/SKILL.md +1098 -0
  51. package/.env.example +81 -0
  52. package/.mcp.json +13 -0
  53. package/README.md +297 -0
  54. package/dist/app.d.ts +4 -0
  55. package/dist/app.js +74 -0
  56. package/dist/cli.d.ts +3 -0
  57. package/dist/cli.js +5 -0
  58. package/dist/client/adaptive-documentation-bot.d.ts +108 -0
  59. package/dist/client/adaptive-documentation-bot.js +475 -0
  60. package/dist/client/adaptive-documentation-types.d.ts +66 -0
  61. package/dist/client/adaptive-documentation-types.js +9 -0
  62. package/dist/client/agent-activity-bot.d.ts +51 -0
  63. package/dist/client/agent-activity-bot.js +166 -0
  64. package/dist/client/agent-tracker.d.ts +499 -0
  65. package/dist/client/agent-tracker.js +659 -0
  66. package/dist/client/description-updater.d.ts +56 -0
  67. package/dist/client/description-updater.js +259 -0
  68. package/dist/client/log-parser.d.ts +72 -0
  69. package/dist/client/log-parser.js +387 -0
  70. package/dist/client/mcp-client.d.ts +50 -0
  71. package/dist/client/mcp-client.js +532 -0
  72. package/dist/client/message-processor.d.ts +35 -0
  73. package/dist/client/message-processor.js +352 -0
  74. package/dist/client/multi-bot-manager.d.ts +24 -0
  75. package/dist/client/multi-bot-manager.js +74 -0
  76. package/dist/client/providers/anthropic-provider.d.ts +19 -0
  77. package/dist/client/providers/anthropic-provider.js +631 -0
  78. package/dist/client/providers/llm-provider.d.ts +47 -0
  79. package/dist/client/providers/llm-provider.js +367 -0
  80. package/dist/client/providers/openai-provider.d.ts +23 -0
  81. package/dist/client/providers/openai-provider.js +621 -0
  82. package/dist/client/simple-llm-caller.d.ts +19 -0
  83. package/dist/client/simple-llm-caller.js +100 -0
  84. package/dist/client/skill-generator.d.ts +81 -0
  85. package/dist/client/skill-generator.js +386 -0
  86. package/dist/client/test-adaptive-bot.d.ts +9 -0
  87. package/dist/client/test-adaptive-bot.js +82 -0
  88. package/dist/client/token-pricing.d.ts +38 -0
  89. package/dist/client/token-pricing.js +127 -0
  90. package/dist/client/token-tracker.d.ts +232 -0
  91. package/dist/client/token-tracker.js +457 -0
  92. package/dist/client/token-usage-bot.d.ts +53 -0
  93. package/dist/client/token-usage-bot.js +153 -0
  94. package/dist/client/tool-executor.d.ts +69 -0
  95. package/dist/client/tool-executor.js +159 -0
  96. package/dist/client/tool-schema-loader.d.ts +60 -0
  97. package/dist/client/tool-schema-loader.js +178 -0
  98. package/dist/client/types.d.ts +69 -0
  99. package/dist/client/types.js +7 -0
  100. package/dist/config.d.ts +162 -0
  101. package/dist/config.js +296 -0
  102. package/dist/core.d.ts +26 -0
  103. package/dist/core.js +147 -0
  104. package/dist/lib/context-manager.d.ts +111 -0
  105. package/dist/lib/context-manager.js +431 -0
  106. package/dist/lib/logger.d.ts +74 -0
  107. package/dist/lib/logger.js +277 -0
  108. package/dist/lib/materialize.d.ts +3 -0
  109. package/dist/lib/materialize.js +101 -0
  110. package/dist/lib/normalizedName.d.ts +7 -0
  111. package/dist/lib/normalizedName.js +48 -0
  112. package/dist/lib/prompt-length-manager.d.ts +81 -0
  113. package/dist/lib/prompt-length-manager.js +457 -0
  114. package/dist/lib/terminal-prompt.d.ts +9 -0
  115. package/dist/lib/terminal-prompt.js +108 -0
  116. package/dist/mcp/UserContextCache.d.ts +56 -0
  117. package/dist/mcp/UserContextCache.js +163 -0
  118. package/dist/mcp/auth.d.ts +2 -0
  119. package/dist/mcp/auth.js +29 -0
  120. package/dist/mcp/hailer-clients.d.ts +42 -0
  121. package/dist/mcp/hailer-clients.js +246 -0
  122. package/dist/mcp/signal-handler.d.ts +45 -0
  123. package/dist/mcp/signal-handler.js +317 -0
  124. package/dist/mcp/tool-registry.d.ts +100 -0
  125. package/dist/mcp/tool-registry.js +306 -0
  126. package/dist/mcp/tools/activity.d.ts +15 -0
  127. package/dist/mcp/tools/activity.js +955 -0
  128. package/dist/mcp/tools/app.d.ts +20 -0
  129. package/dist/mcp/tools/app.js +1488 -0
  130. package/dist/mcp/tools/discussion.d.ts +19 -0
  131. package/dist/mcp/tools/discussion.js +950 -0
  132. package/dist/mcp/tools/file.d.ts +15 -0
  133. package/dist/mcp/tools/file.js +119 -0
  134. package/dist/mcp/tools/insight.d.ts +17 -0
  135. package/dist/mcp/tools/insight.js +806 -0
  136. package/dist/mcp/tools/skill.d.ts +10 -0
  137. package/dist/mcp/tools/skill.js +279 -0
  138. package/dist/mcp/tools/user.d.ts +10 -0
  139. package/dist/mcp/tools/user.js +108 -0
  140. package/dist/mcp/tools/workflow-template.d.ts +19 -0
  141. package/dist/mcp/tools/workflow-template.js +822 -0
  142. package/dist/mcp/tools/workflow.d.ts +18 -0
  143. package/dist/mcp/tools/workflow.js +1362 -0
  144. package/dist/mcp/utils/api-errors.d.ts +45 -0
  145. package/dist/mcp/utils/api-errors.js +160 -0
  146. package/dist/mcp/utils/data-transformers.d.ts +102 -0
  147. package/dist/mcp/utils/data-transformers.js +194 -0
  148. package/dist/mcp/utils/file-upload.d.ts +33 -0
  149. package/dist/mcp/utils/file-upload.js +148 -0
  150. package/dist/mcp/utils/hailer-api-client.d.ts +120 -0
  151. package/dist/mcp/utils/hailer-api-client.js +323 -0
  152. package/dist/mcp/utils/index.d.ts +13 -0
  153. package/dist/mcp/utils/index.js +39 -0
  154. package/dist/mcp/utils/logger.d.ts +42 -0
  155. package/dist/mcp/utils/logger.js +103 -0
  156. package/dist/mcp/utils/types.d.ts +286 -0
  157. package/dist/mcp/utils/types.js +7 -0
  158. package/dist/mcp/workspace-cache.d.ts +42 -0
  159. package/dist/mcp/workspace-cache.js +97 -0
  160. package/dist/mcp-server.d.ts +42 -0
  161. package/dist/mcp-server.js +280 -0
  162. package/package.json +56 -0
  163. package/tsconfig.json +23 -0
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UserContextCache = void 0;
4
+ const hailer_clients_1 = require("./hailer-clients");
5
+ const workspace_cache_1 = require("./workspace-cache");
6
+ const config_1 = require("../config");
7
+ const logger_1 = require("../lib/logger");
8
+ const index_1 = require("./utils/index");
9
+ const logger = (0, logger_1.createLogger)({ component: 'user-context-cache' });
10
+ /**
11
+ * Cache for user-specific data (client connections, init data, workspace cache)
12
+ *
13
+ * Replaces per-user ClassBasedToolManager instances with efficient caching:
14
+ * - Reuses shared connection pool from hailer-clients.ts
15
+ * - Caches user-specific data (init, workspaceCache) separately from tool logic
16
+ * - Used by both MCP Server and Client for consistent user context
17
+ */
18
+ class UserContextCache {
19
+ static cache = new Map();
20
+ /** Life of each cache unit. */
21
+ static DEFAULT_TTL_MS = 15 * 60 * 1000; // 15 minutes
22
+ /**
23
+ * Get or create user context (client, init, workspaceCache) for an API key
24
+ * This data is user-specific and should be cached per user
25
+ *
26
+ * @param apiKey - Hailer API key for authentication
27
+ * @param forceRefresh - If true, bypasses cache and creates fresh context (useful for testing or after permission changes)
28
+ * @param ttlMs - Custom TTL in milliseconds, defaults to 15 minutes
29
+ */
30
+ static async getContext(apiKey, forceRefresh = false, ttlMs = this.DEFAULT_TTL_MS) {
31
+ console.log(`🔍 getContext called: apiKey=${apiKey.substring(0, 8)}..., forceRefresh=${forceRefresh}, cacheHas=${this.cache.has(apiKey)}`);
32
+ // Check cache first (unless forceRefresh is requested)
33
+ if (!forceRefresh && this.cache.has(apiKey)) {
34
+ const cachedContext = this.cache.get(apiKey);
35
+ const age = Date.now() - cachedContext.createdAt;
36
+ // Check if cache is still fresh
37
+ if (age < ttlMs) {
38
+ logger.debug('Using cached user context', {
39
+ apiKey: apiKey.substring(0, 8) + '...',
40
+ ageMinutes: Math.round(age / (1000 * 60)),
41
+ ttlMinutes: Math.round(ttlMs / (1000 * 60))
42
+ });
43
+ return cachedContext;
44
+ }
45
+ else {
46
+ // Cache expired, remove stale entry
47
+ this.cache.delete(apiKey);
48
+ logger.info('Cache expired, refreshing user context', {
49
+ apiKey: apiKey.substring(0, 8) + '...',
50
+ ageMinutes: Math.round(age / (1000 * 60))
51
+ });
52
+ }
53
+ }
54
+ else if (forceRefresh && this.cache.has(apiKey)) {
55
+ // Force refresh requested, clear existing cache
56
+ this.cache.delete(apiKey);
57
+ logger.info('Force refresh requested, clearing cached user context', {
58
+ apiKey: apiKey.substring(0, 8) + '...'
59
+ });
60
+ }
61
+ logger.info('Creating new user context', { apiKey: apiKey.substring(0, 8) + '...' });
62
+ try {
63
+ // Create client connection (uses existing connection pool)
64
+ const client = await (0, hailer_clients_1.createHailerClientByApiKey)(apiKey);
65
+ // Create API client (host extracted from client automatically)
66
+ const hailer = new index_1.HailerApiClient(client);
67
+ // Fetch user's workspace initialization data
68
+ const init = await client.socket.request('v2.core.init', [
69
+ ['processes', 'users', 'network', 'networks']
70
+ ]);
71
+ // Create workspace cache from init data
72
+ const appConfig = (0, config_1.createApplicationConfig)(); // TODO: small concern here: it creates a new instance of config instaed of reusing it, is it wise?
73
+ const workspaceCache = (0, workspace_cache_1.createWorkspaceCache)(init, appConfig.mcpConfig);
74
+ const context = {
75
+ client,
76
+ hailer,
77
+ init,
78
+ workspaceCache,
79
+ apiKey,
80
+ createdAt: Date.now()
81
+ };
82
+ // Calculate cache sizes
83
+ const rawInitSize = Buffer.byteLength(JSON.stringify(init), 'utf8');
84
+ const workspaceCacheSize = Buffer.byteLength(JSON.stringify(workspaceCache), 'utf8');
85
+ const totalContextSize = Buffer.byteLength(JSON.stringify({
86
+ init,
87
+ workspaceCache
88
+ }), 'utf8');
89
+ // Log cache sizes to console
90
+ console.log('\n📊 === WORKSPACE CACHE SIZE ANALYSIS ===');
91
+ console.log(`Raw init response: ${rawInitSize.toLocaleString()} bytes (${(rawInitSize / 1024).toFixed(2)} KB)`);
92
+ console.log(`Workspace cache: ${workspaceCacheSize.toLocaleString()} bytes (${(workspaceCacheSize / 1024).toFixed(2)} KB)`);
93
+ console.log(`Total context: ${totalContextSize.toLocaleString()} bytes (${(totalContextSize / 1024).toFixed(2)} KB)`);
94
+ console.log(`Reduction: ${((1 - workspaceCacheSize / rawInitSize) * 100).toFixed(1)}%`);
95
+ console.log(`Workflows: ${init.processes?.length || 0}`);
96
+ console.log(`Users: ${workspaceCache.users.length}`);
97
+ console.log(`Workspaces: ${Object.keys(workspaceCache.allWorkspaces).length}`);
98
+ console.log('=====================================\n');
99
+ // Cache for future requests
100
+ this.cache.set(apiKey, context);
101
+ logger.info('User context created and cached', {
102
+ apiKey: apiKey.substring(0, 8) + '...',
103
+ workflowCount: init.processes?.length || 0,
104
+ userCount: workspaceCache.users.length,
105
+ rawInitBytes: rawInitSize,
106
+ workspaceCacheBytes: workspaceCacheSize,
107
+ totalContextBytes: totalContextSize,
108
+ rawInitKB: (rawInitSize / 1024).toFixed(2),
109
+ workspaceCacheKB: (workspaceCacheSize / 1024).toFixed(2),
110
+ totalContextKB: (totalContextSize / 1024).toFixed(2)
111
+ });
112
+ return context;
113
+ }
114
+ catch (error) {
115
+ logger.error('Failed to create user context', error, {
116
+ apiKey: apiKey.substring(0, 8) + '...'
117
+ });
118
+ throw error;
119
+ }
120
+ }
121
+ /**
122
+ * Clear cache entry for specific API key (for cleanup/testing)
123
+ */
124
+ static clearContext(apiKey) {
125
+ this.cache.delete(apiKey);
126
+ logger.debug('Cleared user context from cache', {
127
+ apiKey: apiKey.substring(0, 8) + '...'
128
+ });
129
+ }
130
+ /**
131
+ * Clear all cached contexts (for cleanup/testing)
132
+ */
133
+ static clearAll() {
134
+ const size = this.cache.size;
135
+ this.cache.clear();
136
+ logger.info('Cleared all user contexts from cache', { clearedCount: size });
137
+ }
138
+ /**
139
+ * Get cache statistics (for monitoring and debugging)
140
+ *
141
+ * @returns Cache stats including size, API keys, and age information
142
+ */
143
+ static getCacheStats() {
144
+ const now = Date.now();
145
+ const entries = Array.from(this.cache.entries()).map(([key, context]) => {
146
+ const ageMs = now - context.createdAt;
147
+ const ageMinutes = Math.round(ageMs / (1000 * 60));
148
+ const isExpired = ageMs > this.DEFAULT_TTL_MS;
149
+ return {
150
+ apiKey: key.substring(0, 8) + '...',
151
+ ageMinutes,
152
+ isExpired
153
+ };
154
+ });
155
+ return {
156
+ size: this.cache.size,
157
+ apiKeys: Array.from(this.cache.keys()).map(key => key.substring(0, 8) + '...'),
158
+ entries
159
+ };
160
+ }
161
+ }
162
+ exports.UserContextCache = UserContextCache;
163
+ //# sourceMappingURL=UserContextCache.js.map
@@ -0,0 +1,2 @@
1
+ export declare const safeFetch: (url: string, options?: RequestInit) => Promise<Response>;
2
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.safeFetch = void 0;
4
+ // Custom fetch function that handles self-signed certificates
5
+ const safeFetch = async (url, options = {}) => {
6
+ const isLocalDev = url.includes('local.gd') || url.includes('localhost');
7
+ if (isLocalDev) {
8
+ // For Next.js/Node.js environment with self-signed certs
9
+ // We need to set the environment variable before making the request
10
+ const originalValue = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
11
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
12
+ try {
13
+ const response = await fetch(url, options);
14
+ return response;
15
+ }
16
+ finally {
17
+ // Restore the original value
18
+ if (originalValue !== undefined) {
19
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = originalValue;
20
+ }
21
+ else {
22
+ delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
23
+ }
24
+ }
25
+ }
26
+ return fetch(url, options);
27
+ };
28
+ exports.safeFetch = safeFetch;
29
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1,42 @@
1
+ import { Client } from "@hailer/cli";
2
+ export interface HailerRestClient {
3
+ fetch: (url: string, options?: RequestInit) => Promise<Response>;
4
+ sessionKey: string;
5
+ }
6
+ export interface HailerClient {
7
+ socket: Client;
8
+ rest: HailerRestClient;
9
+ sessionKey: string;
10
+ }
11
+ /**
12
+ * Get the current user ID from the authenticated session
13
+ * This is called after authentication to retrieve the user ID automatically
14
+ */
15
+ export declare function getCurrentUserId(client: HailerClient): Promise<string>;
16
+ /** Hailer Client for a specific account with listeners and basic handling logic */
17
+ export declare class HailerClientManager {
18
+ private host;
19
+ private username;
20
+ private password;
21
+ private socketClient;
22
+ private restClient;
23
+ private signalHandlers;
24
+ constructor(host: string, username: string, password: string);
25
+ connect(): Promise<HailerClient>;
26
+ private setupSignalHandling;
27
+ onSignal(eventType: string, handler: (data: any) => void): void;
28
+ offSignal(eventType: string, handler: (data: any) => void): void;
29
+ disconnect(): void;
30
+ isConnected(): boolean;
31
+ getClient(): HailerClient | null;
32
+ }
33
+ /**
34
+ * Create Hailer client by API key (O(1) lookup)
35
+ * This is the new efficient method for MCP Server to get connections
36
+ */
37
+ export declare const createHailerClientByApiKey: (apiKey: string) => Promise<HailerClient>;
38
+ /**
39
+ * Disconnect client by API key (new unified approach)
40
+ */
41
+ export declare const disconnectHailerClientByApiKey: (apiKey: string) => void;
42
+ //# sourceMappingURL=hailer-clients.d.ts.map
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.disconnectHailerClientByApiKey = exports.createHailerClientByApiKey = exports.HailerClientManager = void 0;
4
+ exports.getCurrentUserId = getCurrentUserId;
5
+ const cli_1 = require("@hailer/cli");
6
+ const auth_1 = require("./auth");
7
+ const logger_1 = require("../lib/logger");
8
+ const config_1 = require("../config");
9
+ const logger = (0, logger_1.createLogger)({ component: 'hailer-clients' });
10
+ /**
11
+ * Get the current user ID from the authenticated session
12
+ * This is called after authentication to retrieve the user ID automatically
13
+ */
14
+ async function getCurrentUserId(client) {
15
+ try {
16
+ // Use the socket client to get initialization data with the current user
17
+ // According to the API docs, requesting "user" returns the current authenticated user
18
+ const init = (await client.socket.request("v2.core.init", [
19
+ ["user"],
20
+ ]));
21
+ // Check if there's a user field (this is the current authenticated user)
22
+ if (init.user && init.user._id) {
23
+ logger.info('Retrieved user ID from init', { userId: init.user._id });
24
+ return init.user._id;
25
+ }
26
+ // Fallback: Try with different parameters if the above doesn't work
27
+ logger.warn('User not found in init.user, trying alternative approach');
28
+ // Method 2: Try to get current user from a profile/me endpoint
29
+ try {
30
+ const profileResponse = await client.rest.fetch("/api/user/me", {
31
+ method: "GET",
32
+ });
33
+ if (profileResponse.ok) {
34
+ const profile = (await profileResponse.json());
35
+ if (profile && profile._id) {
36
+ logger.info('Retrieved user ID from profile endpoint', { userId: profile._id });
37
+ return profile._id;
38
+ }
39
+ }
40
+ }
41
+ catch (error) {
42
+ logger.warn('Profile endpoint not available, trying network method');
43
+ }
44
+ // Method 3: Try with network data to infer from workspace membership
45
+ const initWithNetwork = (await client.socket.request("v2.core.init", [
46
+ ["network"],
47
+ ]));
48
+ if (initWithNetwork.network && initWithNetwork.network.members) {
49
+ const members = initWithNetwork.network.members;
50
+ if (members.length === 1) {
51
+ // If there's only one member, it's likely the current user
52
+ const member = members[0];
53
+ const userId = member.uid || member._id || member.id;
54
+ if (userId) {
55
+ logger.info('Inferred user ID from single workspace member', { userId });
56
+ return userId;
57
+ }
58
+ }
59
+ }
60
+ // If all methods fail, throw an error with helpful information
61
+ throw new Error("Could not determine current user ID from any available method. Check API credentials and permissions.");
62
+ }
63
+ catch (error) {
64
+ logger.error('Failed to get current user ID', error);
65
+ throw new Error(`Failed to retrieve current user ID: ${error instanceof Error ? error.message : String(error)}`);
66
+ }
67
+ }
68
+ // manager is supposed to manage many instances, isn't it? a bit weird naming as for me but not sure
69
+ /** Hailer Client for a specific account with listeners and basic handling logic */
70
+ class HailerClientManager {
71
+ host;
72
+ username;
73
+ password;
74
+ socketClient = null;
75
+ restClient = null;
76
+ signalHandlers = new Map();
77
+ constructor(host, username, password) {
78
+ this.host = host;
79
+ this.username = username;
80
+ this.password = password;
81
+ }
82
+ async connect() {
83
+ // Check if this is a local development environment
84
+ const isLocalDev = this.host.includes("local.gd") || this.host.includes("localhost");
85
+ const clientOptions = {
86
+ host: this.host,
87
+ username: this.username,
88
+ password: this.password,
89
+ ...(isLocalDev && { rejectUnauthorized: false }), // Add for local dev with self-signed certs
90
+ };
91
+ try {
92
+ // Create socket client using @hailer/cli with timeout
93
+ this.socketClient = (await Promise.race([
94
+ cli_1.Client.create(clientOptions),
95
+ new Promise((_, reject) => setTimeout(() => reject(new Error("Socket connection timeout after 30s")), 30000)),
96
+ ]));
97
+ }
98
+ catch (error) {
99
+ logger.error('Failed to create socket client', error, { username: this.username });
100
+ throw error;
101
+ }
102
+ if (!this.socketClient.sessionKey) {
103
+ throw new Error("Socket client connected but no session key received");
104
+ }
105
+ // Create REST client that uses the same session key
106
+ this.restClient = {
107
+ fetch: (url, options = {}) => {
108
+ const headers = {
109
+ ...options.headers,
110
+ hlrkey: this.socketClient.sessionKey,
111
+ "Content-Type": "application/json",
112
+ };
113
+ return (0, auth_1.safeFetch)(url, { ...options, headers });
114
+ },
115
+ sessionKey: this.socketClient.sessionKey,
116
+ };
117
+ // Set up signal handling
118
+ this.setupSignalHandling();
119
+ return {
120
+ socket: this.socketClient,
121
+ rest: this.restClient,
122
+ sessionKey: this.socketClient.sessionKey,
123
+ };
124
+ }
125
+ setupSignalHandling() {
126
+ if (!this.socketClient)
127
+ return;
128
+ this.socketClient.on("signals", (signal) => {
129
+ const [eventType, eventData] = signal;
130
+ // Dispatch to registered handlers
131
+ const handlers = this.signalHandlers.get(eventType);
132
+ if (handlers) {
133
+ handlers.forEach((handler) => {
134
+ try {
135
+ handler(eventData);
136
+ }
137
+ catch (error) {
138
+ logger.error('Signal handler failed', error);
139
+ }
140
+ });
141
+ }
142
+ });
143
+ }
144
+ // Register a handler for specific signal types
145
+ onSignal(eventType, handler) {
146
+ if (!this.signalHandlers.has(eventType)) {
147
+ this.signalHandlers.set(eventType, []);
148
+ }
149
+ this.signalHandlers.get(eventType).push(handler);
150
+ }
151
+ // Remove a signal handler
152
+ offSignal(eventType, handler) {
153
+ const handlers = this.signalHandlers.get(eventType);
154
+ if (handlers) {
155
+ const index = handlers.indexOf(handler);
156
+ if (index > -1) {
157
+ handlers.splice(index, 1);
158
+ }
159
+ }
160
+ }
161
+ disconnect() {
162
+ if (this.socketClient) {
163
+ this.socketClient.disconnect();
164
+ this.socketClient = null;
165
+ }
166
+ this.restClient = null;
167
+ this.signalHandlers.clear();
168
+ }
169
+ isConnected() {
170
+ return this.socketClient !== null && this.restClient !== null;
171
+ }
172
+ getClient() {
173
+ if (!this.socketClient ||
174
+ !this.restClient ||
175
+ !this.socketClient.sessionKey) {
176
+ return null;
177
+ }
178
+ return {
179
+ socket: this.socketClient,
180
+ rest: this.restClient,
181
+ sessionKey: this.socketClient.sessionKey,
182
+ };
183
+ }
184
+ }
185
+ exports.HailerClientManager = HailerClientManager;
186
+ // Connection pool for the MCP server
187
+ const connectionPool = new Map();
188
+ /**
189
+ * Create Hailer client by API key (O(1) lookup)
190
+ * This is the new efficient method for MCP Server to get connections
191
+ */
192
+ const createHailerClientByApiKey = async (apiKey) => {
193
+ // O(1) lookup in Map-based CLIENT_CONFIGS
194
+ const account = config_1.environment.CLIENT_CONFIGS[apiKey];
195
+ if (!account) {
196
+ throw new Error(`No Hailer account found for API key: ${apiKey}`);
197
+ }
198
+ logger.debug('Creating clients for API key', {
199
+ apiKey: apiKey.substring(0, 8) + '...',
200
+ email: account.email,
201
+ host: account.apiBaseUrl
202
+ });
203
+ // Use API key as connection key for direct mapping
204
+ const connectionKey = apiKey;
205
+ // Check if we have an existing connection
206
+ let clientManager = connectionPool.get(connectionKey);
207
+ if (clientManager && clientManager.isConnected()) {
208
+ logger.info('Reusing existing connection', {
209
+ apiKey: apiKey.substring(0, 8) + '...',
210
+ email: account.email
211
+ });
212
+ return clientManager.getClient();
213
+ }
214
+ // Clear any stale connection
215
+ if (clientManager) {
216
+ logger.info('Cleaning up stale connection', {
217
+ apiKey: apiKey.substring(0, 8) + '...',
218
+ email: account.email
219
+ });
220
+ clientManager.disconnect();
221
+ connectionPool.delete(connectionKey);
222
+ }
223
+ // Create new connection
224
+ logger.info('Creating new connection', {
225
+ apiKey: apiKey.substring(0, 8) + '...',
226
+ email: account.email,
227
+ host: account.apiBaseUrl
228
+ });
229
+ clientManager = new HailerClientManager(account.apiBaseUrl, account.email, account.password);
230
+ await clientManager.connect();
231
+ connectionPool.set(connectionKey, clientManager);
232
+ return clientManager.getClient();
233
+ };
234
+ exports.createHailerClientByApiKey = createHailerClientByApiKey;
235
+ /**
236
+ * Disconnect client by API key (new unified approach)
237
+ */
238
+ const disconnectHailerClientByApiKey = (apiKey) => {
239
+ const clientManager = connectionPool.get(apiKey);
240
+ if (clientManager) {
241
+ clientManager.disconnect();
242
+ connectionPool.delete(apiKey);
243
+ }
244
+ };
245
+ exports.disconnectHailerClientByApiKey = disconnectHailerClientByApiKey;
246
+ //# sourceMappingURL=hailer-clients.js.map
@@ -0,0 +1,45 @@
1
+ import { WorkspaceCache } from './workspace-cache';
2
+ import { HailerClient } from './hailer-clients';
3
+ /**
4
+ * Signal types that Hailer emits via socket.io
5
+ */
6
+ export type HailerSignalType = 'activities.updated' | 'activities.created' | 'activities.deleted' | 'discussion.message' | 'messenger.new' | 'user.joined' | 'user.left' | 'workspace.updated' | 'process.updated' | 'cache.invalidate';
7
+ export interface HailerSignal {
8
+ type: HailerSignalType;
9
+ data: any;
10
+ timestamp: number;
11
+ workspaceId?: string;
12
+ }
13
+ export interface SignalSubscription {
14
+ id: string;
15
+ types: HailerSignalType[];
16
+ handler: (signal: HailerSignal) => void;
17
+ workspaceId?: string;
18
+ }
19
+ export declare class SignalHandler {
20
+ private client;
21
+ private workspaceCache?;
22
+ private subscriptions;
23
+ private signalHistory;
24
+ private maxHistorySize;
25
+ constructor(client: HailerClient, workspaceCache?: WorkspaceCache | undefined);
26
+ private initializeSignalHandling;
27
+ private handleIncomingSignal;
28
+ private addToHistory;
29
+ private dispatchSignal;
30
+ private processBuiltinSignals;
31
+ private handleActivityUpdated;
32
+ private handleActivityCreated;
33
+ private handleDiscussionMessage;
34
+ private handleWorkspaceUpdated;
35
+ private handleMessengerNew;
36
+ private handleCacheInvalidate;
37
+ subscribe(id: string, types: HailerSignalType[], handler: (signal: HailerSignal) => void, workspaceId?: string): void;
38
+ unsubscribe(id: string): boolean;
39
+ getSignalHistory(types?: HailerSignalType[], limit?: number, workspaceId?: string): HailerSignal[];
40
+ getActiveSubscriptions(): SignalSubscription[];
41
+ clearHistory(): void;
42
+ createMcpSignalTools(server: any): void;
43
+ }
44
+ export declare const createSignalHandler: (clients: HailerClient, workspaceCache?: WorkspaceCache) => SignalHandler;
45
+ //# sourceMappingURL=signal-handler.d.ts.map