@hailer/mcp 1.0.22 → 1.0.24

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.
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
  /**
3
- * Webhook Handler for Bot Config Updates
3
+ * Webhook Handler Utilities
4
4
  *
5
- * Receives activity updates from Hailer workflow webhooks and updates
6
- * local .bot-config/{workspaceId}.json files.
5
+ * Provides webhook token generation and verification for secure endpoints.
7
6
  */
8
7
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
8
  if (k2 === undefined) k2 = k;
@@ -43,15 +42,10 @@ exports.generateWebhookSignature = generateWebhookSignature;
43
42
  exports.verifyWebhookSignature = verifyWebhookSignature;
44
43
  exports.getWebhookToken = getWebhookToken;
45
44
  exports.getWebhookPath = getWebhookPath;
46
- exports.onBotUpdate = onBotUpdate;
47
- exports.handleBotConfigWebhook = handleBotConfigWebhook;
48
- exports.getWorkspaceConfig = getWorkspaceConfig;
49
- exports.listWorkspaceConfigs = listWorkspaceConfigs;
50
45
  const fs = __importStar(require("fs"));
51
46
  const path = __importStar(require("path"));
52
47
  const crypto = __importStar(require("crypto"));
53
48
  const logger_1 = require("../lib/logger");
54
- const config_1 = require("../config");
55
49
  const logger = (0, logger_1.createLogger)({ component: 'webhook-handler' });
56
50
  const BOT_CONFIG_DIR = '.bot-config';
57
51
  const WEBHOOK_SECRET_FILE = 'webhook-secret.txt';
@@ -62,15 +56,12 @@ const WEBHOOK_SECRET_FILE = 'webhook-secret.txt';
62
56
  * Constant-time string comparison to prevent timing attacks
63
57
  */
