@heimdall-ai/heimdall 0.1.0

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 (67) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +471 -0
  3. package/dist/config/constants.d.ts +24 -0
  4. package/dist/config/constants.d.ts.map +1 -0
  5. package/dist/config/constants.js +70 -0
  6. package/dist/config/constants.js.map +1 -0
  7. package/dist/core/bash-manager.d.ts +56 -0
  8. package/dist/core/bash-manager.d.ts.map +1 -0
  9. package/dist/core/bash-manager.js +106 -0
  10. package/dist/core/bash-manager.js.map +1 -0
  11. package/dist/core/pyodide-manager.d.ts +125 -0
  12. package/dist/core/pyodide-manager.d.ts.map +1 -0
  13. package/dist/core/pyodide-manager.js +669 -0
  14. package/dist/core/pyodide-manager.js.map +1 -0
  15. package/dist/core/pyodide-worker.d.ts +9 -0
  16. package/dist/core/pyodide-worker.d.ts.map +1 -0
  17. package/dist/core/pyodide-worker.js +295 -0
  18. package/dist/core/pyodide-worker.js.map +1 -0
  19. package/dist/core/secure-fs.d.ts +101 -0
  20. package/dist/core/secure-fs.d.ts.map +1 -0
  21. package/dist/core/secure-fs.js +279 -0
  22. package/dist/core/secure-fs.js.map +1 -0
  23. package/dist/integration.test.d.ts +10 -0
  24. package/dist/integration.test.d.ts.map +1 -0
  25. package/dist/integration.test.js +439 -0
  26. package/dist/integration.test.js.map +1 -0
  27. package/dist/resources/index.d.ts +12 -0
  28. package/dist/resources/index.d.ts.map +1 -0
  29. package/dist/resources/index.js +13 -0
  30. package/dist/resources/index.js.map +1 -0
  31. package/dist/resources/workspace.d.ts +12 -0
  32. package/dist/resources/workspace.d.ts.map +1 -0
  33. package/dist/resources/workspace.js +105 -0
  34. package/dist/resources/workspace.js.map +1 -0
  35. package/dist/server.d.ts +17 -0
  36. package/dist/server.d.ts.map +1 -0
  37. package/dist/server.js +51 -0
  38. package/dist/server.js.map +1 -0
  39. package/dist/tools/bash-execution.d.ts +13 -0
  40. package/dist/tools/bash-execution.d.ts.map +1 -0
  41. package/dist/tools/bash-execution.js +135 -0
  42. package/dist/tools/bash-execution.js.map +1 -0
  43. package/dist/tools/filesystem.d.ts +12 -0
  44. package/dist/tools/filesystem.d.ts.map +1 -0
  45. package/dist/tools/filesystem.js +104 -0
  46. package/dist/tools/filesystem.js.map +1 -0
  47. package/dist/tools/index.d.ts +13 -0
  48. package/dist/tools/index.d.ts.map +1 -0
  49. package/dist/tools/index.js +17 -0
  50. package/dist/tools/index.js.map +1 -0
  51. package/dist/tools/python-execution.d.ts +12 -0
  52. package/dist/tools/python-execution.d.ts.map +1 -0
  53. package/dist/tools/python-execution.js +77 -0
  54. package/dist/tools/python-execution.js.map +1 -0
  55. package/dist/types/index.d.ts +64 -0
  56. package/dist/types/index.d.ts.map +1 -0
  57. package/dist/types/index.js +2 -0
  58. package/dist/types/index.js.map +1 -0
  59. package/dist/utils/async-lock.d.ts +35 -0
  60. package/dist/utils/async-lock.d.ts.map +1 -0
  61. package/dist/utils/async-lock.js +57 -0
  62. package/dist/utils/async-lock.js.map +1 -0
  63. package/dist/utils/index.d.ts +5 -0
  64. package/dist/utils/index.d.ts.map +1 -0
  65. package/dist/utils/index.js +5 -0
  66. package/dist/utils/index.js.map +1 -0
  67. package/package.json +61 -0
