@stackmemoryai/stackmemory 0.3.21 → 0.3.22

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.
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Pre-Tool-Use Hook for StackMemory
5
+ * Controls and filters tool usage based on configured policies
6
+ */
7
+
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+
15
+ // Configuration for tool restrictions
16
+ const CONFIG = {
17
+ // Mode: 'restrictive' | 'permissive' | 'code_only'
18
+ mode: process.env.STACKMEMORY_TOOL_MODE || 'permissive',
19
+
20
+ // Tools that are always allowed
21
+ alwaysAllowed: [
22
+ 'mcp__stackmemory__save_context',
23
+ 'mcp__stackmemory__load_context',
24
+ 'TodoWrite',
25
+ 'TodoRead',
26
+ ],
27
+
28
+ // Tools that are blocked in restrictive mode
29
+ restrictedTools: [
30
+ 'Bash',
31
+ 'Write',
32
+ 'Edit',
33
+ 'Delete',
34
+ 'WebFetch',
35
+ ],
36
+
37
+ // Code execution tools
38
+ codeExecutionTools: [
39
+ 'mcp__code-executor__execute_code',
40
+ 'mcp__stackmemory__code.execute',
41
+ ],
42
+
43
+ // Log file for audit
44
+ logFile: path.join(process.env.HOME || '/tmp', '.stackmemory', 'tool-use.log'),
45
+ };
46
+
47
+ // Read input from stdin
48
+ async function readInput() {
49
+ let input = '';
50
+ for await (const chunk of process.stdin) {
51
+ input += chunk;
52
+ }
53
+ return JSON.parse(input);
54
+ }
55
+
56
+ // Log tool usage
57
+ function logToolUse(toolName, allowed, reason) {
58
+ try {
59
+ const logDir = path.dirname(CONFIG.logFile);
60
+ if (!fs.existsSync(logDir)) {
61
+ fs.mkdirSync(logDir, { recursive: true });
62
+ }
63
+
64
+ const logEntry = {
65
+ timestamp: new Date().toISOString(),
66
+ tool: toolName,
67
+ allowed,
68
+ reason,
69
+ mode: CONFIG.mode,
70
+ };
71
+
72
+ fs.appendFileSync(CONFIG.logFile, JSON.stringify(logEntry) + '\n');
73
+ } catch (error) {
74
+ // Silently fail logging
75
+ }
76
+ }
77
+
78
+ // Check if tool should be allowed
79
+ function shouldAllowTool(toolName, params) {
80
+ // Always allowed tools
81
+ if (CONFIG.alwaysAllowed.includes(toolName)) {
82
+ return { allowed: true, reason: 'Always allowed tool' };
83
+ }
84
+
85
+ // Mode-specific logic
86
+ switch (CONFIG.mode) {
87
+ case 'code_only':
88
+ // Only allow code execution tools
89
+ if (CONFIG.codeExecutionTools.includes(toolName)) {
90
+ return { allowed: true, reason: 'Code execution tool in code_only mode' };
91
+ }
92
+ return {
93
+ allowed: false,
94
+ reason: 'Only code execution is allowed in code_only mode',
95
+ suggestion: 'Use mcp__stackmemory__code.execute to run Python/JavaScript code',
96
+ };
97
+
98
+ case 'restrictive':
99
+ // Block restricted tools
100
+ if (CONFIG.restrictedTools.includes(toolName)) {
101
+ return {
102
+ allowed: false,
103
+ reason: `Tool '${toolName}' is restricted in current mode`,
104
+ suggestion: 'This tool is blocked for safety. Consider using code execution instead.',
105
+ };
106
+ }
107
+ return { allowed: true, reason: 'Not in restricted list' };
108
+
109
+ case 'permissive':
110
+ default:
111
+ // Allow everything but log dangerous operations
112
+ if (CONFIG.restrictedTools.includes(toolName)) {
113
+ logToolUse(toolName, true, 'Potentially dangerous tool in permissive mode');
114
+ }
115
+ return { allowed: true, reason: 'Permissive mode' };
116
+ }
117
+ }
118
+
119
+ // Main hook logic
120
+ async function main() {
121
+ try {
122
+ const input = await readInput();
123
+ const { tool_name, parameters } = input;
124
+
125
+ // Check if tool should be allowed
126
+ const decision = shouldAllowTool(tool_name, parameters);
127
+
128
+ // Log the decision
129
+ logToolUse(tool_name, decision.allowed, decision.reason);
130
+
131
+ if (!decision.allowed) {
132
+ // Block the tool
133
+ console.error(JSON.stringify({
134
+ status: 'blocked',
135
+ tool: tool_name,
136
+ reason: decision.reason,
137
+ suggestion: decision.suggestion,
138
+ message: `Tool '${tool_name}' is blocked. ${decision.suggestion || ''}`,
139
+ }));
140
+ process.exit(1);
141
+ }
142
+
143
+ // Special handling for code execution
144
+ if (CONFIG.codeExecutionTools.includes(tool_name)) {
145
+ // Add safety warnings
146
+ if (parameters.code && parameters.code.includes('import os')) {
147
+ console.error(JSON.stringify({
148
+ status: 'warning',
149
+ message: 'Code contains potentially dangerous imports. Reviewing...',
150
+ }));
151
+ }
152
+ }
153
+
154
+ // Allow the tool
155
+ console.log(JSON.stringify({
156
+ status: 'allowed',
157
+ tool: tool_name,
158
+ mode: CONFIG.mode,
159
+ }));
160
+
161
+ } catch (error) {
162
+ // On error, allow by default (fail open)
163
+ console.error(JSON.stringify({
164
+ status: 'error',
165
+ message: error.message,
166
+ fallback: 'allowing tool due to hook error',
167
+ }));
168
+ }
169
+ }
170
+
171
+ // Handle environment info request
172
+ if (process.argv.includes('--info')) {
173
+ console.log(JSON.stringify({
174
+ hook: 'pre-tool-use',
175
+ version: '1.0.0',
176
+ mode: CONFIG.mode,
177
+ modes: ['permissive', 'restrictive', 'code_only'],
178
+ description: 'Controls tool usage based on configured policies',
179
+ }));
180
+ process.exit(0);
181
+ }
182
+
183
+ main().catch(error => {
184
+ console.error(JSON.stringify({
185
+ status: 'error',
186
+ message: error.message,
187
+ }));
188
+ process.exit(1);
189
+ });
@@ -1,91 +0,0 @@
1
- #!/usr/bin/env node
2
- import http from "http";
3
- const PORT = process.env.PORT || 3e3;
4
- const server = http.createServer(async (req, res) => {
5
- console.log(`${(/* @__PURE__ */ new Date()).toISOString()} ${req.method} ${req.url}`);
6
- if (req.url === "/health" || req.url === "/api/health") {
7
- res.writeHead(200, { "Content-Type": "application/json" });
8
- res.end(
9
- JSON.stringify({
10
- status: "healthy",
11
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12
- port: PORT,
13
- env: process.env.NODE_ENV || "development"
14
- })
15
- );
16
- } else if (req.url === "/test-db") {
17
- res.writeHead(200, { "Content-Type": "application/json" });
18
- const testResults = { postgresql: {}, redis: {} };
19
- if (process.env.DATABASE_URL) {
20
- try {
21
- const { Client } = (await import("pg")).default;
22
- const pgClient = new Client({ connectionString: process.env.DATABASE_URL });
23
- await pgClient.connect();
24
- const result = await pgClient.query("SELECT NOW() as time, version() as version");
25
- testResults.postgresql = {
26
- status: "connected",
27
- time: result.rows[0].time,
28
- version: result.rows[0].version.split(" ")[0]
29
- };
30
- await pgClient.end();
31
- } catch (error) {
32
- testResults.postgresql = { status: "error", message: error.message };
33
- }
34
- } else {
35
- testResults.postgresql = { status: "not_configured" };
36
- }
37
- const redisUrl = process.env.REDIS_URL || (process.env.REDISHOST ? `redis://${process.env.REDISHOST}:${process.env.REDISPORT || 6379}` : null);
38
- if (redisUrl) {
39
- try {
40
- const { createClient } = await import("redis");
41
- const redisClient = createClient({ url: redisUrl });
42
- await redisClient.connect();
43
- await redisClient.ping();
44
- const info = await redisClient.info("server");
45
- const version = info.match(/redis_version:(.+)/)?.[1];
46
- testResults.redis = {
47
- status: "connected",
48
- version,
49
- url: redisUrl.replace(/:\/\/[^@]+@/, "://***:***@")
50
- // Hide credentials
51
- };
52
- await redisClient.disconnect();
53
- } catch (error) {
54
- testResults.redis = { status: "error", message: error.message };
55
- }
56
- } else {
57
- testResults.redis = { status: "not_configured" };
58
- }
59
- res.end(JSON.stringify(testResults, null, 2));
60
- } else if (req.url === "/") {
61
- res.writeHead(200, { "Content-Type": "application/json" });
62
- res.end(
63
- JSON.stringify({
64
- message: "StackMemory Minimal Server Running",
65
- version: "1.0.0",
66
- endpoints: ["/health", "/api/health", "/test-db"]
67
- })
68
- );
69
- } else {
70
- res.writeHead(404, { "Content-Type": "application/json" });
71
- res.end(JSON.stringify({ error: "Not found" }));
72
- }
73
- });
74
- server.listen(PORT, "0.0.0.0", () => {
75
- console.log(`
76
- =================================
77
- Minimal Server Started
78
- Port: ${PORT}
79
- Time: ${(/* @__PURE__ */ new Date()).toISOString()}
80
- =================================
81
- `);
82
- });
83
- process.on("SIGTERM", () => {
84
- console.log("SIGTERM received");
85
- server.close(() => process.exit(0));
86
- });
87
- process.on("SIGINT", () => {
88
- console.log("SIGINT received");
89
- server.close(() => process.exit(0));
90
- });
91
- //# sourceMappingURL=minimal.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../src/servers/railway/minimal.ts"],
4
- "sourcesContent": ["#!/usr/bin/env node\n/**\n * Minimal Railway Server - Absolute minimum for testing\n */\n\nimport http from 'http';\n\nconst PORT = process.env.PORT || 3000;\n\nconst server = http.createServer(async (req, res) => {\n console.log(`${new Date().toISOString()} ${req.method} ${req.url}`);\n\n if (req.url === '/health' || req.url === '/api/health') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n status: 'healthy',\n timestamp: new Date().toISOString(),\n port: PORT,\n env: process.env.NODE_ENV || 'development',\n })\n );\n } else if (req.url === '/test-db') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n \n // Test database connections\n const testResults = { postgresql: {}, redis: {} };\n \n // Test PostgreSQL\n if (process.env.DATABASE_URL) {\n try {\n const { Client } = (await import('pg')).default;\n const pgClient = new Client({ connectionString: process.env.DATABASE_URL });\n await pgClient.connect();\n \n const result = await pgClient.query('SELECT NOW() as time, version() as version');\n testResults.postgresql = {\n status: 'connected',\n time: result.rows[0].time,\n version: result.rows[0].version.split(' ')[0]\n };\n \n await pgClient.end();\n } catch (error) {\n testResults.postgresql = { status: 'error', message: error.message };\n }\n } else {\n testResults.postgresql = { status: 'not_configured' };\n }\n \n // Test Redis\n const redisUrl = process.env.REDIS_URL || \n (process.env.REDISHOST ? `redis://${process.env.REDISHOST}:${process.env.REDISPORT || 6379}` : null);\n \n if (redisUrl) {\n try {\n const { createClient } = await import('redis');\n const redisClient = createClient({ url: redisUrl });\n await redisClient.connect();\n \n await redisClient.ping();\n const info = await redisClient.info('server');\n const version = info.match(/redis_version:(.+)/)?.[1];\n \n testResults.redis = {\n status: 'connected',\n version: version,\n url: redisUrl.replace(/:\\/\\/[^@]+@/, '://***:***@') // Hide credentials\n };\n \n await redisClient.disconnect();\n } catch (error) {\n testResults.redis = { status: 'error', message: error.message };\n }\n } else {\n testResults.redis = { status: 'not_configured' };\n }\n \n res.end(JSON.stringify(testResults, null, 2));\n } else if (req.url === '/') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(\n JSON.stringify({\n message: 'StackMemory Minimal Server Running',\n version: '1.0.0',\n endpoints: ['/health', '/api/health', '/test-db']\n })\n );\n } else {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Not found' }));\n }\n});\n\nserver.listen(PORT, '0.0.0.0', () => {\n console.log(`\n=================================\nMinimal Server Started\nPort: ${PORT}\nTime: ${new Date().toISOString()}\n=================================\n `);\n});\n\n// Keep alive\nprocess.on('SIGTERM', () => {\n console.log('SIGTERM received');\n server.close(() => process.exit(0));\n});\n\nprocess.on('SIGINT', () => {\n console.log('SIGINT received');\n server.close(() => process.exit(0));\n});\n"],
5
- "mappings": "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",
6
- "names": []
7
- }