64
58
  function timingSafeEqual(a, b) {
65
- const bufA = Buffer.from(a);
66
- const bufB = Buffer.from(b);
67
- if (bufA.length !== bufB.length) {
68
- // Compare against dummy buffer of same length to maintain constant time
69
- const dummy = Buffer.alloc(bufA.length);
70
- crypto.timingSafeEqual(bufA, dummy);
59
+ if (a.length !== b.length) {
60
+ // Still perform comparison to maintain constant time
61
+ crypto.timingSafeEqual(Buffer.from(a), Buffer.from(a));
71
62
  return false;
72
63
  }
73
- return crypto.timingSafeEqual(bufA, bufB);
64
+ return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
74
65
  }
75
66
  /**
76
67
  * Generate HMAC-SHA256 signature for webhook payload
@@ -141,208 +132,10 @@ function getWebhookToken() {
141
132
  return token;
142
133
  }
143
134
  /**
144
- * Get the full webhook path with token (no prefix for security through obscurity)
135
+ * Get the full webhook path with token
145
136
  */
146
137
  function getWebhookPath() {
147
138
  const token = getWebhookToken();
148
139
  return token ? `/${token}` : null;
149
140
  }
150
- let botUpdateCallback = null;
151
- function onBotUpdate(callback) {
152
- botUpdateCallback = callback;
153
- }
154
- /**
155
- * Get field value from webhook payload by key
156
- */
157
- function getFieldValue(fields, key) {
158
- const field = fields.find((f) => f.key === key);
159
- return field?.value ?? null;
160
- }
161
- /**
162
- * Load existing workspace config or create empty one
163
- */
164
- function loadWorkspaceConfig(workspaceId) {
165
- const configDir = path.join(process.cwd(), BOT_CONFIG_DIR);
166
- const configPath = path.join(configDir, `${workspaceId}.json`);
167
- if (fs.existsSync(configPath)) {
168
- try {
169
- return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
170
- }
171
- catch (error) {
172
- logger.warn('Failed to load workspace config', { workspaceId, error: String(error) });
173
- }
174
- }
175
- return {
176
- workspaceId,
177
- workspaceName: workspaceId,
178
- specialists: [],
179
- lastSynced: new Date().toISOString(),
180
- };
181
- }
182
- /**
183
- * Save workspace config to file
184
- */
185
- function saveWorkspaceConfig(config) {
186
- const configDir = path.join(process.cwd(), BOT_CONFIG_DIR);
187
- // Ensure directory exists
188
- if (!fs.existsSync(configDir)) {
189
- fs.mkdirSync(configDir, { recursive: true });
190
- }
191
- const configPath = path.join(configDir, `${config.workspaceId}.json`);
192
- config.lastSynced = new Date().toISOString();
193
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
194
- logger.info('Saved workspace config', {
195
- workspaceId: config.workspaceId,
196
- path: configPath,
197
- });
198
- }
199
- /**
200
- * Process webhook payload and update workspace config
201
- */
202
- function handleBotConfigWebhook(payload) {
203
- const workspaceId = payload.cid;
204
- logger.info('Processing bot config webhook', {
205
- activityId: payload._id,
206
- activityName: payload.name,
207
- workspaceId,
208
- phase: payload.currentPhase,
209
- });
210
- // Extract fields
211
- const email = getFieldValue(payload.fields, 'agentEmailInHailer');
212
- const password = getFieldValue(payload.fields, 'password');
213
- const botType = getFieldValue(payload.fields, 'botType');
214
- const userId = getFieldValue(payload.fields, 'hailerProfile');
215
- const schemaConfigStr = getFieldValue(payload.fields, 'schemaConfig');
216
- // Validate required fields
217
- if (!email || !password) {
218
- logger.warn('Webhook missing credentials', {
219
- activityId: payload._id,
220
- hasEmail: !!email,
221
- hasPassword: !!password,
222
- });
223
- return {
224
- success: false,
225
- action: 'skip',
226
- workspaceId,
227
- botType,
228
- error: 'Missing email or password',
229
- };
230
- }
231
- // Parse schema config to determine if deployed or retired
232
- let deployedPhaseId = null;
233
- let retiredPhaseId = null;
234
- if (schemaConfigStr) {
235
- try {
236
- const schemaConfig = JSON.parse(schemaConfigStr);
237
- deployedPhaseId = schemaConfig.deployedPhaseId;
238
- retiredPhaseId = schemaConfig.retiredPhaseId;
239
- }
240
- catch (e) {
241
- logger.warn('Failed to parse schemaConfig', { schemaConfigStr });
242
- }
243
- }
244
- const isDeployed = deployedPhaseId ? payload.currentPhase === deployedPhaseId : true;
245
- const isRetired = retiredPhaseId ? payload.currentPhase === retiredPhaseId : false;
246
- const enabled = isDeployed && !isRetired;
247
- // Load existing config
248
- const config = loadWorkspaceConfig(workspaceId);
249
- const botEntry = {
250
- activityId: payload._id,
251
- userId: userId || null,
252
- email,
253
- password,
254
- botType: botType || 'unknown',
255
- enabled,
256
- displayName: payload.name, // Activity name from Agent Directory
257
- };
258
- let action;
259
- // Handle orchestrator
260
- if (botType === 'orchestrator') {
261
- if (enabled) {
262
- config.orchestrator = {
263
- activityId: payload._id,
264
- userId: userId || '',
265
- email,
266
- password,
267
- displayName: payload.name,
268
- };
269
- action = 'update';
270
- logger.info('Updated orchestrator', { workspaceId, email: (0, config_1.maskEmail)(email), displayName: payload.name });
271
- }
272
- else {
273
- // Orchestrator disabled - remove it
274
- if (config.orchestrator?.activityId === payload._id) {
275
- delete config.orchestrator;
276
- action = 'remove';
277
- logger.info('Removed orchestrator', { workspaceId, email: (0, config_1.maskEmail)(email) });
278
- }
279
- else {
280
- action = 'update';
281
- }
282
- }
283
- }
284
- else {
285
- // Handle specialist
286
- const existingIndex = config.specialists.findIndex((s) => s.activityId === payload._id);
287
- if (existingIndex >= 0) {
288
- // Update existing
289
- config.specialists[existingIndex] = botEntry;
290
- action = enabled ? 'update' : 'remove';
291
- }
292
- else if (enabled) {
293
- // Add new
294
- config.specialists.push(botEntry);
295
- action = 'add';
296
- }
297
- else {
298
- action = 'update';
299
- }
300
- logger.info('Updated specialist', {
301
- workspaceId,
302
- email,
303
- botType,
304
- enabled,
305
- action,
306
- });
307
- }
308
- // Save config
309
- saveWorkspaceConfig(config);
310
- // Trigger callback for hot reload
311
- if (botUpdateCallback) {
312
- botUpdateCallback(workspaceId, botEntry, action);
313
- }
314
- return {
315
- success: true,
316
- action,
317
- workspaceId,
318
- botType,
319
- };
320
- }
321
- /**
322
- * Get workspace config (for debugging/status)
323
- */
324
- function getWorkspaceConfig(workspaceId) {
325
- const config = loadWorkspaceConfig(workspaceId);
326
- return config.orchestrator || config.specialists.length > 0 ? config : null;
327
- }
328
- /**
329
- * List all workspace configs
330
- */
331
- function listWorkspaceConfigs() {
332
- const configDir = path.join(process.cwd(), BOT_CONFIG_DIR);
333
- if (!fs.existsSync(configDir))
334
- return [];
335
- const files = fs.readdirSync(configDir).filter((f) => f.endsWith('.json'));
336
- const configs = [];
337
- for (const file of files) {
338
- try {
339
- const content = fs.readFileSync(path.join(configDir, file), 'utf-8');
340
- configs.push(JSON.parse(content));
341
- }
342
- catch (error) {
343
- logger.warn('Failed to load workspace config', { file, error: String(error) });
344
- }
345
- }
346
- return configs;
347
- }
348
141
  //# sourceMappingURL=webhook-handler.js.map
@@ -16,10 +16,6 @@ export interface MCPServerConfig {
16
16
  port: number;
17
17
  corsOrigins: string[];
18
18
  toolRegistry: ToolRegistry;
19
- getDaemonStatus?: () => Record<string, Array<{
20
- botId: string;
21
- state: any;
22
- }>>;
23
19
  }
24
20
  export declare class MCPServerService {
25
21
  private app;
@@ -5,39 +5,6 @@
5
5
  * Implements JSON-RPC 2.0 MCP protocol over HTTP with Server-Sent Events (SSE)
6
6
  * for LLM clients (Claude Desktop, etc.)
7
7
  */
8
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
- if (k2 === undefined) k2 = k;
10
- var desc = Object.getOwnPropertyDescriptor(m, k);
11
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
- desc = { enumerable: true, get: function() { return m[k]; } };
13
- }
14
- Object.defineProperty(o, k2, desc);
15
- }) : (function(o, m, k, k2) {
16
- if (k2 === undefined) k2 = k;
17
- o[k2] = m[k];
18
- }));
19
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
- Object.defineProperty(o, "default", { enumerable: true, value: v });
21
- }) : function(o, v) {
22
- o["default"] = v;
23
- });
24
- var __importStar = (this && this.__importStar) || (function () {
25
- var ownKeys = function(o) {
26
- ownKeys = Object.getOwnPropertyNames || function (o) {
27
- var ar = [];
28
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
- return ar;
30
- };
31
- return ownKeys(o);
32
- };
33
- return function (mod) {
34
- if (mod && mod.__esModule) return mod;
35
- var result = {};
36
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
- __setModuleDefault(result, mod);
38
- return result;
39
- };
40
- })();
41
8
  var __importDefault = (this && this.__importDefault) || function (mod) {
42
9
  return (mod && mod.__esModule) ? mod : { "default": mod };
43
10
  };