package/dist/server.js ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Heimdall MCP Server
4
+ *
5
+ * A TypeScript MCP server providing sandboxed Python and Bash execution.
6
+ * Named after the Norse god who guards the Bifröst bridge, Heimdall watches
7
+ * over code execution with security and vigilance.
8
+ *
9
+ * Features:
10
+ * - Secure Python execution via Pyodide (WebAssembly sandbox)
11
+ * - Bash command execution via just-bash
12
+ * - Virtual filesystem with host sync
13
+ * - Package installation via micropip
14
+ * - Session persistence
15
+ */
16
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18
+ import { PyodideManager } from "./core/pyodide-manager.js";
19
+ import { BashManager } from "./core/bash-manager.js";
20
+ import { registerAllTools } from "./tools/index.js";
21
+ import { registerAllResources } from "./resources/index.js";
22
+ /**
23
+ * Main entry point
24
+ */
25
+ async function main() {
26
+ console.error("[Heimdall] Starting server...");
27
+ // Create manager instances
28
+ const pyodideManager = new PyodideManager();
29
+ const bashManager = new BashManager();
30
+ // Create MCP server
31
+ const server = new McpServer({
32
+ name: "heimdall",
33
+ version: "1.0.0",
34
+ });
35
+ // Register all tools and resources
36
+ registerAllTools(server, pyodideManager, bashManager);
37
+ registerAllResources(server, pyodideManager);
38
+ // Pre-initialize managers (optional, improves first tool call latency)
39
+ // Note: Disabled for now - managers will initialize lazily on first use
40
+ // await pyodideManager.initialize();
41
+ // await bashManager.initialize();
42
+ // Connect transport
43
+ const transport = new StdioServerTransport();
44
+ await server.connect(transport);
45
+ console.error("[Heimdall] Server connected and ready");
46
+ }
47
+ main().catch((error) => {
48
+ console.error("[Heimdall] Fatal error:", error);
49
+ process.exit(1);
50
+ });
51
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAE5D;;GAEG;AACH,KAAK,UAAU,IAAI;IACjB,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAE/C,2BAA2B;IAC3B,MAAM,cAAc,GAAG,IAAI,cAAc,EAAE,CAAC;IAC5C,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC;IAEtC,oBAAoB;IACpB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,OAAO;KACjB,CAAC,CAAC;IAEH,mCAAmC;IACnC,gBAAgB,CAAC,MAAM,EAAE,cAAc,EAAE,WAAW,CAAC,CAAC;IACtD,oBAAoB,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAE7C,uEAAuE;IACvE,wEAAwE;IACxE,qCAAqC;IACrC,kCAAkC;IAElC,oBAAoB;IACpB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,OAAO,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC;AACzD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACrB,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;IAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Bash Execution Tools
3
+ *
4
+ * Provides tools for executing bash commands in the Heimdall environment
5
+ */
6
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import type { BashManager } from "../core/bash-manager.js";
8
+ import type { PyodideManager } from "../core/pyodide-manager.js";
9
+ /**
10
+ * Register bash execution tools with the MCP server
11
+ */
12
+ export declare function registerBashExecutionTools(server: McpServer, bashManager: BashManager, pyodideManager: PyodideManager): void;
13
+ //# sourceMappingURL=bash-execution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash-execution.d.ts","sourceRoot":"","sources":["../../src/tools/bash-execution.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAIjE;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,WAAW,EACxB,cAAc,EAAE,cAAc,GAC7B,IAAI,CAgIN"}
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Bash Execution Tools
3
+ *
4
+ * Provides tools for executing bash commands in the Heimdall environment
5
+ */
6
+ import { z } from "zod";
7
+ import { posix as path } from "path";
8
+ /**
9
+ * Register bash execution tools with the MCP server
10
+ */
11
+ export function registerBashExecutionTools(server, bashManager, pyodideManager) {
12
+ server.registerTool("execute_bash", {
13
+ title: "Execute Bash",
14
+ description: `Execute a bash command in the Heimdall environment.
15
+
16
+ Features:
17
+ - Supports 50+ built-in commands: grep, sed, awk, find, jq, curl, tar, etc.
18
+ - Pipes and redirections: |, >, >>, 2>, 2>&1
19
+ - Variables, loops, conditionals, and functions
20
+ - File operations: ls, cat, cp, mv, rm, mkdir, etc.
21
+ - Text processing: grep, sed, awk, cut, sort, uniq, wc, etc.
22
+ - Data tools: jq (JSON), sqlite3 (SQLite), xan (CSV), yq (YAML)
23
+ - Find and search: find, grep, rg (ripgrep)
24
+
25
+ The command runs with access to the /workspace directory. All file changes are immediately visible to Python code.
26
+
27
+ Security:
28
+ - No real processes spawned (TypeScript simulation)
29
+ - Execution limits prevent infinite loops
30
+ - Network access disabled by default
31
+ - Filesystem limited to workspace directory
32
+
33
+ Examples:
34
+ - Find files: "find . -name '*.py' -type f"
35
+ - Process text: "cat data.txt | grep 'pattern' | wc -l"
36
+ - JSON query: "cat data.json | jq '.users[] | {name, email}'"
37
+ - Multiple commands: "ls -la && cat README.md | head -10"`,
38
+ inputSchema: {
39
+ command: z.string().describe("The bash command to execute"),
40
+ cwd: z
41
+ .string()
42
+ .optional()
43
+ .describe("Working directory (relative to /workspace, e.g., 'subdir' or '.')"),
44
+ },
45
+ }, async ({ command, cwd }) => {
46
+ try {
47
+ // Validate cwd to prevent path traversal attacks
48
+ if (cwd) {
49
+ // Normalize the path to resolve any '..' components
50
+ const normalizedCwd = path.normalize(cwd);
51
+ // Check if it's an absolute path
52
+ if (path.isAbsolute(normalizedCwd)) {
53
+ // Absolute paths must be exactly /workspace or start with /workspace/
54
+ if (normalizedCwd !== "/workspace" && !normalizedCwd.startsWith("/workspace/")) {
55
+ return {
56
+ content: [
57
+ {
58
+ type: "text",
59
+ text: `Invalid working directory: Absolute paths must be within /workspace (got: ${normalizedCwd})`,
60
+ },
61
+ ],
62
+ isError: true,
63
+ };
64
+ }
65
+ }
66
+ else {
67
+ // Relative paths: resolve from /workspace and check if it stays within
68
+ const resolvedPath = path.join("/workspace", normalizedCwd);
69
+ if (resolvedPath !== "/workspace" && !resolvedPath.startsWith("/workspace/")) {
70
+ return {
71
+ content: [
72
+ {
73
+ type: "text",
74
+ text: `Invalid working directory: Path traversal detected (resolved to: ${resolvedPath})`,
75
+ },
76
+ ],
77
+ isError: true,
78
+ };
79
+ }
80
+ }
81
+ }
82
+ // Execute bash command
83
+ const result = await bashManager.execute(command, { cwd });
84
+ // Sync changes to Pyodide virtual filesystem
85
+ // This ensures Python sees any file modifications made by bash
86
+ await pyodideManager.syncHostToVirtual();
87
+ // Combine stdout and stderr for output
88
+ let output = "";
89
+ if (result.stdout) {
90
+ output += result.stdout;
91
+ }
92
+ if (result.stderr) {
93
+ if (output)
94
+ output += "\n";
95
+ output += result.stderr;
96
+ }
97
+ // Return based on exit code
98
+ if (result.exitCode === 0) {
99
+ return {
100
+ content: [
101
+ {
102
+ type: "text",
103
+ text: output || "Command executed successfully (no output)",
104
+ },
105
+ ],
106
+ };
107
+ }
108
+ else {
109
+ // Non-zero exit code indicates failure
110
+ return {
111
+ content: [
112
+ {
113
+ type: "text",
114
+ text: `Command failed with exit code ${result.exitCode}\n${output}`,
115
+ },
116
+ ],
117
+ isError: true,
118
+ };
119
+ }
120
+ }
121
+ catch (error) {
122
+ const errorMessage = error instanceof Error ? error.message : String(error);
123
+ return {
124
+ content: [
125
+ {
126
+ type: "text",
127
+ text: `Error executing bash command: ${errorMessage}`,
128
+ },
129
+ ],
130
+ isError: true,
131
+ };
132
+ }
133
+ });
134
+ }
135
+ //# sourceMappingURL=bash-execution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bash-execution.js","sourceRoot":"","sources":["../../src/tools/bash-execution.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,KAAK,IAAI,IAAI,EAAE,MAAM,MAAM,CAAC;AAErC;;GAEG;AACH,MAAM,UAAU,0BAA0B,CACxC,MAAiB,EACjB,WAAwB,EACxB,cAA8B;IAE9B,MAAM,CAAC,YAAY,CACjB,cAAc,EACd;QACE,KAAK,EAAE,cAAc;QACrB,WAAW,EAAE;;;;;;;;;;;;;;;;;;;;;;;0DAuBuC;QACpD,WAAW,EAAE;YACX,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,6BAA6B,CAAC;YAC3D,GAAG,EAAE,CAAC;iBACH,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,mEAAmE,CAAC;SACjF;KACF,EACD,KAAK,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE;QACzB,IAAI,CAAC;YACH,iDAAiD;YACjD,IAAI,GAAG,EAAE,CAAC;gBACR,oDAAoD;gBACpD,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBAE1C,iCAAiC;gBACjC,IAAI,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;oBACnC,sEAAsE;oBACtE,IAAI,aAAa,KAAK,YAAY,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;wBAC/E,OAAO;4BACL,OAAO,EAAE;gCACP;oCACE,IAAI,EAAE,MAAM;oCACZ,IAAI,EAAE,6EAA6E,aAAa,GAAG;iCACpG;6BACF;4BACD,OAAO,EAAE,IAAI;yBACd,CAAC;oBACJ,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,uEAAuE;oBACvE,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;oBAC5D,IAAI,YAAY,KAAK,YAAY,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;wBAC7E,OAAO;4BACL,OAAO,EAAE;gCACP;oCACE,IAAI,EAAE,MAAM;oCACZ,IAAI,EAAE,oEAAoE,YAAY,GAAG;iCAC1F;6BACF;4BACD,OAAO,EAAE,IAAI;yBACd,CAAC;oBACJ,CAAC;gBACH,CAAC;YACH,CAAC;YAED,uBAAuB;YACvB,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;YAE3D,6CAA6C;YAC7C,+DAA+D;YAC/D,MAAM,cAAc,CAAC,iBAAiB,EAAE,CAAC;YAEzC,uCAAuC;YACvC,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC;YAC1B,CAAC;YACD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,IAAI,MAAM;oBAAE,MAAM,IAAI,IAAI,CAAC;gBAC3B,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC;YAC1B,CAAC;YAED,4BAA4B;YAC5B,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;gBAC1B,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,MAAM,IAAI,2CAA2C;yBAC5D;qBACF;iBACF,CAAC;YACJ,CAAC;iBAAM,CAAC;gBACN,uCAAuC;gBACvC,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,iCAAiC,MAAM,CAAC,QAAQ,KAAK,MAAM,EAAE;yBACpE;qBACF;oBACD,OAAO,EAAE,IAAI;iBACd,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5E,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,iCAAiC,YAAY,EAAE;qBACtD;iBACF;gBACD,OAAO,EAAE,IAAI;aACd,CAAC;QACJ,CAAC;IACH,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Filesystem Tools
3
+ *
4
+ * MCP tools for file system operations in the Heimdall workspace
5
+ */
6
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import type { PyodideManager } from "../core/pyodide-manager.js";
8
+ /**
9
+ * Register filesystem tools with the MCP server
10
+ */
11
+ export declare function registerFilesystemTools(server: McpServer, pyodideManager: PyodideManager): void;
12
+ //# sourceMappingURL=filesystem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filesystem.d.ts","sourceRoot":"","sources":["../../src/tools/filesystem.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,cAAc,QAmHxF"}
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Filesystem Tools
3
+ *
4
+ * MCP tools for file system operations in the Heimdall workspace
5
+ */
6
+ import { z } from "zod";
7
+ /**
8
+ * Register filesystem tools with the MCP server
9
+ */
10
+ export function registerFilesystemTools(server, pyodideManager) {
11
+ // Write file
12
+ server.registerTool("write_file", {
13
+ title: "Write File",
14
+ description: `Write content to a file in the Heimdall workspace.
15
+
16
+ Creates parent directories automatically. Files persist between executions.`,
17
+ inputSchema: {
18
+ path: z.string().describe("File path relative to workspace"),
19
+ content: z.string().describe("Content to write"),
20
+ },
21
+ }, async ({ path: filePath, content }) => {
22
+ const result = await pyodideManager.writeFile(filePath, content);
23
+ return {
24
+ content: [
25
+ {
26
+ type: "text",
27
+ text: result.success ? `✓ Written to ${filePath}` : `✗ Error: ${result.error}`,
28
+ },
29
+ ],
30
+ structuredContent: result,
31
+ };
32
+ });
33
+ // Read file
34
+ server.registerTool("read_file", {
35
+ title: "Read File",
36
+ description: "Read content from a file in the Heimdall workspace.",
37
+ inputSchema: {
38
+ path: z.string().describe("File path relative to workspace"),
39
+ },
40
+ }, async ({ path: filePath }) => {
41
+ const result = await pyodideManager.readFile(filePath);
42
+ return {
43
+ content: [
44
+ {
45
+ type: "text",
46
+ text: result.success ? result.content : `Error: ${result.error}`,
47
+ },
48
+ ],
49
+ structuredContent: result,
50
+ };
51
+ });
52
+ // List files
53
+ server.registerTool("list_files", {
54
+ title: "List Files",
55
+ description: "List files and directories in the Heimdall workspace.",
56
+ inputSchema: {
57
+ path: z.string().optional().describe("Directory path (empty for workspace root)"),
58
+ },
59
+ }, async ({ path: dirPath }) => {
60
+ const result = await pyodideManager.listFiles(dirPath || "");
61
+ let text;
62
+ if (result.success) {
63
+ if (result.files.length === 0) {
64
+ text = "Directory is empty";
65
+ }
66
+ else {
67
+ text = result.files
68
+ .map((f) => {
69
+ const icon = f.isDirectory ? "📁" : "📄";
70
+ const size = f.isDirectory ? "" : ` (${f.size} bytes)`;
71
+ return `${icon} ${f.name}${size}`;
72
+ })
73
+ .join("\n");
74
+ }
75
+ }
76
+ else {
77
+ text = `Error: ${result.error}`;
78
+ }
79
+ return {
80
+ content: [{ type: "text", text }],
81
+ structuredContent: result,
82
+ };
83
+ });
84
+ // Delete file
85
+ server.registerTool("delete_file", {
86
+ title: "Delete File",
87
+ description: "Delete a file or empty directory from the Heimdall workspace.",
88
+ inputSchema: {
89
+ path: z.string().describe("File or directory path"),
90
+ },
91
+ }, async ({ path: filePath }) => {
92
+ const result = await pyodideManager.deleteFile(filePath);
93
+ return {
94
+ content: [
95
+ {
96
+ type: "text",
97
+ text: result.success ? `✓ Deleted ${filePath}` : `✗ Error: ${result.error}`,
98
+ },
99
+ ],
100
+ structuredContent: result,
101
+ };
102
+ });
103
+ }
104
+ //# sourceMappingURL=filesystem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filesystem.js","sourceRoot":"","sources":["../../src/tools/filesystem.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;GAEG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAAiB,EAAE,cAA8B;IACvF,aAAa;IACb,MAAM,CAAC,YAAY,CACjB,YAAY,EACZ;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE;;4EAEyD;QACtE,WAAW,EAAE;YACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;YAC5D,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,kBAAkB,CAAC;SACjD;KACF,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;QACpC,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAEjE,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,QAAQ,EAAE,CAAC,CAAC,CAAC,YAAY,MAAM,CAAC,KAAK,EAAE;iBAC/E;aACF;YACD,iBAAiB,EAAE,MAAM;SAC1B,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,YAAY;IACZ,MAAM,CAAC,YAAY,CACjB,WAAW,EACX;QACE,KAAK,EAAE,WAAW;QAClB,WAAW,EAAE,qDAAqD;QAClE,WAAW,EAAE;YACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;SAC7D;KACF,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;QAC3B,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAEvD,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAQ,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,KAAK,EAAE;iBAClE;aACF;YACD,iBAAiB,EAAE,MAAM;SAC1B,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,aAAa;IACb,MAAM,CAAC,YAAY,CACjB,YAAY,EACZ;QACE,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,uDAAuD;QACpE,WAAW,EAAE;YACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;SAClF;KACF,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE;QAC1B,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,SAAS,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;QAE7D,IAAI,IAAY,CAAC;QACjB,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC9B,IAAI,GAAG,oBAAoB,CAAC;YAC9B,CAAC;iBAAM,CAAC;gBACN,IAAI,GAAG,MAAM,CAAC,KAAK;qBAChB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;oBACT,MAAM,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;oBACzC,MAAM,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,SAAS,CAAC;oBACvD,OAAO,GAAG,IAAI,IAAI,CAAC,CAAC,IAAI,GAAG,IAAI,EAAE,CAAC;gBACpC,CAAC,CAAC;qBACD,IAAI,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC;QACH,CAAC;aAAM,CAAC;YACN,IAAI,GAAG,UAAU,MAAM,CAAC,KAAK,EAAE,CAAC;QAClC,CAAC;QAED,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YACjC,iBAAiB,EAAE,MAAM;SAC1B,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,cAAc;IACd,MAAM,CAAC,YAAY,CACjB,aAAa,EACb;QACE,KAAK,EAAE,aAAa;QACpB,WAAW,EAAE,+DAA+D;QAC5E,WAAW,EAAE;YACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAC;SACpD;KACF,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;QAC3B,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QAEzD,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,QAAQ,EAAE,CAAC,CAAC,CAAC,YAAY,MAAM,CAAC,KAAK,EAAE;iBAC5E;aACF;YACD,iBAAiB,EAAE,MAAM;SAC1B,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Tools Registration
3
+ *
4
+ * Central module for registering all MCP tools
5
+ */
6
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import type { PyodideManager } from "../core/pyodide-manager.js";
8
+ import type { BashManager } from "../core/bash-manager.js";
9
+ /**
10
+ * Register all tools with the MCP server
11
+ */
12
+ export declare function registerAllTools(server: McpServer, pyodideManager: PyodideManager, bashManager: BashManager): void;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAK3D;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EACjB,cAAc,EAAE,cAAc,EAC9B,WAAW,EAAE,WAAW,GACvB,IAAI,CAIN"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Tools Registration
3
+ *
4
+ * Central module for registering all MCP tools
5
+ */
6
+ import { registerPythonExecutionTools } from "./python-execution.js";
7
+ import { registerFilesystemTools } from "./filesystem.js";
8
+ import { registerBashExecutionTools } from "./bash-execution.js";
9
+ /**
10
+ * Register all tools with the MCP server
11
+ */
12
+ export function registerAllTools(server, pyodideManager, bashManager) {
13
+ registerPythonExecutionTools(server, pyodideManager);
14
+ registerFilesystemTools(server, pyodideManager);
15
+ registerBashExecutionTools(server, bashManager, pyodideManager);
16
+ }
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEjE;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAiB,EACjB,cAA8B,EAC9B,WAAwB;IAExB,4BAA4B,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACrD,uBAAuB,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAChD,0BAA0B,CAAC,MAAM,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;AAClE,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Python Execution Tools
3
+ *
4
+ * MCP tools for executing Python code and installing packages
5
+ */
6
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import type { PyodideManager } from "../core/pyodide-manager.js";
8
+ /**
9
+ * Register Python execution tools with the MCP server
10
+ */
11
+ export declare function registerPythonExecutionTools(server: McpServer, pyodideManager: PyodideManager): void;
12
+ //# sourceMappingURL=python-execution.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python-execution.d.ts","sourceRoot":"","sources":["../../src/tools/python-execution.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAEjE;;GAEG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,cAAc,QA6E7F"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Python Execution Tools
3
+ *
4
+ * MCP tools for executing Python code and installing packages
5
+ */
6
+ import { z } from "zod";
7
+ /**
8
+ * Register Python execution tools with the MCP server
9
+ */
10
+ export function registerPythonExecutionTools(server, pyodideManager) {
11
+ // Execute Python code
12
+ server.registerTool("execute_python", {
13
+ title: "Execute Python",
14
+ description: `Execute Python code in the Heimdall environment (Pyodide sandbox).
15
+
16
+ The code runs in an isolated WebAssembly sandbox with:
17
+ - Access to /workspace directory for file I/O
18
+ - Standard library and auto-loaded packages
19
+ - stdout/stderr capture for output
20
+ - NO network access (WebAssembly security boundary)
21
+ - Execution time limits to prevent long-running scripts
22
+
23
+ Returns execution results including output and any errors.`,
24
+ inputSchema: {
25
+ code: z.string().describe("Python code to execute"),
26
+ packages: z
27
+ .array(z.string())
28
+ .optional()
29
+ .describe("Optional additional packages to install (most are auto-detected from imports)"),
30
+ },
31
+ }, async ({ code, packages }) => {
32
+ const result = await pyodideManager.executeCode(code, packages || []);
33
+ const output = {
34
+ success: result.success,
35
+ stdout: result.stdout,
36
+ stderr: result.stderr,
37
+ result: result.result,
38
+ error: result.error,
39
+ };
40
+ let text = "";
41
+ if (result.stdout)
42
+ text += `Output:\n${result.stdout}\n`;
43
+ if (result.stderr)
44
+ text += `Stderr:\n${result.stderr}\n`;
45
+ if (result.result)
46
+ text += `Result: ${result.result}\n`;
47
+ if (result.error)
48
+ text += `Error:\n${result.error}\n`;
49
+ if (!text)
50
+ text = "Code executed successfully (no output)";
51
+ return {
52
+ content: [{ type: "text", text }],
53
+ structuredContent: output,
54
+ };
55
+ });
56
+ // Install packages
57
+ server.registerTool("install_packages", {
58
+ title: "Install Packages",
59
+ description: `Install Python packages via micropip.
60
+
61
+ Note: Only pure Python packages or packages with WebAssembly wheels are supported.
62
+ Common data science packages like numpy, pandas, scipy are available.`,
63
+ inputSchema: {
64
+ packages: z.array(z.string()).describe("List of package names to install"),
65
+ },
66
+ }, async ({ packages }) => {
67
+ const results = await pyodideManager.installPackages(packages);
68
+ const text = results
69
+ .map((r) => `${r.package}: ${r.success ? "✓ installed" : `✗ ${r.error}`}`)
70
+ .join("\n");
71
+ return {
72
+ content: [{ type: "text", text }],
73
+ structuredContent: { results },
74
+ };
75
+ });
76
+ }
77
+ //# sourceMappingURL=python-execution.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python-execution.js","sourceRoot":"","sources":["../../src/tools/python-execution.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;GAEG;AACH,MAAM,UAAU,4BAA4B,CAAC,MAAiB,EAAE,cAA8B;IAC5F,sBAAsB;IACtB,MAAM,CAAC,YAAY,CACjB,gBAAgB,EAChB;QACE,KAAK,EAAE,gBAAgB;QACvB,WAAW,EAAE;;;;;;;;;2DASwC;QACrD,WAAW,EAAE;YACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,wBAAwB,CAAC;YACnD,QAAQ,EAAE,CAAC;iBACR,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;iBACjB,QAAQ,EAAE;iBACV,QAAQ,CACP,+EAA+E,CAChF;SACJ;KACF,EACD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;QAC3B,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,IAAI,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC;QAEtE,MAAM,MAAM,GAAG;YACb,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,KAAK,EAAE,MAAM,CAAC,KAAK;SACpB,CAAC;QAEF,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,MAAM,CAAC,MAAM;YAAE,IAAI,IAAI,YAAY,MAAM,CAAC,MAAM,IAAI,CAAC;QACzD,IAAI,MAAM,CAAC,MAAM;YAAE,IAAI,IAAI,YAAY,MAAM,CAAC,MAAM,IAAI,CAAC;QACzD,IAAI,MAAM,CAAC,MAAM;YAAE,IAAI,IAAI,WAAW,MAAM,CAAC,MAAM,IAAI,CAAC;QACxD,IAAI,MAAM,CAAC,KAAK;YAAE,IAAI,IAAI,WAAW,MAAM,CAAC,KAAK,IAAI,CAAC;QACtD,IAAI,CAAC,IAAI;YAAE,IAAI,GAAG,wCAAwC,CAAC;QAE3D,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YACjC,iBAAiB,EAAE,MAAM;SAC1B,CAAC;IACJ,CAAC,CACF,CAAC;IAEF,mBAAmB;IACnB,MAAM,CAAC,YAAY,CACjB,kBAAkB,EAClB;QACE,KAAK,EAAE,kBAAkB;QACzB,WAAW,EAAE;;;sEAGmD;QAChE,WAAW,EAAE;YACX,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,CAAC,kCAAkC,CAAC;SAC3E;KACF,EACD,KAAK,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE;QACrB,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;QAE/D,MAAM,IAAI,GAAG,OAAO;aACjB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC;aACzE,IAAI,CAAC,IAAI,CAAC,CAAC;QAEd,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YACjC,iBAAiB,EAAE,EAAE,OAAO,EAAE;SAC/B,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Base interface with index signature for MCP SDK compatibility
3
+ */
4
+ interface BaseResult {
5
+ [x: string]: unknown;
6
+ }
7
+ /**
8
+ * Result from executing Python code
9
+ */
10
+ export interface ExecutionResult extends BaseResult {
11
+ success: boolean;
12
+ stdout: string;
13
+ stderr: string;
14
+ result: string | null;
15
+ error: string | null;
16
+ }
17
+ /**
18
+ * Result from reading a file
19
+ */
20
+ export interface FileReadResult extends BaseResult {
21
+ success: boolean;
22
+ content: string | null;
23
+ error: string | null;
24
+ }
25
+ /**
26
+ * Result from writing a file
27
+ */
28
+ export interface FileWriteResult extends BaseResult {
29
+ success: boolean;
30
+ error: string | null;
31
+ }
32
+ /**
33
+ * Result from installing packages
34
+ */
35
+ export interface PackageInstallResult extends BaseResult {
36
+ package: string;
37
+ success: boolean;
38
+ error: string | null;
39
+ }
40
+ /**
41
+ * File information
42
+ */
43
+ export interface FileInfo extends BaseResult {
44
+ name: string;
45
+ isDirectory: boolean;
46
+ size: number;
47
+ }
48
+ /**
49
+ * Result from listing files
50
+ */
51
+ export interface FileListResult extends BaseResult {
52
+ success: boolean;
53
+ files: FileInfo[];
54
+ error: string | null;
55
+ }
56
+ /**
57
+ * Result from deleting a file
58
+ */
59
+ export interface FileDeleteResult extends BaseResult {
60
+ success: boolean;
61
+ error: string | null;
62
+ }
63
+ export {};
64
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,UAAU,UAAU;IAClB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,UAAU;IACjD,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,eAAgB,SAAQ,UAAU;IACjD,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAqB,SAAQ,UAAU;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,QAAS,SAAQ,UAAU;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,cAAe,SAAQ,UAAU;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAClD,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * AsyncLock - A simple async mutex/lock implementation
3
+ *
4
+ * Provides mutual exclusion for async operations, preventing race conditions
5
+ * in concurrent code. Used to protect critical sections like file writes
6
+ * where TOCTOU (time-of-check to time-of-use) vulnerabilities could occur.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const lock = new AsyncLock();
11
+ *
12
+ * // Multiple concurrent calls will be serialized
13
+ * await lock.acquire('write', async () => {
14
+ * await checkSize();
15
+ * await writeFile();
16
+ * });
17
+ * ```
18
+ */
19
+ export declare class AsyncLock {
20
+ private locks;
21
+ /**
22
+ * Acquire a lock for the given key, execute the function, then release.
23
+ * If another operation holds the lock, this will wait until it's released.
24
+ *
25
+ * @param key - The lock key (allows multiple independent locks)
26
+ * @param fn - The async function to execute while holding the lock
27
+ * @returns The result of the function
28
+ */
29
+ acquire<T>(key: string, fn: () => Promise<T>): Promise<T>;
30
+ /**
31
+ * Check if a lock is currently held for the given key
32
+ */
33
+ isLocked(key: string): boolean;
34
+ }
35
+ //# sourceMappingURL=async-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-lock.d.ts","sourceRoot":"","sources":["../../src/utils/async-lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,KAAK,CAAyC;IAEtD;;;;;;;OAOG;IACG,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAwB/D;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;CAG/B"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * AsyncLock - A simple async mutex/lock implementation
3
+ *
4
+ * Provides mutual exclusion for async operations, preventing race conditions
5
+ * in concurrent code. Used to protect critical sections like file writes
6
+ * where TOCTOU (time-of-check to time-of-use) vulnerabilities could occur.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * const lock = new AsyncLock();
11
+ *
12
+ * // Multiple concurrent calls will be serialized
13
+ * await lock.acquire('write', async () => {
14
+ * await checkSize();
15
+ * await writeFile();
16
+ * });
17
+ * ```
18
+ */
19
+ export class AsyncLock {
20
+ locks = new Map();
21
+ /**
22
+ * Acquire a lock for the given key, execute the function, then release.
23
+ * If another operation holds the lock, this will wait until it's released.
24
+ *
25
+ * @param key - The lock key (allows multiple independent locks)
26
+ * @param fn - The async function to execute while holding the lock
27
+ * @returns The result of the function
28
+ */
29
+ async acquire(key, fn) {
30
+ // Wait for any existing lock on this key
31
+ while (this.locks.has(key)) {
32
+ await this.locks.get(key);
33
+ }
34
+ // Create a new lock promise
35
+ let releaseLock;
36
+ const lockPromise = new Promise((resolve) => {
37
+ releaseLock = resolve;
38
+ });
39
+ this.locks.set(key, lockPromise);
40
+ try {
41
+ // Execute the protected function
42
+ return await fn();
43
+ }
44
+ finally {
45
+ // Release the lock
46
+ this.locks.delete(key);
47
+ releaseLock();
48
+ }
49
+ }
50
+ /**
51
+ * Check if a lock is currently held for the given key
52
+ */
53
+ isLocked(key) {
54
+ return this.locks.has(key);
55
+ }
56
+ }
57
+ //# sourceMappingURL=async-lock.js.map