main
1import { Type } from "@sinclair/typebox";
2import { Tool } from "./types.js";
3import * as fs from "fs/promises";
4
5const OrgmodeParams = Type.Object({
6 title: Type.String({ description: "Title for the org entry" }),
7 content: Type.String({ description: "Content to save (markdown or plain text)" }),
8 tags: Type.Optional(
9 Type.Array(Type.String(), { description: "Optional tags for the entry" })
10 ),
11});
12
13type OrgmodeArgs = typeof OrgmodeParams.static;
14
15export function createOrgmodeTool(inboxPath: string): Tool<OrgmodeArgs> {
16 return {
17 name: "save_to_org",
18 description:
19 "Save content to the org-mode inbox file. Use for saving research results, notes, or any content the user wants to keep.",
20 parameters: OrgmodeParams,
21 execute: async (args) => {
22 const { title, content, tags } = args;
23
24 const timestamp = new Date().toISOString();
25 const dateStr = timestamp.slice(0, 10);
26 const timeStr = timestamp.slice(11, 16);
27
28 // Format tags for org-mode
29 const tagStr = tags && tags.length > 0 ? `:${tags.join(":")}:` : "";
30
31 // Convert markdown to org-mode (basic conversion)
32 const orgContent = convertMarkdownToOrg(content);
33
34 // Create org entry
35 const entry = `
36* TODO ${title} ${tagStr}
37:PROPERTIES:
38:CREATED: [${dateStr} ${timeStr}]
39:SOURCE: daneel
40:END:
41
42${orgContent}
43`;
44
45 // Append to inbox file
46 await fs.appendFile(inboxPath, entry, "utf-8");
47
48 return `Saved to org inbox: "${title}"`;
49 },
50 };
51}
52
53function convertMarkdownToOrg(markdown: string): string {
54 let org = markdown;
55
56 // Convert headers (### -> ***)
57 org = org.replace(/^### (.+)$/gm, "*** $1");
58 org = org.replace(/^## (.+)$/gm, "** $1");
59 org = org.replace(/^# (.+)$/gm, "* $1");
60
61 // Convert bold (**text** -> *text*)
62 org = org.replace(/\*\*(.+?)\*\*/g, "*$1*");
63
64 // Convert italic (*text* or _text_ -> /text/)
65 // Be careful not to convert already-converted bold
66 org = org.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "/$1/");
67 org = org.replace(/_(.+?)_/g, "/$1/");
68
69 // Convert inline code (`code` -> =code=)
70 org = org.replace(/`([^`]+)`/g, "=$1=");
71
72 // Convert code blocks
73 org = org.replace(/```(\w*)\n([\s\S]*?)```/g, (_, lang, code) => {
74 const langStr = lang ? ` ${lang}` : "";
75 return `#+BEGIN_SRC${langStr}\n${code}#+END_SRC`;
76 });
77
78 // Convert links [text](url) -> [[url][text]]
79 org = org.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "[[$2][$1]]");
80
81 // Convert unordered lists (- item -> - item, already compatible)
82 // Convert ordered lists (1. item -> 1. item, already compatible)
83
84 return org;
85}