@@ -49,13 +16,12 @@ const logger_1 = require("./lib/logger");
49
16
  const config_1 = require("./config");
50
17
  const UserContextCache_1 = require("./mcp/UserContextCache");
51
18
  const tool_registry_1 = require("./mcp/tool-registry");
52
- const webhook_handler_1 = require("./mcp/webhook-handler");
53
19
  class MCPServerService {
54
20
  app;
55
21
  server;
56
22
  logger;
57
23
  config;
58
- toolRegistry; // ← Injected
24
+ toolRegistry;
59
25
  constructor(config) {
60
26
  this.config = config;
61
27
  this.toolRegistry = config.toolRegistry;
@@ -121,31 +87,15 @@ class MCPServerService {
121
87
  status: 'ok',
122
88
  timestamp: new Date().toISOString(),
123
89
  service: 'hailer-mcp-server',
124
- version: config_1.APP_VERSION
90
+ version: '0.1.0'
125
91
  };
126
92
  res.json(health);
127
93
  });
128
- // Daemon status endpoint - monitor LLM context
129
- this.app.get('/daemon/status', (_, res) => {
130
- if (!this.config.getDaemonStatus) {
131
- res.status(404).json({ error: 'Daemon mode not enabled' });
132
- return;
133
- }
134
- const status = this.config.getDaemonStatus();
135
- if (!status) {
136
- res.status(503).json({ error: 'Daemon not running' });
137
- return;
138
- }
139
- res.json({
140
- timestamp: new Date().toISOString(),
141
- daemons: status
142
- });
143
- });
144
- // MCP Protocol handler - shared by both routes
145
- const mcpHandler = async (req, res, apiKeyOverride) => {
146
- const apiKey = apiKeyOverride || req.query.apiKey;
147
- req.logger.debug('MCP request received', { method: req.body?.method, apiKey: apiKey?.slice(0, 8) + '...' });
94
+ // MCP Protocol endpoint - JSON-RPC 2.0 over SSE
95
+ this.app.post('/api/mcp', async (req, res) => {
96
+ req.logger.debug('MCP request received', { method: req.body?.method });
148
97
  try {
98
+ const apiKey = req.query.apiKey;
149
99
  const mcpRequest = req.body;
150
100
  let result;
151
101
  if (mcpRequest.method === 'tools/list') {
@@ -168,23 +118,19 @@ class MCPServerService {
168
118
  }
169
119
  // Apply default tool group filtering
170
120
  // - NUCLEAR: Only if ENABLE_NUCLEAR_TOOLS=true
171
- // - BOT_INTERNAL: Only if explicitly requested via params.includeBotInternal (for daemons)
172
- const includeBotInternal = mcpRequest.params?.includeBotInternal === true;
173
121
  if (!filterConfig) {
174
- // No filter yet - create default excluding NUCLEAR and BOT_INTERNAL
122
+ // No filter yet - create default excluding NUCLEAR
175
123
  filterConfig = {
176
124
  allowedGroups: [tool_registry_1.ToolGroup.READ, tool_registry_1.ToolGroup.WRITE, tool_registry_1.ToolGroup.PLAYGROUND]
177
125
  };
178
- req.logger.debug('Using default tool filter (excludes NUCLEAR and BOT_INTERNAL)');
126
+ req.logger.debug('Using default tool filter (excludes NUCLEAR)');
179
127
  }
180
128
  else if (filterConfig.allowedGroups) {
181
- // Filter groups - remove BOT_INTERNAL unless explicitly requested, remove NUCLEAR unless enabled
182
- filterConfig.allowedGroups = filterConfig.allowedGroups.filter(g => (includeBotInternal || g !== tool_registry_1.ToolGroup.BOT_INTERNAL) &&
183
- (config_1.environment.ENABLE_NUCLEAR_TOOLS || g !== tool_registry_1.ToolGroup.NUCLEAR));
129
+ // Filter groups - remove NUCLEAR unless enabled
130
+ filterConfig.allowedGroups = filterConfig.allowedGroups.filter(g => (config_1.environment.ENABLE_NUCLEAR_TOOLS || g !== tool_registry_1.ToolGroup.NUCLEAR));
184
131
  req.logger.debug('Filtered tool groups', {
185
132
  allowedGroups: filterConfig.allowedGroups,
186
- nuclearEnabled: config_1.environment.ENABLE_NUCLEAR_TOOLS,
187
- includeBotInternal
133
+ nuclearEnabled: config_1.environment.ENABLE_NUCLEAR_TOOLS
188
134
  });
189
135
  }
190
136
  result = {
@@ -225,7 +171,7 @@ class MCPServerService {
225
171
  capabilities: { tools: {} },
226
172
  serverInfo: {
227
173
  name: 'hailer-mcp-server',
228
- version: config_1.APP_VERSION
174
+ version: '1.0.0'
229
175
  }
230
176
  };
231
177
  }
@@ -264,170 +210,13 @@ class MCPServerService {
264
210
  this.sendMcpError(res, req.body?.id || null, -32000, `Server error: ${errorMessage}`, 500);
265
211
  }
266
212
  }
267
- };
268
- // MCP Protocol endpoint - JSON-RPC 2.0 over SSE
269
- // Route 1: /api/mcp?apiKey=xxx (standard format)
270
- this.app.post('/api/mcp', (req, res) => mcpHandler(req, res));
271
- // Route 2: /:apiKey (simplified format - API key as path)
272
- // Matches 16-64 char alphanumeric keys, but ONLY for MCP requests (has jsonrpc field)
273
- // Non-MCP requests (webhooks) pass through to later routes
274
- this.app.post('/:apiKey([a-zA-Z0-9_-]{16,64})', (req, res, next) => {
275
- if (req.body?.jsonrpc) {
276
- // MCP request - handle it
277
- mcpHandler(req, res, req.params.apiKey);
278
- }
279
- else {
280
- // Not MCP (likely webhook) - pass to next route
281
- next();
282
- }
283
213
  });
284
- // ===== Bot Configuration API (only when MCP_CLIENT_ENABLED=true) =====
285
- if (config_1.environment.MCP_CLIENT_ENABLED) {
286
- // GET /api/bots - List all bots and their status
287
- this.app.get('/api/bots', async (req, res) => {
288
- req.logger.debug('List bots requested');
289
- try {
290
- const { AVAILABLE_BOTS, getBotState } = await Promise.resolve().then(() => __importStar(require('./bot-config')));
291
- const state = getBotState();
292
- const bots = AVAILABLE_BOTS.map(bot => ({
293
- ...bot,
294
- enabled: state[bot.id] || false
295
- }));
296
- res.json({ bots });
297
- }
298
- catch (error) {
299
- req.logger.error('Failed to list bots', { error });
300
- res.status(500).json({ error: 'Failed to list bots' });
301
- }
302
- });
303
- // POST /api/bots/:id/enable - Enable a bot
304
- this.app.post('/api/bots/:id/enable', async (req, res) => {
305
- const { id } = req.params;
306
- req.logger.info('Enable bot requested', { botId: id });
307
- try {
308
- const { AVAILABLE_BOTS, setBotEnabled } = await Promise.resolve().then(() => __importStar(require('./bot-config')));
309
- const bot = AVAILABLE_BOTS.find(b => b.id === id);
310
- if (!bot) {
311
- return res.status(404).json({ error: `Unknown bot: ${id}` });
312
- }
313
- setBotEnabled(id, true);
314
- res.json({ success: true, botId: id, enabled: true });
315
- }
316
- catch (error) {
317
- req.logger.error('Failed to enable bot', { botId: id, error });
318
- res.status(500).json({ error: 'Failed to enable bot' });
319
- }
320
- });
321
- // POST /api/bots/:id/disable - Disable a bot
322
- this.app.post('/api/bots/:id/disable', async (req, res) => {
323
- const { id } = req.params;
324
- req.logger.info('Disable bot requested', { botId: id });
325
- try {
326
- const { AVAILABLE_BOTS, setBotEnabled } = await Promise.resolve().then(() => __importStar(require('./bot-config')));
327
- const bot = AVAILABLE_BOTS.find(b => b.id === id);
328
- if (!bot) {
329
- return res.status(404).json({ error: `Unknown bot: ${id}` });
330
- }
331
- setBotEnabled(id, false);
332
- res.json({ success: true, botId: id, enabled: false });
333
- }
334
- catch (error) {
335
- req.logger.error('Failed to disable bot', { botId: id, error });
336
- res.status(500).json({ error: 'Failed to disable bot' });
337
- }
338
- });
339
- // POST /api/bots/:id/toggle - Toggle a bot
340
- this.app.post('/api/bots/:id/toggle', async (req, res) => {
341
- const { id } = req.params;
342
- req.logger.info('Toggle bot requested', { botId: id });
343
- try {
344
- const { AVAILABLE_BOTS, getBotState, setBotEnabled } = await Promise.resolve().then(() => __importStar(require('./bot-config')));
345
- const bot = AVAILABLE_BOTS.find(b => b.id === id);
346
- if (!bot) {
347
- return res.status(404).json({ error: `Unknown bot: ${id}` });
348
- }
349
- const currentState = getBotState();
350
- const newState = !currentState[id];
351
- setBotEnabled(id, newState);
352
- res.json({ success: true, botId: id, enabled: newState });
353
- }
354
- catch (error) {
355
- req.logger.error('Failed to toggle bot', { botId: id, error });
356
- res.status(500).json({ error: 'Failed to toggle bot' });
357
- }
358
- });
359
- // ===== Bot Config Webhook API =====
360
- // Get secure webhook path (auto-generated token) - null if disabled
361
- const webhookPath = (0, webhook_handler_1.getWebhookPath)();
362
- if (webhookPath) {
363
- // POST /webhook/{token} - Receives updates from Hailer workflow webhooks
364
- this.app.post(webhookPath, (req, res) => {
365
- req.logger.info('Bot config webhook received', {
366
- activityId: req.body?._id,
367
- activityName: req.body?.name,
368
- workspaceId: req.body?.cid,
369
- });
370
- try {
371
- const result = (0, webhook_handler_1.handleBotConfigWebhook)(req.body);
372
- if (result.success) {
373
- req.logger.info('Bot config updated via webhook', {
374
- action: result.action,
375
- workspaceId: result.workspaceId,
376
- botType: result.botType,
377
- });
378
- res.status(200).json(result);
379
- }
380
- else {
381
- req.logger.warn('Bot config webhook failed', { error: result.error });
382
- res.status(400).json(result);
383
- }
384
- }
385
- catch (error) {
386
- req.logger.error('Bot config webhook error', { error });
387
- res.status(500).json({
388
- success: false,
389
- error: error instanceof Error ? error.message : 'Internal error',
390
- });
391
- }
392
- });
393
- // GET /webhook/{token}/status - Status endpoint to see all workspace configs
394
- this.app.get(`${webhookPath}/status`, (_req, res) => {
395
- const configs = (0, webhook_handler_1.listWorkspaceConfigs)();
396
- res.json({
397
- timestamp: new Date().toISOString(),
398
- workspaceCount: configs.length,
399
- workspaces: configs.map((c) => ({
400
- workspaceId: c.workspaceId,
401
- workspaceName: c.workspaceName,
402
- hasOrchestrator: !!c.orchestrator,
403
- specialistCount: c.specialists.length,
404
- enabledSpecialists: c.specialists.filter((s) => s.enabled).length,
405
- lastSynced: c.lastSynced,
406
- })),
407
- });
408
- });
409
- }
410
- else {
411
- this.logger.info('Webhook endpoint disabled (no WEBHOOK_TOKEN)');
412
- }
413
- this.logger.debug('Routes configured', {
414
- routes: [
415
- '/health',
416
- '/daemon/status',
417
- '/api/mcp',
418
- '/api/bots'
419
- ]
420
- });
421
- }
422
- else {
423
- this.logger.debug('Routes configured', {
424
- routes: [
425
- '/health',
426
- '/daemon/status',
427
- '/api/mcp'
428
- ]
429
- });
430
- }
214
+ this.logger.debug('Routes configured', {
215
+ routes: [
216
+ '/health',
217
+ '/api/mcp'
218
+ ]
219
+ });
431
220
  }
432
221
  /**
433
222
  * Check if agent has access to a specific tool
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hailer/mcp",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "config": {
5
5
  "docker": {
6
6
  "registry": "registry.gitlab.com/hailer-repos/hailer-mcp"
@@ -10,22 +10,20 @@
10
10
  "dev": "tsx watch src/app.ts",
11
11
  "claude-code-mcp": "DISABLE_CLIENT=true tsx src/app.ts",
12
12
  "start-mcp-only": "DISABLE_CLIENT=true tsx src/app.ts",
13
- "start-client-only": "DISABLE_MCP_SERVER=true tsx src/app.ts",
14
13
  "build": "tsc",
15
14
  "start": "node dist/app.js",
16
15
  "lint": "eslint src/",
17
- "build-dev-push": "rm -rf build/docker-dev && docker build --target artifacts --output build/docker-dev . -f Dockerfile.build-dev && docker buildx build --push --platform linux/amd64,linux/arm64/v8 -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs | sed 's/@hailer\\//hailer-/'):$(npm pkg get version | xargs)-dev -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs | sed 's/@hailer\\//hailer-/'):dev -f ./Dockerfile.dev .",
18
- "build-debug-push": "docker buildx build --push --platform linux/amd64,linux/arm64/v8 -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs | sed 's/@hailer\\//hailer-/'):$(npm pkg get version | xargs)-debug -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs | sed 's/@hailer\\//hailer-/'):debug -f ./Dockerfile.debug .",
19
- "build-prod-push": "rm -rf build/docker-prod && docker build --target artifacts --output build/docker-prod . -f Dockerfile.build-prod && docker buildx build --push --platform linux/amd64,linux/arm64/v8 -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs | sed 's/@hailer\\//hailer-/'):$(npm pkg get version | xargs) -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs | sed 's/@hailer\\//hailer-/'):prod -f ./Dockerfile.prod .",
20
- "build-k3d": "docker build -f Dockerfile.debug . -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs | sed 's/@hailer\\//hailer-/'):$(npm pkg get version | xargs) && k3d image import $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs | sed 's/@hailer\\//hailer-/'):$(npm pkg get version | xargs) -c hailer",
21
- "build-k3d-dummy": "docker build --target artifacts --output build/docker-dev . -f Dockerfile.build-dev && docker build -f Dockerfile.dev . -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs | sed 's/@hailer\\//hailer-/'):$(npm pkg get version | xargs)-dev && k3d image import $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs | sed 's/@hailer\\//hailer-/'):$(npm pkg get version | xargs)-dev -c hailer",
16
+ "build-dev-push": "rm -rf build/docker-dev && docker build --target artifacts --output build/docker-dev . -f Dockerfile.build-dev && docker buildx build --push --platform linux/amd64,linux/arm64/v8 -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs):$(npm pkg get version | xargs)-dev -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs):dev -f ./Dockerfile.dev .",
17
+ "build-debug-push": "docker buildx build --push --platform linux/amd64,linux/arm64/v8 -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs):$(npm pkg get version | xargs)-debug -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs):debug -f ./Dockerfile.debug .",
18
+ "build-prod-push": "rm -rf build/docker-prod && docker build --target artifacts --output build/docker-prod . -f Dockerfile.build-prod && docker buildx build --push --platform linux/amd64,linux/arm64/v8 -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs):$(npm pkg get version | xargs) -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs):prod -f ./Dockerfile.prod .",
19
+ "build-k3d": "docker build -f Dockerfile.debug . -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs):$(npm pkg get version | xargs) && k3d image import $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs):$(npm pkg get version | xargs) -c hailer",
20
+ "build-k3d-dummy": "docker build --target artifacts --output build/docker-dev . -f Dockerfile.build-dev && docker build -f Dockerfile.dev . -t $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs):$(npm pkg get version | xargs)-dev && k3d image import $(npm pkg get config.docker.registry | xargs)/$(npm pkg get name | xargs):$(npm pkg get version | xargs)-dev -c hailer",
22
21
  "mcp-server": "hailer-mcp",
23
- "server": "tsx src/client/server.ts",
24
- "server:prod": "node dist/client/server.js",
25
22
  "release:patch": "npm version patch -m 'chore: release v%s' && git push && git push --tags && npm run build && npm publish --access public",
26
23
  "release:minor": "npm version minor -m 'chore: release v%s' && git push && git push --tags && npm run build && npm publish --access public",
27
24
  "release:major": "npm version major -m 'chore: release v%s' && git push && git push --tags && npm run build && npm publish --access public",
28
- "seed-config": "tsx src/commands/seed-config.ts"
25
+ "seed-config": "tsx src/commands/seed-config.ts",
26
+ "bot": "tsx src/bot/chat-bot.ts"
29
27
  },
30
28
  "dependencies": {
31
29
  "@anthropic-ai/sdk": "^0.54.0",
@@ -37,6 +35,7 @@
37
35
  "@opentelemetry/sdk-logs": "^0.57.0",
38
36
  "@opentelemetry/sdk-node": "^0.57.0",
39
37
  "@opentelemetry/semantic-conventions": "^1.36.0",
38
+ "axios": "^1.13.4",
40
39
  "cors": "^2.8.5",
41
40
  "dotenv": "^16.5.0",
42
41
  "express": "^4.21.2",