@mariozechner/pi-mom 0.9.4

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 (47) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +183 -0
  3. package/dist/agent.d.ts +9 -0
  4. package/dist/agent.d.ts.map +1 -0
  5. package/dist/agent.js +248 -0
  6. package/dist/agent.js.map +1 -0
  7. package/dist/main.d.ts +3 -0
  8. package/dist/main.d.ts.map +1 -0
  9. package/dist/main.js +125 -0
  10. package/dist/main.js.map +1 -0
  11. package/dist/sandbox.d.ts +34 -0
  12. package/dist/sandbox.d.ts.map +1 -0
  13. package/dist/sandbox.js +183 -0
  14. package/dist/sandbox.js.map +1 -0
  15. package/dist/slack.d.ts +46 -0
  16. package/dist/slack.d.ts.map +1 -0
  17. package/dist/slack.js +208 -0
  18. package/dist/slack.js.map +1 -0
  19. package/dist/store.d.ts +52 -0
  20. package/dist/store.d.ts.map +1 -0
  21. package/dist/store.js +124 -0
  22. package/dist/store.js.map +1 -0
  23. package/dist/tools/attach.d.ts +10 -0
  24. package/dist/tools/attach.d.ts.map +1 -0
  25. package/dist/tools/attach.js +34 -0
  26. package/dist/tools/attach.js.map +1 -0
  27. package/dist/tools/bash.d.ts +10 -0
  28. package/dist/tools/bash.d.ts.map +1 -0
  29. package/dist/tools/bash.js +30 -0
  30. package/dist/tools/bash.js.map +1 -0
  31. package/dist/tools/edit.d.ts +11 -0
  32. package/dist/tools/edit.d.ts.map +1 -0
  33. package/dist/tools/edit.js +131 -0
  34. package/dist/tools/edit.js.map +1 -0
  35. package/dist/tools/index.d.ts +5 -0
  36. package/dist/tools/index.d.ts.map +1 -0
  37. package/dist/tools/index.js +16 -0
  38. package/dist/tools/index.js.map +1 -0
  39. package/dist/tools/read.d.ts +11 -0
  40. package/dist/tools/read.d.ts.map +1 -0
  41. package/dist/tools/read.js +102 -0
  42. package/dist/tools/read.js.map +1 -0
  43. package/dist/tools/write.d.ts +10 -0
  44. package/dist/tools/write.d.ts.map +1 -0
  45. package/dist/tools/write.js +33 -0
  46. package/dist/tools/write.js.map +1 -0
  47. package/package.json +52 -0
