obsform 0.1.0__tar.gz

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.
obsform-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: obsform
3
+ Version: 0.1.0
4
+ Summary: An LLM-powered CLI to fix, format, and enhance Obsidian notes.
5
+ Requires-Python: >=3.9
6
+ Requires-Dist: typer>=0.9.0
7
+ Requires-Dist: rich>=13.0.0
8
+ Requires-Dist: google-generativeai>=0.4.0
File without changes
@@ -0,0 +1,156 @@
1
+ import os
2
+ import typer
3
+ import difflib
4
+ import json
5
+ from rich.console import Console
6
+ from pathlib import Path
7
+ import google.generativeai as genai
8
+
9
+ app = typer.Typer(help= "CLI tool to format, correct and improve obsidan score")
10
+ console= Console()
11
+
12
+ CONFIG_FILE = Path.home() / ".obsidian_tutor.json"
13
+
14
+ SYST_PROMPT = """
15
+ You are an expert editor and Obsidian note optimizer. Your task is to process the user's raw notes and return a polished version.
16
+ If a file is in txt format convert it to markdown and then process it.
17
+ Strictly adhere to the following rules:
18
+ 1. Fix any spelling, grammar, and punctuation mistakes.
19
+ 2. Fact-check the content. If a statement is factually incorrect, (Do not change the original statemet) add a note of the correct statement.
20
+ 3. Format the document cleanly using Markdown (headers, bullet points, bold text).
21
+ 4. **Formulas:** Convert all mathematical formulas into Obsidian-compatible LaTeX. Use `$` for inline math and `$$` for block math.
22
+ 5. Preserve any existing Obsidian internal links (e.g., [[Link]]) and tags (e.g., #tag).
23
+ 6. Output ONLY the final processed Markdown content. Do not include introductory or concluding conversational text.
24
+ """
25
+ @app.command()
26
+ def setup():
27
+ console.print("[cyan]Set up your Obsidian Tutor[/cyan]")
28
+ console.print("You can get a free API key from: https://aistudio.google.com/app/apikey")
29
+
30
+ # hide_input=True ensures the key doesn't show up on screen while typing
31
+ api_key = typer.prompt("Enter your Gemini API Key", hide_input=True)
32
+ config_data = {"GEMINI_API_KEY": api_key}
33
+
34
+ with open(CONFIG_FILE, "w") as f:
35
+ json.dump(config_data, f)
36
+
37
+ console.print(f"[green]Success! API Key saved to {CONFIG_FILE}[/green]")
38
+
39
+ def setupLLM():
40
+ api_key = os.environ.get("GEMINI_API_KEY")
41
+
42
+ if not api_key and CONFIG_FILE.exists():
43
+ try:
44
+ with open(CONFIG_FILE, "r") as f:
45
+ config = json.load(f)
46
+ api_key = config.get("GEMINI_API_KEY")
47
+ except json.JSONDecodeError:
48
+ console.print("[red]Error reading config file. Try running setup again.[/red]")
49
+ raise typer.Exit(code=1)
50
+
51
+ if not api_key:
52
+ console.print("[red]Error: API key not found.[/red]")
53
+ console.print("Please run [bold cyan]obsidian-tutor setup[/bold cyan] to configure your tool.")
54
+ raise typer.Exit(code=1)
55
+
56
+ genai.configure(api_key=api_key)
57
+ return genai.GenerativeModel(
58
+ model_name="gemini-1.5-flash",
59
+ system_instruction=SYST_PROMPT
60
+ )
61
+
62
+ def show_diff(original:str, processed:str, filename:str):
63
+ diff = list(difflib.unified_diff(
64
+ original.splitlines(keepends=True),
65
+ processed.splitlines(keepends=True),
66
+ fromfile=f"Original: {filename}",
67
+ tofile = f"Processed: {filename}",
68
+ n=3
69
+ ))
70
+
71
+
72
+ if not diff:
73
+ console.print("[dim]No changes were made by LLM.[/dim]")
74
+ return False
75
+
76
+ console.print("\n[bold cyan]--- Diff View ---[/bold cyan]")
77
+ for line in diff:
78
+ if line.startswith("+") and not line.startswith("+++"):
79
+ console.print(f"[green]{line.strip()}[/green]")
80
+ elif line.startswith("-") and not line.startswith("---"):
81
+ console.print(f"[red]{line.strip()}[/red]")
82
+ elif line.startswith("@@"):
83
+ console.print(f"[cyan]{line.strip()}[/cyan]")
84
+ console.print("[bold cyan]--------------[/bold cyan]")
85
+ return True
86
+
87
+ def processSingleFile(file_path, model, replace:bool, skip_diff: bool=False ):
88
+ console.print(f"[cyan]Reading '{file_path.name}'...[/cyan]")
89
+ with open(file_path, "r", encoding="utf-8") as f:
90
+ content = f.read()
91
+
92
+ try:
93
+ console.print("[yellow]Setting up LLM...[/yellow]")
94
+ response = model.generate_content(content)
95
+ proText = response.text
96
+
97
+ if replace:
98
+ outputPath = file_path.with_suffix('.md')
99
+ else:
100
+ outputPath = file_path.with_name(f'{file_path.stem}_processed.md')
101
+
102
+ if not skip_diff:
103
+ has_changes = show_diff(content, proText, file_path.name)
104
+ if has_changes:
105
+ if not typer.confirm("Do you want to save these changes?"):
106
+ console.print(f"[yellow]Skipped saving {file_path.name}.[/yellow]")
107
+ return
108
+
109
+ with open(outputPath, "w", encoding="utf-8") as f:
110
+ f.write(proText)
111
+ if replace and file_path.suffix == '.txt' and outputPath != file_path:
112
+ file_path.unlink()
113
+ console.print(f"[green]Saved successfully to: {outputPath.name}[/green]")
114
+
115
+ except Exception as e:
116
+ console.print(f"[red]An error occurred during LLM processing: {e}[/red]")
117
+
118
+
119
+ @app.command()
120
+ def process(
121
+ file_path: Path= typer.Argument(..., help="Path to file"),
122
+ replace: bool= typer.Option(False, "--replace", "-r", help = "Overwrite the file instead of creating a new one"),
123
+ auto_approve: bool = typer.Option(False, "--auto-approve", "-y", help="Skip the diff view and automatically save changes (useful for large batches)")
124
+ ):
125
+ """
126
+ Reads a note, uses an LLM to fix spelling, format content, and format formulas, then saves it as an Obsidian .md file.
127
+ """
128
+
129
+ if not file_path.exists():
130
+ console.print(f"[red]Error: File '{file_path}' does not exist[/red]")
131
+ raise typer.Exit(code=1)
132
+
133
+ model = setupLLM()
134
+ if file_path.is_dir():
135
+ files_to_process = list(file_path.rglob("*.md")) + list(file_path.rglob("*.txt"))
136
+
137
+ if not files_to_process:
138
+ console.print("[yellow]No .md or .txt files found in the directory.[/yellow]")
139
+ raise typer.Exit(0)
140
+
141
+ # Confirm batch operation
142
+ typer.confirm(f"Found {len(files_to_process)} files. Are you sure you want to process all of them?", abort=True)
143
+
144
+ for file in files_to_process:
145
+ processSingleFile(file, model, replace, skip_diff=auto_approve)
146
+
147
+
148
+ elif file_path.is_file():
149
+ if file_path.suffix not in ['.md','.txt']:
150
+ console.print("[red] Only folders, .md and .txt files are supported[/red]")
151
+ raise typer.Exit(code=1)
152
+ processSingleFile(file_path, model, replace, skip_diff=auto_approve)
153
+
154
+
155
+ if __name__ == "__main__":
156
+ app()
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: obsform
3
+ Version: 0.1.0
4
+ Summary: An LLM-powered CLI to fix, format, and enhance Obsidian notes.
5
+ Requires-Python: >=3.9
6
+ Requires-Dist: typer>=0.9.0
7
+ Requires-Dist: rich>=13.0.0
8
+ Requires-Dist: google-generativeai>=0.4.0
@@ -0,0 +1,9 @@
1
+ pyproject.toml
2
+ obsform/__init__.py
3
+ obsform/main.py
4
+ obsform.egg-info/PKG-INFO
5
+ obsform.egg-info/SOURCES.txt
6
+ obsform.egg-info/dependency_links.txt
7
+ obsform.egg-info/entry_points.txt
8
+ obsform.egg-info/requires.txt
9
+ obsform.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ obsform = obsform.main:app
@@ -0,0 +1,3 @@
1
+ typer>=0.9.0
2
+ rich>=13.0.0
3
+ google-generativeai>=0.4.0
@@ -0,0 +1 @@
1
+ obsform
@@ -0,0 +1,17 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "obsform"
7
+ version = "0.1.0"
8
+ description = "An LLM-powered CLI to fix, format, and enhance Obsidian notes."
9
+ dependencies = [
10
+ "typer>=0.9.0",
11
+ "rich>=13.0.0",
12
+ "google-generativeai>=0.4.0"
13
+ ]
14
+ requires-python = ">=3.9"
15
+
16
+ [project.scripts]
17
+ obsform = "obsform.main:app"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+