botforje 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 (84) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +381 -0
  3. package/dist/action/action.js +44 -0
  4. package/dist/action/catalog.js +40 -0
  5. package/dist/action/cooldown.js +60 -0
  6. package/dist/action/executor.js +23 -0
  7. package/dist/action/template.js +13 -0
  8. package/dist/action/types.js +2 -0
  9. package/dist/action/webhook.js +36 -0
  10. package/dist/actions/action.js +26 -0
  11. package/dist/actions/cooldown.js +66 -0
  12. package/dist/actions/request.js +40 -0
  13. package/dist/api/routes/auth.js +56 -0
  14. package/dist/api/routes/bots.js +51 -0
  15. package/dist/api/routes/config.js +24 -0
  16. package/dist/api/routes/health.js +15 -0
  17. package/dist/api/routes/index.js +14 -0
  18. package/dist/api/routes/messages.js +69 -0
  19. package/dist/api/routes/sessions.js +97 -0
  20. package/dist/api/routes/status.js +38 -0
  21. package/dist/api/server.js +153 -0
  22. package/dist/auth/service.js +102 -0
  23. package/dist/boot/fleet.js +207 -0
  24. package/dist/bot/bot.js +30 -0
  25. package/dist/bot/fleet.js +211 -0
  26. package/dist/bot/fuzzy.js +20 -0
  27. package/dist/bot/mapper.js +47 -0
  28. package/dist/bot/types.js +2 -0
  29. package/dist/bot/validation.js +38 -0
  30. package/dist/bot.js +31 -0
  31. package/dist/cli/create-bot.js +84 -0
  32. package/dist/cli.js +138 -0
  33. package/dist/commands/auth.js +200 -0
  34. package/dist/commands/create-bot.js +64 -0
  35. package/dist/commands/guide.js +563 -0
  36. package/dist/commands/lock.js +73 -0
  37. package/dist/commands/status.js +145 -0
  38. package/dist/commands/unlock.js +88 -0
  39. package/dist/commands/validate.js +19 -0
  40. package/dist/config/mapper.js +152 -0
  41. package/dist/config/schema.js +2 -0
  42. package/dist/config/validation.js +705 -0
  43. package/dist/config/watcher.js +145 -0
  44. package/dist/config/yaml.js +197 -0
  45. package/dist/fleet.js +247 -0
  46. package/dist/flow/executor.js +286 -0
  47. package/dist/flow/flow.js +2 -0
  48. package/dist/flow/mapper.js +72 -0
  49. package/dist/flow/state.js +115 -0
  50. package/dist/flow/types.js +2 -0
  51. package/dist/graph/executor.js +294 -0
  52. package/dist/graph/graph.js +2 -0
  53. package/dist/graph/state.js +118 -0
  54. package/dist/helpers/data.js +42 -0
  55. package/dist/helpers/fuzzy.js +30 -0
  56. package/dist/helpers/logger.js +89 -0
  57. package/dist/helpers/validation.js +38 -0
  58. package/dist/index.js +92 -0
  59. package/dist/messages/contracts.js +2 -0
  60. package/dist/messages/inbox.js +58 -0
  61. package/dist/messages/outbox.js +136 -0
  62. package/dist/services/auto-response.js +62 -0
  63. package/dist/services/cooldown.js +60 -0
  64. package/dist/services/fleet.js +207 -0
  65. package/dist/services/flow-executor.js +195 -0
  66. package/dist/services/flow-state.js +118 -0
  67. package/dist/services/inbox.js +50 -0
  68. package/dist/services/message-handler.js +78 -0
  69. package/dist/services/message-queue.js +138 -0
  70. package/dist/services/outbox.js +138 -0
  71. package/dist/services/session.js +118 -0
  72. package/dist/services/webhook.js +87 -0
  73. package/dist/utils/logger.js +89 -0
  74. package/dist/utils/webhook.js +36 -0
  75. package/dist/validation/validate.js +592 -0
  76. package/dist/whatsapp/client.js +293 -0
  77. package/dist/whatsapp/session.js +158 -0
  78. package/dist/whatsapp/types.js +109 -0
  79. package/dist/whatsapp/whatsapp.js +109 -0
  80. package/package.json +97 -0
  81. package/scripts/postinstall.js +45 -0
  82. package/scripts/preuninstall.js +48 -0
  83. package/scripts/setup-systemd.js +91 -0
  84. package/service/botforje.service.template +25 -0