@@ -0,0 +1,102 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { extname } from "path";
3
+ /**
4
+ * Map of file extensions to MIME types for common image formats
5
+ */
6
+ const IMAGE_MIME_TYPES = {
7
+ ".jpg": "image/jpeg",
8
+ ".jpeg": "image/jpeg",
9
+ ".png": "image/png",
10
+ ".gif": "image/gif",
11
+ ".webp": "image/webp",
12
+ };
13
+ /**
14
+ * Check if a file is an image based on its extension
15
+ */
16
+ function isImageFile(filePath) {
17
+ const ext = extname(filePath).toLowerCase();
18
+ return IMAGE_MIME_TYPES[ext] || null;
19
+ }
20
+ const readSchema = Type.Object({
21
+ label: Type.String({ description: "Brief description of what you're reading and why (shown to user)" }),
22
+ path: Type.String({ description: "Path to the file to read (relative or absolute)" }),
23
+ offset: Type.Optional(Type.Number({ description: "Line number to start reading from (1-indexed)" })),
24
+ limit: Type.Optional(Type.Number({ description: "Maximum number of lines to read" })),
25
+ });
26
+ const MAX_LINES = 2000;
27
+ const MAX_LINE_LENGTH = 2000;
28
+ export function createReadTool(executor) {
29
+ return {
30
+ name: "read",
31
+ label: "read",
32
+ description: "Read the contents of a file. Supports text files and images (jpg, png, gif, webp). Images are sent as attachments. For text files, defaults to first 2000 lines. Use offset/limit for large files.",
33
+ parameters: readSchema,
34
+ execute: async (_toolCallId, { path, offset, limit }, signal) => {
35
+ const mimeType = isImageFile(path);
36
+ if (mimeType) {
37
+ // Read as image (binary) - use base64
38
+ const result = await executor.exec(`base64 < ${shellEscape(path)}`, { signal });
39
+ if (result.code !== 0) {
40
+ throw new Error(result.stderr || `Failed to read file: ${path}`);
41
+ }
42
+ const base64 = result.stdout.replace(/\s/g, ""); // Remove whitespace from base64
43
+ return {
44
+ content: [
45
+ { type: "text", text: `Read image file [${mimeType}]` },
46
+ { type: "image", data: base64, mimeType },
47
+ ],
48
+ details: undefined,
49
+ };
50
+ }
51
+ // Read as text using cat with offset/limit via sed/head/tail
52
+ let cmd;
53
+ const startLine = offset ? Math.max(1, offset) : 1;
54
+ const maxLines = limit || MAX_LINES;
55
+ if (startLine === 1) {
56
+ cmd = `head -n ${maxLines} ${shellEscape(path)}`;
57
+ }
58
+ else {
59
+ cmd = `sed -n '${startLine},${startLine + maxLines - 1}p' ${shellEscape(path)}`;
60
+ }
61
+ // Also get total line count
62
+ const countResult = await executor.exec(`wc -l < ${shellEscape(path)}`, { signal });
63
+ const totalLines = Number.parseInt(countResult.stdout.trim(), 10) || 0;
64
+ const result = await executor.exec(cmd, { signal });
65
+ if (result.code !== 0) {
66
+ throw new Error(result.stderr || `Failed to read file: ${path}`);
67
+ }
68
+ const lines = result.stdout.split("\n");
69
+ // Truncate long lines
70
+ let hadTruncatedLines = false;
71
+ const formattedLines = lines.map((line) => {
72
+ if (line.length > MAX_LINE_LENGTH) {
73
+ hadTruncatedLines = true;
74
+ return line.slice(0, MAX_LINE_LENGTH);
75
+ }
76
+ return line;
77
+ });
78
+ let outputText = formattedLines.join("\n");
79
+ // Add notices
80
+ const notices = [];
81
+ const endLine = startLine + lines.length - 1;
82
+ if (hadTruncatedLines) {
83
+ notices.push(`Some lines were truncated to ${MAX_LINE_LENGTH} characters for display`);
84
+ }
85
+ if (endLine < totalLines) {
86
+ const remaining = totalLines - endLine;
87
+ notices.push(`${remaining} more lines not shown. Use offset=${endLine + 1} to continue reading`);
88
+ }
89
+ if (notices.length > 0) {
90
+ outputText += `\n\n... (${notices.join(". ")})`;
91
+ }
92
+ return {
93
+ content: [{ type: "text", text: outputText }],
94
+ details: undefined,
95
+ };
96
+ },
97
+ };
98
+ }
99
+ function shellEscape(s) {
100
+ return `'${s.replace(/'/g, "'\\''")}'`;
101
+ }
102
+ //# sourceMappingURL=read.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"read.js","sourceRoot":"","sources":["../../src/tools/read.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentTool, ImageContent, TextContent } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport { extname } from \"path\";\nimport type { Executor } from \"../sandbox.js\";\n\n/**\n * Map of file extensions to MIME types for common image formats\n */\nconst IMAGE_MIME_TYPES: Record<string, string> = {\n\t\".jpg\": \"image/jpeg\",\n\t\".jpeg\": \"image/jpeg\",\n\t\".png\": \"image/png\",\n\t\".gif\": \"image/gif\",\n\t\".webp\": \"image/webp\",\n};\n\n/**\n * Check if a file is an image based on its extension\n */\nfunction isImageFile(filePath: string): string | null {\n\tconst ext = extname(filePath).toLowerCase();\n\treturn IMAGE_MIME_TYPES[ext] || null;\n}\n\nconst readSchema = Type.Object({\n\tlabel: Type.String({ description: \"Brief description of what you're reading and why (shown to user)\" }),\n\tpath: Type.String({ description: \"Path to the file to read (relative or absolute)\" }),\n\toffset: Type.Optional(Type.Number({ description: \"Line number to start reading from (1-indexed)\" })),\n\tlimit: Type.Optional(Type.Number({ description: \"Maximum number of lines to read\" })),\n});\n\nconst MAX_LINES = 2000;\nconst MAX_LINE_LENGTH = 2000;\n\nexport function createReadTool(executor: Executor): AgentTool<typeof readSchema> {\n\treturn {\n\t\tname: \"read\",\n\t\tlabel: \"read\",\n\t\tdescription:\n\t\t\t\"Read the contents of a file. Supports text files and images (jpg, png, gif, webp). Images are sent as attachments. For text files, defaults to first 2000 lines. Use offset/limit for large files.\",\n\t\tparameters: readSchema,\n\t\texecute: async (\n\t\t\t_toolCallId: string,\n\t\t\t{ path, offset, limit }: { label: string; path: string; offset?: number; limit?: number },\n\t\t\tsignal?: AbortSignal,\n\t\t) => {\n\t\t\tconst mimeType = isImageFile(path);\n\n\t\t\tif (mimeType) {\n\t\t\t\t// Read as image (binary) - use base64\n\t\t\t\tconst result = await executor.exec(`base64 < ${shellEscape(path)}`, { signal });\n\t\t\t\tif (result.code !== 0) {\n\t\t\t\t\tthrow new Error(result.stderr || `Failed to read file: ${path}`);\n\t\t\t\t}\n\t\t\t\tconst base64 = result.stdout.replace(/\\s/g, \"\"); // Remove whitespace from base64\n\n\t\t\t\treturn {\n\t\t\t\t\tcontent: [\n\t\t\t\t\t\t{ type: \"text\", text: `Read image file [${mimeType}]` },\n\t\t\t\t\t\t{ type: \"image\", data: base64, mimeType },\n\t\t\t\t\t] as (TextContent | ImageContent)[],\n\t\t\t\t\tdetails: undefined,\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Read as text using cat with offset/limit via sed/head/tail\n\t\t\tlet cmd: string;\n\t\t\tconst startLine = offset ? Math.max(1, offset) : 1;\n\t\t\tconst maxLines = limit || MAX_LINES;\n\n\t\t\tif (startLine === 1) {\n\t\t\t\tcmd = `head -n ${maxLines} ${shellEscape(path)}`;\n\t\t\t} else {\n\t\t\t\tcmd = `sed -n '${startLine},${startLine + maxLines - 1}p' ${shellEscape(path)}`;\n\t\t\t}\n\n\t\t\t// Also get total line count\n\t\t\tconst countResult = await executor.exec(`wc -l < ${shellEscape(path)}`, { signal });\n\t\t\tconst totalLines = Number.parseInt(countResult.stdout.trim(), 10) || 0;\n\n\t\t\tconst result = await executor.exec(cmd, { signal });\n\t\t\tif (result.code !== 0) {\n\t\t\t\tthrow new Error(result.stderr || `Failed to read file: ${path}`);\n\t\t\t}\n\n\t\t\tconst lines = result.stdout.split(\"\\n\");\n\n\t\t\t// Truncate long lines\n\t\t\tlet hadTruncatedLines = false;\n\t\t\tconst formattedLines = lines.map((line) => {\n\t\t\t\tif (line.length > MAX_LINE_LENGTH) {\n\t\t\t\t\thadTruncatedLines = true;\n\t\t\t\t\treturn line.slice(0, MAX_LINE_LENGTH);\n\t\t\t\t}\n\t\t\t\treturn line;\n\t\t\t});\n\n\t\t\tlet outputText = formattedLines.join(\"\\n\");\n\n\t\t\t// Add notices\n\t\t\tconst notices: string[] = [];\n\t\t\tconst endLine = startLine + lines.length - 1;\n\n\t\t\tif (hadTruncatedLines) {\n\t\t\t\tnotices.push(`Some lines were truncated to ${MAX_LINE_LENGTH} characters for display`);\n\t\t\t}\n\n\t\t\tif (endLine < totalLines) {\n\t\t\t\tconst remaining = totalLines - endLine;\n\t\t\t\tnotices.push(`${remaining} more lines not shown. Use offset=${endLine + 1} to continue reading`);\n\t\t\t}\n\n\t\t\tif (notices.length > 0) {\n\t\t\t\toutputText += `\\n\\n... (${notices.join(\". \")})`;\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: outputText }] as (TextContent | ImageContent)[],\n\t\t\t\tdetails: undefined,\n\t\t\t};\n\t\t},\n\t};\n}\n\nfunction shellEscape(s: string): string {\n\treturn `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { AgentTool } from "@mariozechner/pi-ai";
2
+ import type { Executor } from "../sandbox.js";
3
+ declare const writeSchema: import("@sinclair/typebox").TObject<{
4
+ label: import("@sinclair/typebox").TString;
5
+ path: import("@sinclair/typebox").TString;
6
+ content: import("@sinclair/typebox").TString;
7
+ }>;
8
+ export declare function createWriteTool(executor: Executor): AgentTool<typeof writeSchema>;
9
+ export {};
10
+ //# sourceMappingURL=write.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../../src/tools/write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,QAAA,MAAM,WAAW;;;;EAIf,CAAC;AAEH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC,OAAO,WAAW,CAAC,CA8BjF","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox.js\";\n\nconst writeSchema = Type.Object({\n\tlabel: Type.String({ description: \"Brief description of what you're writing (shown to user)\" }),\n\tpath: Type.String({ description: \"Path to the file to write (relative or absolute)\" }),\n\tcontent: Type.String({ description: \"Content to write to the file\" }),\n});\n\nexport function createWriteTool(executor: Executor): AgentTool<typeof writeSchema> {\n\treturn {\n\t\tname: \"write\",\n\t\tlabel: \"write\",\n\t\tdescription:\n\t\t\t\"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.\",\n\t\tparameters: writeSchema,\n\t\texecute: async (\n\t\t\t_toolCallId: string,\n\t\t\t{ path, content }: { label: string; path: string; content: string },\n\t\t\tsignal?: AbortSignal,\n\t\t) => {\n\t\t\t// Create parent directories and write file using heredoc\n\t\t\tconst dir = path.includes(\"/\") ? path.substring(0, path.lastIndexOf(\"/\")) : \".\";\n\n\t\t\t// Use printf to handle content with special characters, pipe to file\n\t\t\t// This avoids issues with heredoc and special characters\n\t\t\tconst cmd = `mkdir -p ${shellEscape(dir)} && printf '%s' ${shellEscape(content)} > ${shellEscape(path)}`;\n\n\t\t\tconst result = await executor.exec(cmd, { signal });\n\t\t\tif (result.code !== 0) {\n\t\t\t\tthrow new Error(result.stderr || `Failed to write file: ${path}`);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: `Successfully wrote ${content.length} bytes to ${path}` }],\n\t\t\t\tdetails: undefined,\n\t\t\t};\n\t\t},\n\t};\n}\n\nfunction shellEscape(s: string): string {\n\treturn `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ const writeSchema = Type.Object({
3
+ label: Type.String({ description: "Brief description of what you're writing (shown to user)" }),
4
+ path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
5
+ content: Type.String({ description: "Content to write to the file" }),
6
+ });
7
+ export function createWriteTool(executor) {
8
+ return {
9
+ name: "write",
10
+ label: "write",
11
+ description: "Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.",
12
+ parameters: writeSchema,
13
+ execute: async (_toolCallId, { path, content }, signal) => {
14
+ // Create parent directories and write file using heredoc
15
+ const dir = path.includes("/") ? path.substring(0, path.lastIndexOf("/")) : ".";
16
+ // Use printf to handle content with special characters, pipe to file
17
+ // This avoids issues with heredoc and special characters
18
+ const cmd = `mkdir -p ${shellEscape(dir)} && printf '%s' ${shellEscape(content)} > ${shellEscape(path)}`;
19
+ const result = await executor.exec(cmd, { signal });
20
+ if (result.code !== 0) {
21
+ throw new Error(result.stderr || `Failed to write file: ${path}`);
22
+ }
23
+ return {
24
+ content: [{ type: "text", text: `Successfully wrote ${content.length} bytes to ${path}` }],
25
+ details: undefined,
26
+ };
27
+ },
28
+ };
29
+ }
30
+ function shellEscape(s) {
31
+ return `'${s.replace(/'/g, "'\\''")}'`;
32
+ }
33
+ //# sourceMappingURL=write.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write.js","sourceRoot":"","sources":["../../src/tools/write.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAGzC,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC;IAC/B,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,0DAA0D,EAAE,CAAC;IAC/F,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,kDAAkD,EAAE,CAAC;IACtF,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,8BAA8B,EAAE,CAAC;CACrE,CAAC,CAAC;AAEH,MAAM,UAAU,eAAe,CAAC,QAAkB,EAAiC;IAClF,OAAO;QACN,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,OAAO;QACd,WAAW,EACV,iIAAiI;QAClI,UAAU,EAAE,WAAW;QACvB,OAAO,EAAE,KAAK,EACb,WAAmB,EACnB,EAAE,IAAI,EAAE,OAAO,EAAoD,EACnE,MAAoB,EACnB,EAAE,CAAC;YACJ,yDAAyD;YACzD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;YAEhF,qEAAqE;YACrE,yDAAyD;YACzD,MAAM,GAAG,GAAG,YAAY,WAAW,CAAC,GAAG,CAAC,mBAAmB,WAAW,CAAC,OAAO,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;YAEzG,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;YACpD,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACvB,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,IAAI,yBAAyB,IAAI,EAAE,CAAC,CAAC;YACnE,CAAC;YAED,OAAO;gBACN,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,sBAAsB,OAAO,CAAC,MAAM,aAAa,IAAI,EAAE,EAAE,CAAC;gBAC1F,OAAO,EAAE,SAAS;aAClB,CAAC;QAAA,CACF;KACD,CAAC;AAAA,CACF;AAED,SAAS,WAAW,CAAC,CAAS,EAAU;IACvC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC;AAAA,CACvC","sourcesContent":["import type { AgentTool } from \"@mariozechner/pi-ai\";\nimport { Type } from \"@sinclair/typebox\";\nimport type { Executor } from \"../sandbox.js\";\n\nconst writeSchema = Type.Object({\n\tlabel: Type.String({ description: \"Brief description of what you're writing (shown to user)\" }),\n\tpath: Type.String({ description: \"Path to the file to write (relative or absolute)\" }),\n\tcontent: Type.String({ description: \"Content to write to the file\" }),\n});\n\nexport function createWriteTool(executor: Executor): AgentTool<typeof writeSchema> {\n\treturn {\n\t\tname: \"write\",\n\t\tlabel: \"write\",\n\t\tdescription:\n\t\t\t\"Write content to a file. Creates the file if it doesn't exist, overwrites if it does. Automatically creates parent directories.\",\n\t\tparameters: writeSchema,\n\t\texecute: async (\n\t\t\t_toolCallId: string,\n\t\t\t{ path, content }: { label: string; path: string; content: string },\n\t\t\tsignal?: AbortSignal,\n\t\t) => {\n\t\t\t// Create parent directories and write file using heredoc\n\t\t\tconst dir = path.includes(\"/\") ? path.substring(0, path.lastIndexOf(\"/\")) : \".\";\n\n\t\t\t// Use printf to handle content with special characters, pipe to file\n\t\t\t// This avoids issues with heredoc and special characters\n\t\t\tconst cmd = `mkdir -p ${shellEscape(dir)} && printf '%s' ${shellEscape(content)} > ${shellEscape(path)}`;\n\n\t\t\tconst result = await executor.exec(cmd, { signal });\n\t\t\tif (result.code !== 0) {\n\t\t\t\tthrow new Error(result.stderr || `Failed to write file: ${path}`);\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text: `Successfully wrote ${content.length} bytes to ${path}` }],\n\t\t\t\tdetails: undefined,\n\t\t\t};\n\t\t},\n\t};\n}\n\nfunction shellEscape(s: string): string {\n\treturn `'${s.replace(/'/g, \"'\\\\''\")}'`;\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@mariozechner/pi-mom",
3
+ "version": "0.9.4",
4
+ "description": "Slack bot that delegates messages to the pi coding agent",
5
+ "type": "module",
6
+ "bin": {
7
+ "mom": "dist/main.js"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "CHANGELOG.md"
14
+ ],
15
+ "scripts": {
16
+ "clean": "rm -rf dist",
17
+ "build": "tsgo -p tsconfig.build.json && chmod +x dist/main.js",
18
+ "dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
19
+ "check": "tsgo --noEmit",
20
+ "prepublishOnly": "npm run clean && npm run build"
21
+ },
22
+ "dependencies": {
23
+ "@anthropic-ai/sandbox-runtime": "^0.0.16",
24
+ "@mariozechner/pi-agent-core": "^0.9.4",
25
+ "@mariozechner/pi-ai": "^0.9.4",
26
+ "@sinclair/typebox": "^0.34.0",
27
+ "@slack/socket-mode": "^2.0.0",
28
+ "@slack/web-api": "^7.0.0",
29
+ "diff": "^8.0.2"
30
+ },
31
+ "devDependencies": {
32
+ "@types/diff": "^7.0.2",
33
+ "@types/node": "^24.3.0",
34
+ "typescript": "^5.7.3"
35
+ },
36
+ "keywords": [
37
+ "slack",
38
+ "bot",
39
+ "ai",
40
+ "agent"
41
+ ],
42
+ "author": "Mario Zechner",
43
+ "license": "MIT",
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/badlogic/pi-mono.git",
47
+ "directory": "packages/mom"
48
+ },
49
+ "engines": {
50
+ "node": ">=20.0.0"
51
+ }
52
+ }