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}