@@ -0,0 +1,294 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GraphExecutor = void 0;
4
+ const action_1 = require("../actions/action");
5
+ const action_2 = require("../actions/action");
6
+ const request_1 = require("../actions/request");
7
+ const fuzzy_1 = require("../helpers/fuzzy");
8
+ const logger_1 = require("../helpers/logger");
9
+ class GraphExecutor {
10
+ constructor(actionCatalog, graphCatalog, graphStateService, outboxService, defaultTimeout = 300, cooldownService) {
11
+ this.actionCatalog = actionCatalog;
12
+ this.graphCatalog = graphCatalog;
13
+ this.graphStateService = graphStateService;
14
+ this.outboxService = outboxService;
15
+ this.defaultTimeout = defaultTimeout;
16
+ this.cooldownService = cooldownService;
17
+ }
18
+ get logger() {
19
+ return (0, logger_1.getLogger)();
20
+ }
21
+ updateCatalogs(actionCatalog, graphCatalog) {
22
+ this.actionCatalog = actionCatalog;
23
+ this.graphCatalog = graphCatalog;
24
+ }
25
+ clearBotSessions(botId) {
26
+ this.graphStateService.clearBotSessions(botId);
27
+ }
28
+ async handleMessage(bot, message) {
29
+ this.graphStateService.cleanupExpired();
30
+ const state = this.graphStateService.findActive(message.from, bot.id);
31
+ if (state) {
32
+ return this.handleActiveState(bot, message, state);
33
+ }
34
+ return this.handleNewSession(bot, message);
35
+ }
36
+ async handleNewSession(bot, message) {
37
+ const graphId = bot.graph;
38
+ if (!graphId) {
39
+ this.logger.error(`Bot "${bot.id}" has no graph assigned`);
40
+ return false;
41
+ }
42
+ const graph = this.graphCatalog.get(graphId);
43
+ if (!graph) {
44
+ this.logger.error(`Graph "${graphId}" referenced by bot "${bot.id}" not found`);
45
+ return false;
46
+ }
47
+ const rootNode = graph.nodes[graph.root];
48
+ if (!rootNode) {
49
+ this.logger.error(`Graph "${graph.id}" root node "${graph.root}" not found`);
50
+ return false;
51
+ }
52
+ if (graph.fallbackNode && !graph.nodes[graph.fallbackNode]) {
53
+ this.logger.error(`Graph "${graph.id}" fallback node "${graph.fallbackNode}" not found`);
54
+ return false;
55
+ }
56
+ const timeout = graph.timeout ?? this.defaultTimeout;
57
+ const state = this.graphStateService.create(message.from, bot.id, graph.id, graph.root, timeout, Date.now(), { __visitedNodes: [graph.root] });
58
+ this.logger.debug(`[new] graph="${graph.id}" root="${graph.root}" action="${rootNode.action}"`);
59
+ this.logger.debug(` message: "${message.content}" from "${message.from}"`);
60
+ const rootConsumed = await this.executeNodeAction(bot, message, rootNode, {});
61
+ if (!rootConsumed) {
62
+ this.graphStateService.destroy(state.id);
63
+ return false;
64
+ }
65
+ return this.resolveFromActive(bot, message, state, graph);
66
+ }
67
+ async handleActiveState(bot, message, state) {
68
+ const graph = this.graphCatalog.get(state.graphId);
69
+ if (!graph) {
70
+ this.graphStateService.destroy(state.id);
71
+ return false;
72
+ }
73
+ const currentNode = graph.nodes[state.nodeId];
74
+ if (!currentNode) {
75
+ this.graphStateService.destroy(state.id);
76
+ return false;
77
+ }
78
+ this.logger.debug(`[active] graph="${graph.id}" node="${state.nodeId}" action="${currentNode.action}"`);
79
+ this.logger.debug(` message: "${message.content}" from "${message.from}"`);
80
+ return this.resolveFromActive(bot, message, state, graph);
81
+ }
82
+ async resolveFromActive(bot, message, state, graph) {
83
+ const match = this.findBestEdge(graph, state, message.content);
84
+ if (!match) {
85
+ if (graph.fallbackNode && graph.nodes[graph.fallbackNode]) {
86
+ await this.transitionToNode(bot, message, state, graph, graph.fallbackNode);
87
+ return true;
88
+ }
89
+ this.logger.debug(` no edge matched, staying on node "${state.nodeId}"`);
90
+ return true;
91
+ }
92
+ this.logger.debug(` >> goto="${match.edge.goto}" | node=${match.nodeIndex} score=${match.score.toFixed(3)}`);
93
+ await this.transitionToNode(bot, message, state, graph, match.edge.goto);
94
+ return true;
95
+ }
96
+ async transitionToNode(bot, message, state, graph, nodeId) {
97
+ const node = graph.nodes[nodeId];
98
+ if (!node) {
99
+ this.logger.error(`Node "${nodeId}" not found in graph "${graph.id}"`);
100
+ this.graphStateService.destroy(state.id);
101
+ return;
102
+ }
103
+ const vars = { ...state.variables };
104
+ const visited = vars.__visitedNodes ?? [];
105
+ if (nodeId === graph.root) {
106
+ vars.__visitedNodes = [graph.root];
107
+ }
108
+ else if (nodeId !== state.nodeId) {
109
+ const prevNode = graph.nodes[state.nodeId];
110
+ const hasNavEdges = prevNode?.edges.some(e => e.match && e.match.length > 0);
111
+ if (hasNavEdges) {
112
+ if (visited.includes(state.nodeId)) {
113
+ const reordered = visited.filter(n => n !== state.nodeId);
114
+ vars.__visitedNodes = [...reordered, state.nodeId];
115
+ }
116
+ else {
117
+ vars.__visitedNodes = [...visited, state.nodeId];
118
+ }
119
+ }
120
+ }
121
+ await this.executeNodeAction(bot, message, node, vars);
122
+ this.graphStateService.updateStep(state.id, nodeId, vars);
123
+ this.logger.debug(` visited updated: [${(vars.__visitedNodes ?? []).join(', ')}]`);
124
+ }
125
+ findBestEdge(graph, state, message) {
126
+ const visited = state.variables?.__visitedNodes ?? [];
127
+ const currentNode = graph.nodes[state.nodeId];
128
+ if (!currentNode)
129
+ return null;
130
+ this.logger.debug(` context: "${message}"`);
131
+ this.logger.debug(` current: ${state.nodeId} | visited: [${visited.join(', ')}]`);
132
+ const nodes = [];
133
+ const allNodeNames = [...new Set([...visited, state.nodeId])];
134
+ for (const nodeName of allNodeNames) {
135
+ const node = graph.nodes[nodeName];
136
+ if (node && nodeName !== state.nodeId) {
137
+ nodes.push({ edges: node.edges, nodeName });
138
+ }
139
+ }
140
+ nodes.push({ edges: currentNode.edges, nodeName: state.nodeId });
141
+ let best = null;
142
+ for (let ni = 0; ni < nodes.length; ni++) {
143
+ const node = nodes[ni];
144
+ const edgeList = node.edges || [];
145
+ const condEdges = edgeList.filter(e => e.match && e.match.length > 0);
146
+ if (condEdges.length === 0) {
147
+ this.logger.debug(` Node ${ni} ${node.nodeName}: 0 conditional edges`);
148
+ continue;
149
+ }
150
+ this.logger.debug(` Node ${ni} ${node.nodeName} — ${condEdges.length} edges:`);
151
+ for (let ei = 0; ei < condEdges.length; ei++) {
152
+ const edge = condEdges[ei];
153
+ const threshold = edge.fuzzyThreshold ?? 0.6;
154
+ const result = (0, fuzzy_1.matchFuzzyVerbose)(edge.match, message, threshold);
155
+ if (!result) {
156
+ this.logger.debug(` [${ei}] match="${edge.match}" → ${edge.goto}`);
157
+ continue;
158
+ }
159
+ const isBest = !best ||
160
+ result.score < best.score ||
161
+ (result.score === best.score && ni > best.nodeIndex);
162
+ const marker = isBest ? '◀ BEST' : '◀ worse';
163
+ this.logger.debug(` [${ei}] match="${edge.match}" → ${edge.goto} | match="${result.match}" score=${result.score.toFixed(3)} ${marker}`);
164
+ if (isBest) {
165
+ best = { edge, score: result.score, nodeIndex: ni };
166
+ }
167
+ }
168
+ }
169
+ if (best) {
170
+ this.logger.debug(` >> winner: Node ${best.nodeIndex} goto="${best.edge.goto}" score=${best.score.toFixed(3)}`);
171
+ return best;
172
+ }
173
+ const defaultEdge = currentNode.edges.find(e => !e.match || e.match.length === 0);
174
+ if (defaultEdge) {
175
+ this.logger.debug(` >> default → goto="${defaultEdge.goto}"`);
176
+ return { edge: defaultEdge, score: 1, nodeIndex: nodes.length - 1 };
177
+ }
178
+ this.logger.debug(` >> no match`);
179
+ return null;
180
+ }
181
+ async executeNodeAction(bot, message, node, variables) {
182
+ const context = {
183
+ botId: bot.id,
184
+ botName: bot.id,
185
+ senderPhone: message.from,
186
+ senderName: message.senderName,
187
+ message: message.content,
188
+ variables,
189
+ };
190
+ const action = (0, action_1.resolveAction)(this.actionCatalog, node.action);
191
+ if (this.isActionOnCooldown(action, message.from)) {
192
+ const onBlockedExecutedKey = `on_blocked.executed:${action.id}`;
193
+ const cooldown = action.guards?.cooldown;
194
+ if (cooldown && this.cooldownService?.isOnCooldown(message.from, onBlockedExecutedKey, cooldown.duration * 1000)) {
195
+ this.logger.debug(` on_blocked pipeline already ran this cooldown for action "${node.action}", skipping`);
196
+ return true;
197
+ }
198
+ const onBlocked = action.guards?.cooldown?.onBlocked;
199
+ if (onBlocked && onBlocked.length > 0) {
200
+ for (const step of onBlocked) {
201
+ await this.runStep(bot, message, step, context);
202
+ }
203
+ this.cooldownService?.setCooldown(message.from, onBlockedExecutedKey);
204
+ this.logger.info(`Cooldown on_blocked pipeline ran for action "${node.action}" to ${message.from}`);
205
+ }
206
+ else {
207
+ this.logger.warn(`Action "${node.action}" on cooldown, no on_blocked defined, skipping`);
208
+ }
209
+ return true;
210
+ }
211
+ for (const step of action.steps) {
212
+ await this.runStep(bot, message, step, context);
213
+ }
214
+ this.setActionCooldown(action, message.from);
215
+ this.cooldownService?.removeCooldown(message.from, `on_blocked.executed:${action.id}`);
216
+ return true;
217
+ }
218
+ async runStep(bot, message, step, context) {
219
+ if ('message' in step) {
220
+ const to = step.message.to ? (0, action_2.resolveVars)(step.message.to, context) : message.from;
221
+ const text = (0, action_2.resolveVars)(step.message.body, context);
222
+ this.outboxService.enqueue(bot.id, to, text);
223
+ this.logger.debug(` message step → to=${to} text="${text.substring(0, 40)}"`);
224
+ return;
225
+ }
226
+ if ('location' in step) {
227
+ this.outboxService.enqueue(bot.id, message.from, '', {
228
+ type: 'location',
229
+ latitude: step.location.latitude,
230
+ longitude: step.location.longitude,
231
+ name: step.location.name,
232
+ address: step.location.address,
233
+ url: step.location.url,
234
+ description: step.location.description,
235
+ });
236
+ this.logger.debug(` location step → lat=${step.location.latitude} lng=${step.location.longitude}`);
237
+ return;
238
+ }
239
+ if ('request' in step) {
240
+ const resolvedUrl = (0, action_2.resolveVars)(step.request.url, context);
241
+ try {
242
+ const payload = this.buildRequestPayload(bot, message, step.request.name || 'unnamed');
243
+ await (0, request_1.sendRequest)({
244
+ url: resolvedUrl,
245
+ method: step.request.method,
246
+ headers: step.request.headers,
247
+ body: payload,
248
+ timeout: step.request.timeout,
249
+ retries: step.request.retries,
250
+ });
251
+ }
252
+ catch (error) {
253
+ const msg = error instanceof Error ? error.message : String(error);
254
+ this.logger.error(`Failed to execute request step for bot "${bot.id}" url="${resolvedUrl}": ${msg}`);
255
+ }
256
+ return;
257
+ }
258
+ }
259
+ isActionOnCooldown(action, sender) {
260
+ if (!this.cooldownService) {
261
+ return false;
262
+ }
263
+ const cooldown = action.guards?.cooldown;
264
+ if (!cooldown || cooldown.duration <= 0) {
265
+ return false;
266
+ }
267
+ const cooldownMs = cooldown.duration * 1000;
268
+ const cooldownKey = `action:${action.id}`;
269
+ return this.cooldownService.isOnCooldown(sender, cooldownKey, cooldownMs);
270
+ }
271
+ setActionCooldown(action, sender) {
272
+ if (!this.cooldownService) {
273
+ return;
274
+ }
275
+ const cooldown = action.guards?.cooldown;
276
+ if (!cooldown || cooldown.duration <= 0) {
277
+ return;
278
+ }
279
+ this.cooldownService.setCooldown(sender, `action:${action.id}`);
280
+ }
281
+ buildRequestPayload(bot, message, requestName) {
282
+ return {
283
+ senderPhone: message.from,
284
+ senderName: message.senderName,
285
+ message: message.content,
286
+ timestamp: message.timestamp.toISOString(),
287
+ botId: bot.id,
288
+ botName: bot.id,
289
+ requestName,
290
+ metadata: message.metadata || {},
291
+ };
292
+ }
293
+ }
294
+ exports.GraphExecutor = GraphExecutor;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GraphStateService = void 0;
4
+ const node_sqlite_1 = require("node:sqlite");
5
+ class GraphStateService {
6
+ constructor(dbPath) {
7
+ this.db = new node_sqlite_1.DatabaseSync(dbPath);
8
+ this.setupSchema();
9
+ }
10
+ setupSchema() {
11
+ this.db.exec(`
12
+ CREATE TABLE IF NOT EXISTS graph_states (
13
+ id TEXT PRIMARY KEY,
14
+ sender TEXT NOT NULL,
15
+ bot_id TEXT NOT NULL,
16
+ graph_id TEXT NOT NULL,
17
+ node_id TEXT NOT NULL,
18
+ variables TEXT NOT NULL DEFAULT '{}',
19
+ started_at INTEGER NOT NULL,
20
+ last_activity_at INTEGER NOT NULL,
21
+ timeout INTEGER NOT NULL
22
+ );
23
+
24
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_graph_states_sender_bot ON graph_states(sender, bot_id);
25
+ CREATE INDEX IF NOT EXISTS idx_graph_states_last_activity ON graph_states(last_activity_at);
26
+ `);
27
+ }
28
+ findActive(sender, botId, now = Date.now()) {
29
+ const row = this.db
30
+ .prepare('SELECT * FROM graph_states WHERE sender = ? AND bot_id = ?')
31
+ .get(sender, botId);
32
+ if (!row) {
33
+ return null;
34
+ }
35
+ const state = this.rowToState(row);
36
+ if (this.isExpired(state, now)) {
37
+ this.destroy(state.id);
38
+ return null;
39
+ }
40
+ return state;
41
+ }
42
+ create(sender, botId, graphId, nodeId, timeout, now = Date.now(), initialVariables) {
43
+ this.destroyBySenderBot(sender, botId);
44
+ const id = `${botId}-${sender}-${now}`;
45
+ const state = {
46
+ id,
47
+ sender,
48
+ botId,
49
+ graphId,
50
+ nodeId,
51
+ variables: { ...initialVariables },
52
+ startedAt: now,
53
+ lastActivityAt: now,
54
+ timeout,
55
+ };
56
+ this.db
57
+ .prepare('INSERT INTO graph_states (id, sender, bot_id, graph_id, node_id, variables, started_at, last_activity_at, timeout) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)')
58
+ .run(state.id, state.sender, state.botId, state.graphId, state.nodeId, JSON.stringify(state.variables), state.startedAt, state.lastActivityAt, state.timeout);
59
+ return state;
60
+ }
61
+ updateStep(id, nodeId, variables, now = Date.now()) {
62
+ const state = this.findById(id);
63
+ if (!state) {
64
+ return;
65
+ }
66
+ const mergedVariables = variables ? { ...state.variables, ...variables } : state.variables;
67
+ this.db
68
+ .prepare('UPDATE graph_states SET node_id = ?, variables = ?, last_activity_at = ? WHERE id = ?')
69
+ .run(nodeId, JSON.stringify(mergedVariables), now, id);
70
+ }
71
+ destroy(id) {
72
+ this.db.prepare('DELETE FROM graph_states WHERE id = ?').run(id);
73
+ }
74
+ destroyBySenderBot(sender, botId) {
75
+ this.db.prepare('DELETE FROM graph_states WHERE sender = ? AND bot_id = ?').run(sender, botId);
76
+ }
77
+ clearBotSessions(botId) {
78
+ this.db.prepare('DELETE FROM graph_states WHERE bot_id = ?').run(botId);
79
+ }
80
+ cleanupExpired(now = Date.now()) {
81
+ const cutoff = now;
82
+ const result = this.db
83
+ .prepare('DELETE FROM graph_states WHERE (last_activity_at + (timeout * 1000)) < ?')
84
+ .run(cutoff);
85
+ return Number(result.changes);
86
+ }
87
+ count() {
88
+ const row = this.db.prepare('SELECT COUNT(*) as count FROM graph_states').get();
89
+ return row.count;
90
+ }
91
+ close() {
92
+ this.db.close();
93
+ }
94
+ findById(id) {
95
+ const row = this.db.prepare('SELECT * FROM graph_states WHERE id = ?').get(id);
96
+ return row ? this.rowToState(row) : null;
97
+ }
98
+ rowToState(row) {
99
+ return {
100
+ id: row.id,
101
+ sender: row.sender,
102
+ botId: row.bot_id,
103
+ graphId: row.graph_id,
104
+ nodeId: row.node_id,
105
+ variables: JSON.parse(row.variables || '{}'),
106
+ startedAt: row.started_at,
107
+ lastActivityAt: row.last_activity_at,
108
+ timeout: row.timeout,
109
+ };
110
+ }
111
+ isExpired(state, now) {
112
+ if (state.timeout <= 0) {
113
+ return false;
114
+ }
115
+ return now > state.lastActivityAt + state.timeout * 1000;
116
+ }
117
+ }
118
+ exports.GraphStateService = GraphStateService;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getDataDir = getDataDir;
37
+ const path = __importStar(require("path"));
38
+ const os = __importStar(require("os"));
39
+ function getDataDir() {
40
+ const dataHome = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share');
41
+ return path.join(dataHome, 'botforje');
42
+ }
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.matchFuzzy = matchFuzzy;
7
+ exports.matchFuzzyVerbose = matchFuzzyVerbose;
8
+ const fuse_js_1 = __importDefault(require("fuse.js"));
9
+ function matchFuzzy(segments, message, threshold) {
10
+ const result = matchFuzzyVerbose(segments, message, threshold);
11
+ return result?.match || null;
12
+ }
13
+ function matchFuzzyVerbose(segments, message, threshold) {
14
+ if (segments.length === 0)
15
+ return null;
16
+ const fuse = new fuse_js_1.default(segments, {
17
+ includeScore: true,
18
+ threshold,
19
+ });
20
+ const results = fuse.search(message);
21
+ if (results.length > 0 && results[0].score !== undefined && results[0].score <= threshold) {
22
+ return {
23
+ match: segments[results[0].refIndex],
24
+ phrase: message,
25
+ score: results[0].score,
26
+ threshold,
27
+ };
28
+ }
29
+ return null;
30
+ }
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.defaultLogger = void 0;
7
+ exports.createLogger = createLogger;
8
+ exports.getLogger = getLogger;
9
+ exports.setGlobalLogger = setGlobalLogger;
10
+ const pino_1 = __importDefault(require("pino"));
11
+ function getCallerInfo() {
12
+ const isDevelopment = process.env.NODE_ENV !== 'production';
13
+ if (!isDevelopment)
14
+ return undefined;
15
+ const originalPrepareStackTrace = Error.prepareStackTrace;
16
+ try {
17
+ const err = new Error();
18
+ Error.prepareStackTrace = (_, stack) => stack;
19
+ const stack = err.stack;
20
+ const caller = stack[2];
21
+ if (caller) {
22
+ const fileName = caller.getFileName() || 'unknown';
23
+ const lineNumber = caller.getLineNumber() || 0;
24
+ const functionName = caller.getFunctionName() || 'anonymous';
25
+ const shortFileName = fileName.split('/').pop() || fileName;
26
+ return {
27
+ file: shortFileName,
28
+ line: lineNumber,
29
+ func: functionName,
30
+ };
31
+ }
32
+ }
33
+ catch (e) {
34
+ }
35
+ finally {
36
+ Error.prepareStackTrace = originalPrepareStackTrace;
37
+ }
38
+ return undefined;
39
+ }
40
+ function createLogger(logLevel = 'info') {
41
+ const isDevelopment = process.env.NODE_ENV !== 'production';
42
+ const logger = (0, pino_1.default)({
43
+ level: logLevel,
44
+ transport: isDevelopment
45
+ ? {
46
+ target: 'pino-pretty',
47
+ options: {
48
+ colorize: true,
49
+ translateTime: 'SYS:yyyy-mm-dd HH:MM:ss',
50
+ ignore: 'pid,hostname,caller',
51
+ messageFormat: '{if caller}[{caller.file}:{caller.line}]{end} {msg}',
52
+ singleLine: false,
53
+ hideObject: true,
54
+ },
55
+ }
56
+ : undefined,
57
+ formatters: {
58
+ level: label => ({ level: label }),
59
+ },
60
+ timestamp: pino_1.default.stdTimeFunctions.isoTime,
61
+ });
62
+ return {
63
+ debug: (message, ...args) => {
64
+ const caller = getCallerInfo();
65
+ logger.debug({ ...(args.length > 0 ? { args } : {}), ...(caller ? { caller } : {}) }, message);
66
+ },
67
+ info: (message, ...args) => {
68
+ const caller = getCallerInfo();
69
+ logger.info({ ...(args.length > 0 ? { args } : {}), ...(caller ? { caller } : {}) }, message);
70
+ },
71
+ warn: (message, ...args) => {
72
+ const caller = getCallerInfo();
73
+ logger.warn({ ...(args.length > 0 ? { args } : {}), ...(caller ? { caller } : {}) }, message);
74
+ },
75
+ error: (message, ...args) => {
76
+ const caller = getCallerInfo();
77
+ logger.error({ ...(args.length > 0 ? { args } : {}), ...(caller ? { caller } : {}) }, message);
78
+ },
79
+ };
80
+ }
81
+ let globalLogger = createLogger('info');
82
+ function getLogger() {
83
+ return globalLogger;
84
+ }
85
+ function setGlobalLogger(config) {
86
+ const logLevel = config?.log_level || 'info';
87
+ globalLogger = createLogger(logLevel);
88
+ }
89
+ exports.defaultLogger = createLogger('info');
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateId = validateId;
4
+ exports.validatePriority = validatePriority;
5
+ exports.validateTypingDelay = validateTypingDelay;
6
+ exports.validateQueueDelay = validateQueueDelay;
7
+ function validateId(id, kind) {
8
+ if (!id || id.length < 3) {
9
+ throw new Error(`${kind} ID must be at least 3 characters long`);
10
+ }
11
+ if (!/^[a-z0-9-]+$/.test(id)) {
12
+ throw new Error(`${kind} ID can only contain lowercase letters, numbers and hyphens`);
13
+ }
14
+ if (/^[-]/.test(id)) {
15
+ throw new Error(`${kind} ID cannot start with a hyphen`);
16
+ }
17
+ if (/[-]$/.test(id)) {
18
+ throw new Error(`${kind} ID cannot end with a hyphen`);
19
+ }
20
+ if (/--/.test(id)) {
21
+ throw new Error(`${kind} ID cannot contain consecutive hyphens`);
22
+ }
23
+ }
24
+ function validatePriority(priority) {
25
+ if (priority < 0) {
26
+ throw new Error('Priority must be non-negative');
27
+ }
28
+ }
29
+ function validateTypingDelay(delay) {
30
+ if (delay < 0) {
31
+ throw new Error('Typing delay must be non-negative');
32
+ }
33
+ }
34
+ function validateQueueDelay(delay) {
35
+ if (delay < 0) {
36
+ throw new Error('Queue delay must be non-negative');
37
+ }
38
+ }