notee 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.
- notee-0.1.0/LICENSE +21 -0
- notee-0.1.0/PKG-INFO +67 -0
- notee-0.1.0/README.md +51 -0
- notee-0.1.0/notee/__init__.py +0 -0
- notee-0.1.0/notee/ai/__init__.py +0 -0
- notee-0.1.0/notee/ai/api_key_alive.py +40 -0
- notee-0.1.0/notee/ai/gemini.py +21 -0
- notee-0.1.0/notee/ai/hackclub.py +34 -0
- notee-0.1.0/notee/ai/openai.py +23 -0
- notee-0.1.0/notee/ai/setup_ai.py +53 -0
- notee-0.1.0/notee/cli/commands/ask.py +37 -0
- notee-0.1.0/notee/cli/commands/change_ai_model.py +14 -0
- notee-0.1.0/notee/cli/commands/change_ai_setup.py +19 -0
- notee-0.1.0/notee/cli/commands/create_book.py +34 -0
- notee-0.1.0/notee/cli/commands/create_idea.py +19 -0
- notee-0.1.0/notee/cli/commands/create_movie.py +30 -0
- notee-0.1.0/notee/cli/commands/create_source.py +19 -0
- notee-0.1.0/notee/cli/commands/create_todo.py +18 -0
- notee-0.1.0/notee/cli/commands/open.py +33 -0
- notee-0.1.0/notee/cli/commands/scan.py +20 -0
- notee-0.1.0/notee/cli/commands/search.py +30 -0
- notee-0.1.0/notee/cli/commands/setup.py +73 -0
- notee-0.1.0/notee/cli/commands/toggle.py +84 -0
- notee-0.1.0/notee/cli/utils/__init__.py +0 -0
- notee-0.1.0/notee/cli/utils/config.py +24 -0
- notee-0.1.0/notee/cli/utils/disk_operations.py +13 -0
- notee-0.1.0/notee/cli/utils/logs.py +18 -0
- notee-0.1.0/notee/cli/utils/process_text.py +38 -0
- notee-0.1.0/notee/cli/utils/scaner.py +25 -0
- notee-0.1.0/notee/cli/utils/view_md.py +17 -0
- notee-0.1.0/notee/db/__init__.py +0 -0
- notee-0.1.0/notee/db/add_file.py +13 -0
- notee-0.1.0/notee/db/create_db.py +5 -0
- notee-0.1.0/notee/db/get_file_patches.py +16 -0
- notee-0.1.0/notee/db/query.py +30 -0
- notee-0.1.0/notee/main.py +35 -0
- notee-0.1.0/notee/templates/__init__.py +0 -0
- notee-0.1.0/notee/templates/book.py +27 -0
- notee-0.1.0/notee/templates/idea.py +20 -0
- notee-0.1.0/notee/templates/movie.py +23 -0
- notee-0.1.0/notee/templates/source.py +25 -0
- notee-0.1.0/notee/templates/todo.py +20 -0
- notee-0.1.0/pyproject.toml +23 -0
notee-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 lusparkl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
notee-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: notee
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python CLI to create md files with terminal in seconds
|
|
5
|
+
Requires-Python: >=3.12, <3.14.2
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: mdutils>=1.8.1
|
|
9
|
+
Requires-Dist: typer>=0.24.1
|
|
10
|
+
Requires-Dist: chromadb>=1.5.2
|
|
11
|
+
Requires-Dist: InquirerPy>=0.3.4
|
|
12
|
+
Requires-Dist: openai>=2.26.0
|
|
13
|
+
Requires-Dist: google-genai>=1.66.0
|
|
14
|
+
Requires-Dist: openrouter>=0.7.11
|
|
15
|
+
|
|
16
|
+
# Notee
|
|
17
|
+
|
|
18
|
+
## Notee is python CLI that is able to fully replace your note making app or just be your fast way to create notes in terminal with additional functionality.
|
|
19
|
+
|
|
20
|
+
Sometimes I have an idea that comes from nowhere and to note it I have to open my obsidian app, create file and only then write my note, it's too slow. I created python CLI that will allow you to start writing your notes just after one command of 9 characters. Also there is functionality like ai module or search with vector database included.
|
|
21
|
+
|
|
22
|
+
## Technologies
|
|
23
|
+
|
|
24
|
+
* [Typer](https://typer.tiangolo.com/) - to realize CLI functionality.
|
|
25
|
+
* [Chroma DB](https://www.trychroma.com/) - vector db for AI context and extendet search through your notes.
|
|
26
|
+
* [MdUtils](https://pypi.org/project/mdutils/) - for the md files creation.
|
|
27
|
+
* [Rich](https://rich.readthedocs.io/en/stable/index.html) - to make prints prettier.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## How to use
|
|
32
|
+
|
|
33
|
+
1. Install package by running `pipx install notee`.
|
|
34
|
+
2. Run `notee setup` and go throught instuctions in the console.
|
|
35
|
+
|
|
36
|
+
**Done!** Now you can use it freely🎉
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Commands
|
|
41
|
+
|
|
42
|
+
- `setup` - setup notee before using it.
|
|
43
|
+
- `idea` - create idea note.
|
|
44
|
+
- `movie` - create movie review note.
|
|
45
|
+
- `book` - create book review note.
|
|
46
|
+
- `source` - create note about source to something.
|
|
47
|
+
- `todo` - create todo note.
|
|
48
|
+
- `search QUERY` - search notes related to your query.
|
|
49
|
+
- `open` - open your note in the terminal.
|
|
50
|
+
- `scan` - scan your vault and add new notes to the database.
|
|
51
|
+
- `obsidian_mode --on/--off` - use to manage obsidian mode(only affects how tags are created).
|
|
52
|
+
- `different_folders --on/--off` - toggle between creating folder for each tipe of template or store all notes in core folder.
|
|
53
|
+
- `ai_module --on/-off` - toggle between using ai module or not.
|
|
54
|
+
- `setup_ai` - change Ai provider/api key.
|
|
55
|
+
- `ask QUERY` - ask AI question and it'll answer it based on your notes.
|
|
56
|
+
- `change_ai_model AI_MODEL_NAME` - change default ai model. Please be careful and make sure that you're passing correct model name.
|
|
57
|
+
|
|
58
|
+
| AI provider | Default model |
|
|
59
|
+
|----------|----------|
|
|
60
|
+
| Open AI | `gpt-4.1-mini-2025-04-14` |
|
|
61
|
+
| Google Gemini | `gemini-2.5-flash` |
|
|
62
|
+
| Hack Club AI API | `google/gemini-2.5-flash` |
|
|
63
|
+
|
|
64
|
+
## Found error?
|
|
65
|
+
|
|
66
|
+
Please let me know about it by submitting issue on the github repository. I'll try to fix it as fast as possible!
|
|
67
|
+
|
notee-0.1.0/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Notee
|
|
2
|
+
|
|
3
|
+
## Notee is python CLI that is able to fully replace your note making app or just be your fast way to create notes in terminal with additional functionality.
|
|
4
|
+
|
|
5
|
+
Sometimes I have an idea that comes from nowhere and to note it I have to open my obsidian app, create file and only then write my note, it's too slow. I created python CLI that will allow you to start writing your notes just after one command of 9 characters. Also there is functionality like ai module or search with vector database included.
|
|
6
|
+
|
|
7
|
+
## Technologies
|
|
8
|
+
|
|
9
|
+
* [Typer](https://typer.tiangolo.com/) - to realize CLI functionality.
|
|
10
|
+
* [Chroma DB](https://www.trychroma.com/) - vector db for AI context and extendet search through your notes.
|
|
11
|
+
* [MdUtils](https://pypi.org/project/mdutils/) - for the md files creation.
|
|
12
|
+
* [Rich](https://rich.readthedocs.io/en/stable/index.html) - to make prints prettier.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## How to use
|
|
17
|
+
|
|
18
|
+
1. Install package by running `pipx install notee`.
|
|
19
|
+
2. Run `notee setup` and go throught instuctions in the console.
|
|
20
|
+
|
|
21
|
+
**Done!** Now you can use it freely🎉
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Commands
|
|
26
|
+
|
|
27
|
+
- `setup` - setup notee before using it.
|
|
28
|
+
- `idea` - create idea note.
|
|
29
|
+
- `movie` - create movie review note.
|
|
30
|
+
- `book` - create book review note.
|
|
31
|
+
- `source` - create note about source to something.
|
|
32
|
+
- `todo` - create todo note.
|
|
33
|
+
- `search QUERY` - search notes related to your query.
|
|
34
|
+
- `open` - open your note in the terminal.
|
|
35
|
+
- `scan` - scan your vault and add new notes to the database.
|
|
36
|
+
- `obsidian_mode --on/--off` - use to manage obsidian mode(only affects how tags are created).
|
|
37
|
+
- `different_folders --on/--off` - toggle between creating folder for each tipe of template or store all notes in core folder.
|
|
38
|
+
- `ai_module --on/-off` - toggle between using ai module or not.
|
|
39
|
+
- `setup_ai` - change Ai provider/api key.
|
|
40
|
+
- `ask QUERY` - ask AI question and it'll answer it based on your notes.
|
|
41
|
+
- `change_ai_model AI_MODEL_NAME` - change default ai model. Please be careful and make sure that you're passing correct model name.
|
|
42
|
+
|
|
43
|
+
| AI provider | Default model |
|
|
44
|
+
|----------|----------|
|
|
45
|
+
| Open AI | `gpt-4.1-mini-2025-04-14` |
|
|
46
|
+
| Google Gemini | `gemini-2.5-flash` |
|
|
47
|
+
| Hack Club AI API | `google/gemini-2.5-flash` |
|
|
48
|
+
|
|
49
|
+
## Found error?
|
|
50
|
+
|
|
51
|
+
Please let me know about it by submitting issue on the github repository. I'll try to fix it as fast as possible!
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from google import genai
|
|
2
|
+
import openrouter
|
|
3
|
+
import requests
|
|
4
|
+
import google
|
|
5
|
+
import openai
|
|
6
|
+
|
|
7
|
+
def openai_apikey_check(api_key: str) -> bool:
|
|
8
|
+
client = openai.Client(api_key=api_key)
|
|
9
|
+
try:
|
|
10
|
+
client.models.list()
|
|
11
|
+
return True
|
|
12
|
+
except openai.AuthenticationError:
|
|
13
|
+
return False
|
|
14
|
+
|
|
15
|
+
def gemini_apikey_check(api_key: str) -> bool:
|
|
16
|
+
client = genai.Client(api_key=api_key)
|
|
17
|
+
try:
|
|
18
|
+
client.models.list()
|
|
19
|
+
return True
|
|
20
|
+
except google.genai.errors.ClientError:
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
def hackclub_apikey_check(api_key: str) -> bool:
|
|
24
|
+
client = openrouter.OpenRouter(
|
|
25
|
+
api_key=api_key,
|
|
26
|
+
server_url="https://ai.hackclub.com/proxy/v1",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
client.chat.send(model="google/gemini-2.5-flash",
|
|
31
|
+
messages=[{"role": "user", "content": "Tell me a joke."}],
|
|
32
|
+
max_tokens=5
|
|
33
|
+
)
|
|
34
|
+
return True
|
|
35
|
+
except openrouter.errors.openrouterdefaulterror.OpenRouterDefaultError:
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from notee.cli.utils.logs import send_error
|
|
2
|
+
from notee.db.query import get_context_for_ai
|
|
3
|
+
from notee.ai.api_key_alive import gemini_apikey_check
|
|
4
|
+
from notee.cli.utils.config import get_config
|
|
5
|
+
from google import genai
|
|
6
|
+
|
|
7
|
+
def gemini_request(query):
|
|
8
|
+
config = get_config()
|
|
9
|
+
api_key = config["settings"]["AI_api_key"]
|
|
10
|
+
model = config["settings"]["AI_model"]
|
|
11
|
+
client = genai.Client(api_key=api_key)
|
|
12
|
+
|
|
13
|
+
if not gemini_apikey_check(api_key):
|
|
14
|
+
send_error("Your Gemini api key is not valid. Please change it with <notee change_api_key>.")
|
|
15
|
+
|
|
16
|
+
responce = client.models.generate_content(
|
|
17
|
+
model=model,
|
|
18
|
+
contents=f"You are ai model that is connected to the CLI notes app. Your main goal is to give persise responces without water to user based on their notes related to question. This is notes: {get_context_for_ai(query)}. \n\n This is user request: {query}"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
return responce.text
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from notee.ai.api_key_alive import hackclub_apikey_check
|
|
2
|
+
from notee.db.query import get_context_for_ai
|
|
3
|
+
from notee.cli.utils.config import get_config
|
|
4
|
+
from notee.cli.utils.logs import send_error
|
|
5
|
+
from openrouter import OpenRouter
|
|
6
|
+
|
|
7
|
+
def hackclub_request(query):
|
|
8
|
+
config = get_config()
|
|
9
|
+
api_key = config["settings"]["AI_api_key"]
|
|
10
|
+
model = config["settings"]["AI_model"]
|
|
11
|
+
client = OpenRouter(
|
|
12
|
+
api_key=api_key,
|
|
13
|
+
server_url="https://ai.hackclub.com/proxy/v1",
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
if not hackclub_apikey_check(api_key):
|
|
17
|
+
send_error("Your HackClub api key is not valid. Please change it with <notee change_api_key>.")
|
|
18
|
+
|
|
19
|
+
response = client.chat.send(
|
|
20
|
+
model=model,
|
|
21
|
+
messages=[
|
|
22
|
+
{"role": "user", "content": query},
|
|
23
|
+
{"role": "system", "content": f"You are ai model that is connected to the CLI notes app. Your main goal is to give persise responces without water to user based on their notes related to question. This is notes: {get_context_for_ai(query)}"}
|
|
24
|
+
],
|
|
25
|
+
stream=False,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
return response.choices[0].message.content
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from notee.cli.utils.logs import send_error
|
|
2
|
+
from notee.ai.api_key_alive import openai_apikey_check
|
|
3
|
+
from notee.db.query import get_context_for_ai
|
|
4
|
+
from notee.cli.utils.config import get_config
|
|
5
|
+
import openai
|
|
6
|
+
|
|
7
|
+
def openai_request(query) -> str:
|
|
8
|
+
config = get_config()
|
|
9
|
+
api_key = config["settings"]["AI_api_key"]
|
|
10
|
+
model = config["settings"]["AI_model"]
|
|
11
|
+
client = openai.Client(api_key=api_key)
|
|
12
|
+
|
|
13
|
+
if not openai_apikey_check(api_key):
|
|
14
|
+
send_error("Your openAI api key is not valid. Please change it with <notee change_api_key>.")
|
|
15
|
+
return
|
|
16
|
+
|
|
17
|
+
responce = client.responses.create(
|
|
18
|
+
model=model,
|
|
19
|
+
input=query,
|
|
20
|
+
instructions=f"You are ai model that is connected to the CLI notes app. Your main goal is to give persise responces without water to user based on their notes related to question. This is notes: {get_context_for_ai(query)}"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
return responce.output_text
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from notee.ai.api_key_alive import openai_apikey_check, gemini_apikey_check, hackclub_apikey_check
|
|
2
|
+
from notee.cli.utils.logs import send_process, send_error
|
|
3
|
+
from prompt_toolkit.completion import WordCompleter
|
|
4
|
+
from prompt_toolkit.styles import Style
|
|
5
|
+
from prompt_toolkit import prompt
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
def setup_ai() -> list:
|
|
9
|
+
|
|
10
|
+
custom_style = Style.from_dict({
|
|
11
|
+
"completion-menu": "bg:#1e1e1e #d4d4d4",
|
|
12
|
+
"completion-menu.completion": "bg:#2d2d2d #bbbbbb",
|
|
13
|
+
"completion-menu.completion.current": "bg:#3a3a3a #ffffff bold underline",
|
|
14
|
+
"scrollbar.background": "bg:#121212",
|
|
15
|
+
"scrollbar.button": "bg:#444444",
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
ai_providers = ["OpenAI", "Google Gemini", "HackClub API"]
|
|
19
|
+
providers_completer = WordCompleter(ai_providers, ignore_case=True, match_middle=True)
|
|
20
|
+
send_process("Choose your AI provider:")
|
|
21
|
+
user_choise = prompt(">", completer=providers_completer, complete_while_typing=True, style=custom_style)
|
|
22
|
+
|
|
23
|
+
if user_choise not in ai_providers:
|
|
24
|
+
send_error("Don't have this one yet. Please choose between providers that already implemented.")
|
|
25
|
+
setup_ai()
|
|
26
|
+
|
|
27
|
+
match user_choise:
|
|
28
|
+
case "OpenAI":
|
|
29
|
+
send_process("Now enter your OpenAI api key:")
|
|
30
|
+
api_key = typer.prompt(">")
|
|
31
|
+
|
|
32
|
+
if not openai_apikey_check(api_key):
|
|
33
|
+
setup_ai()
|
|
34
|
+
|
|
35
|
+
return ["OpenAI", "gpt-4.1-mini-2025-04-14", api_key]
|
|
36
|
+
|
|
37
|
+
case "Google Gemini":
|
|
38
|
+
send_process("Now enter your Google Gemini api key:")
|
|
39
|
+
api_key = typer.prompt(">")
|
|
40
|
+
|
|
41
|
+
if not gemini_apikey_check(api_key):
|
|
42
|
+
setup_ai()
|
|
43
|
+
|
|
44
|
+
return ["Google Gemini", "gemini-2.5-flash", api_key]
|
|
45
|
+
|
|
46
|
+
case "HackClub API":
|
|
47
|
+
send_process("Now enter your Hack Club api key:")
|
|
48
|
+
api_key = typer.prompt(">")
|
|
49
|
+
|
|
50
|
+
if not hackclub_apikey_check(api_key):
|
|
51
|
+
setup_ai()
|
|
52
|
+
|
|
53
|
+
return ["HackClub API", "google/gemini-2.5-flash", api_key]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from notee.ai.hackclub import hackclub_request
|
|
2
|
+
from notee.cli.utils.config import get_config
|
|
3
|
+
from notee.cli.utils.logs import send_error
|
|
4
|
+
from notee.ai.gemini import gemini_request
|
|
5
|
+
from notee.ai.openai import openai_request
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from typing import Annotated
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
app = typer.Typer()
|
|
11
|
+
|
|
12
|
+
@app.command("ask")
|
|
13
|
+
def ask(query: Annotated[str, typer.Argument(help="Query for your request.")]):
|
|
14
|
+
"""Ask AI question and it'll answer it based on your notes."""
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
with console.status(" ", spinner="dots"):
|
|
18
|
+
config = get_config()
|
|
19
|
+
|
|
20
|
+
if config["settings"]["AI_module"] == "n":
|
|
21
|
+
send_error("Your ai module isn't setup yet. Use <notee setup_ai> to use this command.")
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
ai_provider = config["settings"]["AI_provider"]
|
|
25
|
+
match ai_provider:
|
|
26
|
+
case "OpenAI":
|
|
27
|
+
responce = openai_request(query)
|
|
28
|
+
case "Google Gemini":
|
|
29
|
+
responce = gemini_request(query)
|
|
30
|
+
case "HackClub API":
|
|
31
|
+
responce = hackclub_request(query)
|
|
32
|
+
case _ :
|
|
33
|
+
send_error("Something wrong happened. Try to setup your ai module again with <notee setup_ai>.")
|
|
34
|
+
|
|
35
|
+
print(responce)
|
|
36
|
+
|
|
37
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from notee.cli.utils.config import get_config, save_config
|
|
2
|
+
from notee.cli.utils.logs import send_success
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
app = typer.Typer()
|
|
7
|
+
|
|
8
|
+
@app.command("change_ai_model")
|
|
9
|
+
def change_ai_model(model_name: Annotated[str, typer.Argument(help="Model name from official docs.")]):
|
|
10
|
+
"""Change your ai model from the default one. Please be careful, if you pick something wrong ai module might stop working till you change model to working one."""
|
|
11
|
+
config = get_config()
|
|
12
|
+
config["settings"]["AI_model"] = model_name
|
|
13
|
+
save_config(config)
|
|
14
|
+
send_success(f"Changed your AI model to the {model_name}. Make sure that it exists!")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from notee.cli.utils.config import get_config, save_config
|
|
2
|
+
from notee.cli.utils.logs import send_success
|
|
3
|
+
from notee.ai.setup_ai import setup_ai
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
app = typer.Typer()
|
|
7
|
+
|
|
8
|
+
@app.command("setup_ai")
|
|
9
|
+
def change_ai_setup():
|
|
10
|
+
"""Change AI provider/api key"""
|
|
11
|
+
config = get_config()
|
|
12
|
+
provider, model, api_key = setup_ai()
|
|
13
|
+
config["settings"]["AI_module"] = "y"
|
|
14
|
+
config["settings"]["AI_provider"] = provider
|
|
15
|
+
config["settings"]["AI_model"] = model
|
|
16
|
+
config["settings"]["AI_api_key"] = api_key
|
|
17
|
+
|
|
18
|
+
send_success("AI setup finished!")
|
|
19
|
+
save_config(config)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from notee.cli.utils.logs import send_process, send_warning, send_success
|
|
2
|
+
from notee.templates.book import create_book_note
|
|
3
|
+
from notee.db.add_file import add_file_to_db
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
app = typer.Typer()
|
|
8
|
+
|
|
9
|
+
@app.command("book")
|
|
10
|
+
def create_book(title: Annotated[str, typer.Argument(help="Title and name for your book note.")]):
|
|
11
|
+
"""
|
|
12
|
+
Create book review note
|
|
13
|
+
"""
|
|
14
|
+
send_process(f"Alright, creating book with title {title}.")
|
|
15
|
+
author = typer.prompt("What is the author of the book?", default="")
|
|
16
|
+
notes = typer.prompt("Write some of your notes about book", default="")
|
|
17
|
+
tags = typer.prompt("Now enter tags for this file, separate them by spaces", default="")
|
|
18
|
+
rating = None
|
|
19
|
+
while not rating:
|
|
20
|
+
resp = typer.prompt("Enter rating for this book from 0 to 10", default="")
|
|
21
|
+
try:
|
|
22
|
+
if int(resp) >=0 and int(resp) <= 10:
|
|
23
|
+
rating = int(resp)
|
|
24
|
+
elif resp == None:
|
|
25
|
+
break
|
|
26
|
+
else:
|
|
27
|
+
send_warning("Please enter number from 0 to 10 to rate your book.")
|
|
28
|
+
except:
|
|
29
|
+
send_warning("Please enter number from 0 to 10 to rate your book.")
|
|
30
|
+
|
|
31
|
+
text, path, date = create_book_note(title, author, notes, tags, rating)
|
|
32
|
+
add_file_to_db(text, path, date)
|
|
33
|
+
send_success(f"Created note {title}")
|
|
34
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from notee.cli.utils.logs import send_process, send_success
|
|
2
|
+
from notee.db.add_file import add_file_to_db
|
|
3
|
+
from notee.templates.idea import create_idea_note
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
app = typer.Typer()
|
|
8
|
+
|
|
9
|
+
@app.command("idea")
|
|
10
|
+
def create_idea(title: Annotated[str, typer.Argument(help="Title and name for your idea note.")]):
|
|
11
|
+
"""Create idea note"""
|
|
12
|
+
send_process(f"Alright, creating idea note '{title}'.")
|
|
13
|
+
description = typer.prompt("Describe your idea", default="")
|
|
14
|
+
tags = typer.prompt("Now enter tags for this file, separate them by spaces", default="")
|
|
15
|
+
|
|
16
|
+
text, path, date = create_idea_note(title, description, tags)
|
|
17
|
+
add_file_to_db(text, path, date)
|
|
18
|
+
send_success(f"Created note {title}")
|
|
19
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from notee.cli.utils.logs import send_process, send_success, send_warning
|
|
2
|
+
from notee.db.add_file import add_file_to_db
|
|
3
|
+
from notee.templates.movie import create_movie_note
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
app = typer.Typer()
|
|
8
|
+
|
|
9
|
+
@app.command("movie")
|
|
10
|
+
def create_movie(title: Annotated[str, typer.Argument(help="Title and name for your movie note.")]):
|
|
11
|
+
"""Create movie review note"""
|
|
12
|
+
send_process(f"Allright, creating note for {title} movie.")
|
|
13
|
+
notes = typer.prompt("Write some of your notes about this movie", default="")
|
|
14
|
+
tags = typer.prompt("Now enter tags for this file, separate them by spaces", default="")
|
|
15
|
+
rating = None
|
|
16
|
+
while not rating:
|
|
17
|
+
resp = typer.prompt("Enter rating for this movie from 0 to 10", default="")
|
|
18
|
+
try:
|
|
19
|
+
if int(resp) >=0 and int(resp) <= 10:
|
|
20
|
+
rating = int(resp)
|
|
21
|
+
elif resp == None:
|
|
22
|
+
break
|
|
23
|
+
else:
|
|
24
|
+
send_warning("Please enter number from 0 to 10 to rate your movie.")
|
|
25
|
+
except:
|
|
26
|
+
send_warning("Please enter number from 0 to 10 to rate your movie.")
|
|
27
|
+
|
|
28
|
+
text, path, date = create_movie_note(title, notes, tags, rating)
|
|
29
|
+
add_file_to_db(text, path, date)
|
|
30
|
+
send_success(f"Created note {title}")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from notee.cli.utils.logs import send_process, send_success
|
|
2
|
+
from notee.templates.source import create_source_note
|
|
3
|
+
from notee.db.add_file import add_file_to_db
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
app = typer.Typer()
|
|
8
|
+
|
|
9
|
+
@app.command("source")
|
|
10
|
+
def create_source(title: Annotated[str, typer.Argument(help="Title and name for your source note.")]):
|
|
11
|
+
"""Create note with source for smth"""
|
|
12
|
+
send_process(f"Allright, creating note for {title} source.")
|
|
13
|
+
notes = typer.prompt("Write some of your notes about this source", default="")
|
|
14
|
+
links = typer.prompt("Write links to this source, separate them by space too", default="")
|
|
15
|
+
tags = typer.prompt("Now enter tags for this file, separate them by spaces", default="")
|
|
16
|
+
|
|
17
|
+
text, path, date = create_source_note(title, notes, tags, links)
|
|
18
|
+
add_file_to_db(text, path, date)
|
|
19
|
+
send_success(f"Created note {title}")
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from notee.cli.utils.logs import send_process, send_success
|
|
2
|
+
from notee.templates.todo import create_todo_note
|
|
3
|
+
from notee.db.add_file import add_file_to_db
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
app = typer.Typer()
|
|
8
|
+
|
|
9
|
+
@app.command("todo")
|
|
10
|
+
def create_idea(title: Annotated[str, typer.Argument(help="Title and name for your todo note.")]):
|
|
11
|
+
"""Create todo note"""
|
|
12
|
+
send_process(f"Alright, creating todo note '{title}'.")
|
|
13
|
+
description = typer.prompt("Describe what you need to do", default="")
|
|
14
|
+
tags = typer.prompt("Now enter tags for this file, separate them by spaces", default="")
|
|
15
|
+
|
|
16
|
+
text, path, date = create_todo_note(title, description, tags)
|
|
17
|
+
add_file_to_db(text, path, date)
|
|
18
|
+
send_success(f"Created note {title}")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from notee.cli.utils.logs import send_error, send_process
|
|
2
|
+
from prompt_toolkit.completion import WordCompleter
|
|
3
|
+
from notee.db.get_file_patches import get_file_pathes
|
|
4
|
+
from notee.cli.utils.view_md import view_md_file
|
|
5
|
+
from prompt_toolkit.styles import Style
|
|
6
|
+
from prompt_toolkit import prompt
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
app = typer.Typer()
|
|
10
|
+
|
|
11
|
+
@app.command("open")
|
|
12
|
+
def open_note():
|
|
13
|
+
"""Open some note in terminal"""
|
|
14
|
+
|
|
15
|
+
custom_style = Style.from_dict({
|
|
16
|
+
"completion-menu": "bg:#1e1e1e #d4d4d4",
|
|
17
|
+
"completion-menu.completion": "bg:#2d2d2d #bbbbbb",
|
|
18
|
+
"completion-menu.completion.current": "bg:#3a3a3a #ffffff bold underline",
|
|
19
|
+
"scrollbar.background": "bg:#121212",
|
|
20
|
+
"scrollbar.button": "bg:#444444",
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
pathes = get_file_pathes()
|
|
24
|
+
pathes_completer = WordCompleter(pathes, ignore_case=True, match_middle=True)
|
|
25
|
+
send_process("Enter path for your note:")
|
|
26
|
+
user_choise = prompt(">", completer=pathes_completer, complete_while_typing=True, style=custom_style)
|
|
27
|
+
|
|
28
|
+
if user_choise not in pathes:
|
|
29
|
+
send_error("Can't find this path in db. Please use <notee scan> and try again.")
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
view_md_file(user_choise)
|
|
33
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from notee.cli.utils.logs import send_success
|
|
2
|
+
from notee.cli.utils.config import get_config
|
|
3
|
+
from notee.cli.utils.scaner import scan
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
app = typer.Typer()
|
|
8
|
+
|
|
9
|
+
@app.command("scan")
|
|
10
|
+
def scan_folder():
|
|
11
|
+
"""Scan folder to add notes to the db if they're not already there."""
|
|
12
|
+
console = Console()
|
|
13
|
+
with console.status(" ", spinner="dots"):
|
|
14
|
+
config = get_config()
|
|
15
|
+
notes_folder = config["patches"]["base_folder"]
|
|
16
|
+
n_new_files = scan(notes_folder)
|
|
17
|
+
if n_new_files:
|
|
18
|
+
send_success(f"Scanned your vault, added {n_new_files} new files to the db!")
|
|
19
|
+
else:
|
|
20
|
+
send_success("Scanned your vault. Nothing new appeared from last scan.")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from notee.cli.utils.logs import send_success, send_warning
|
|
2
|
+
from notee.cli.utils.view_md import view_md_file
|
|
3
|
+
from notee.db.query import search_in_db
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from InquirerPy import inquirer
|
|
6
|
+
from typing import Annotated
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
app = typer.Typer()
|
|
10
|
+
|
|
11
|
+
@app.command("search")
|
|
12
|
+
def search(query: Annotated[str, typer.Argument(help="Query for your search request")]):
|
|
13
|
+
"""Search for your note by query"""
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
with console.status(" ", spinner="dots"):
|
|
17
|
+
responce = search_in_db(query)
|
|
18
|
+
if not responce:
|
|
19
|
+
send_warning("Can't find anything😢 Try another query.")
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
responce.append("Exit")
|
|
23
|
+
send_success("Here is your notes:")
|
|
24
|
+
choice = inquirer.select(message="Select please", choices=responce, default="Exit").execute()
|
|
25
|
+
|
|
26
|
+
if choice == "Exit":
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
view_md_file(choice)
|
|
30
|
+
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from notee.cli.utils.logs import send_success, send_process, send_error, send_warning
|
|
2
|
+
from notee.cli.utils.disk_operations import create_config_file, is_folder_exists
|
|
3
|
+
from notee.db.create_db import create_db
|
|
4
|
+
from notee.ai.setup_ai import setup_ai
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typer import Typer
|
|
7
|
+
import configparser
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
app = Typer()
|
|
11
|
+
|
|
12
|
+
@app.command("setup")
|
|
13
|
+
def setup():
|
|
14
|
+
"""Setup notee before using it."""
|
|
15
|
+
config_path = create_config_file()
|
|
16
|
+
config = configparser.ConfigParser()
|
|
17
|
+
config["patches"] = {}
|
|
18
|
+
config.add_section("settings")
|
|
19
|
+
TEMPLATES = ["book", "idea", "movie", "source", "todo"]
|
|
20
|
+
|
|
21
|
+
send_process("Now it's time to setup your notes folder.")
|
|
22
|
+
path = typer.prompt("Enter folder path")
|
|
23
|
+
exists = is_folder_exists(path)
|
|
24
|
+
if not exists:
|
|
25
|
+
send_error(f"Can't find folder {path}, please provide already created folder.")
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
config["patches"]["base_folder"] = path
|
|
29
|
+
send_process("Create different folders for each temlate type?")
|
|
30
|
+
different_folders = typer.confirm("Create different folders?")
|
|
31
|
+
|
|
32
|
+
if different_folders:
|
|
33
|
+
for template in TEMPLATES:
|
|
34
|
+
template_folder_path = Path(f"{path}/{template}")
|
|
35
|
+
template_folder_path.touch()
|
|
36
|
+
config["patches"][template] = f"{path}/{template}"
|
|
37
|
+
else:
|
|
38
|
+
for template in TEMPLATES:
|
|
39
|
+
config["patches"][template] = path
|
|
40
|
+
|
|
41
|
+
send_process("Create notes with default MD or for obsidian? (You can change it later)")
|
|
42
|
+
obsidian_mode = typer.confirm("Use obsidian mode?")
|
|
43
|
+
|
|
44
|
+
if obsidian_mode:
|
|
45
|
+
config["settings"]["obsidian_mode"] = "y"
|
|
46
|
+
else:
|
|
47
|
+
config["settings"]["obsidian_mode"] = "n"
|
|
48
|
+
|
|
49
|
+
send_process("Now enter path for the database folder (we'll need it for search feature and AI interactions)")
|
|
50
|
+
db_path = typer.prompt("Database folder")
|
|
51
|
+
exists = is_folder_exists(path)
|
|
52
|
+
if not exists:
|
|
53
|
+
send_error(f"Can't find folder {path}, please provide already created folder.")
|
|
54
|
+
return
|
|
55
|
+
config["settings"]["db_path"] = db_path
|
|
56
|
+
create_db(db_path)
|
|
57
|
+
|
|
58
|
+
send_process("Do you want to use notee AI module?(You can change it later)")
|
|
59
|
+
ai_module = typer.confirm("Use ai module?")
|
|
60
|
+
|
|
61
|
+
if ai_module:
|
|
62
|
+
provider, model, api_key = setup_ai()
|
|
63
|
+
config["settings"]["AI_module"] = "y"
|
|
64
|
+
config["settings"]["AI_provider"] = provider
|
|
65
|
+
config["settings"]["AI_model"] = model
|
|
66
|
+
config["settings"]["AI_api_key"] = api_key
|
|
67
|
+
else:
|
|
68
|
+
config["settings"]["AI_module"] = "n"
|
|
69
|
+
|
|
70
|
+
with open(config_path, 'w', encoding='utf-8') as configfile:
|
|
71
|
+
config.write(configfile)
|
|
72
|
+
|
|
73
|
+
send_success("Finished setup! Use <notee --help> to get all commands")
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from notee.cli.utils.logs import send_success, send_process, send_error
|
|
2
|
+
from notee.cli.utils.config import get_config, save_config
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
app = typer.Typer()
|
|
7
|
+
|
|
8
|
+
@app.command("obsidian_mode")
|
|
9
|
+
def togle_obsidian_mode(
|
|
10
|
+
on: bool = typer.Option(None, "--on", help="Enable obsidian mode."),
|
|
11
|
+
off: bool = typer.Option(None, "--off", help="Disable obsidian mode.")
|
|
12
|
+
):
|
|
13
|
+
"""Toggle between creating notes for obsidian or regular md format.
|
|
14
|
+
--on - Enable obsidian mode
|
|
15
|
+
--off - Disable obsidian mode
|
|
16
|
+
"""
|
|
17
|
+
if bool(on) == bool(off):
|
|
18
|
+
send_error("Use only --on or --off flags please.")
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
config = get_config()
|
|
22
|
+
|
|
23
|
+
if on:
|
|
24
|
+
config["settings"]["obsidian_mode"] = "y"
|
|
25
|
+
save_config(config)
|
|
26
|
+
send_success("Enabled obsidian mode🥳")
|
|
27
|
+
else:
|
|
28
|
+
config["settings"]["obsidian_mode"] = "n"
|
|
29
|
+
save_config(config)
|
|
30
|
+
send_success("Disabled obsidian mode👌")
|
|
31
|
+
|
|
32
|
+
@app.command("different_folders")
|
|
33
|
+
def togle_different_folders(
|
|
34
|
+
on: bool = typer.Option(None, "--on", help="Enable different folders."),
|
|
35
|
+
off: bool = typer.Option(None, "--off", help="Disable different folders.")
|
|
36
|
+
):
|
|
37
|
+
"""Toggle between creating folder for each tipe of template or store all notes in core folder.
|
|
38
|
+
--on - Enable different folders.
|
|
39
|
+
--off - Disable different folders.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
if bool(on) == bool(off):
|
|
43
|
+
send_error("Use only --on or --off flags please.")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
TEMPLATES = ["book", "idea", "movie", "source", "todo"]
|
|
47
|
+
config = get_config()
|
|
48
|
+
base_path = config["patches"]["base_folder"]
|
|
49
|
+
|
|
50
|
+
if on:
|
|
51
|
+
for template in TEMPLATES:
|
|
52
|
+
template_folder_path = Path(f"{base_path}/{template}")
|
|
53
|
+
template_folder_path.touch()
|
|
54
|
+
config["patches"][template] = f"{base_path}/{template}"
|
|
55
|
+
send_success("Enabled different folders. Your notes will be saving into different folders from now.")
|
|
56
|
+
else:
|
|
57
|
+
for template in TEMPLATES:
|
|
58
|
+
config["patches"][template] = base_path
|
|
59
|
+
send_success("Disabled different folders. Your notes will be saving into base folder from now.")
|
|
60
|
+
|
|
61
|
+
save_config(config)
|
|
62
|
+
|
|
63
|
+
@app.command("ai_module")
|
|
64
|
+
def togle_different_folders(
|
|
65
|
+
on: bool = typer.Option(None, "--on", help="Enable different folders."),
|
|
66
|
+
off: bool = typer.Option(None, "--off", help="Disable different folders.")
|
|
67
|
+
):
|
|
68
|
+
"""Toggle between using ai module and not.
|
|
69
|
+
--on - Enable ai module.
|
|
70
|
+
--off - Disable ai module.
|
|
71
|
+
"""
|
|
72
|
+
if bool(on) == bool(off):
|
|
73
|
+
send_error("Use only --on or --off flags please.")
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
config = get_config()
|
|
77
|
+
if on:
|
|
78
|
+
config["settings"]["AI_module"] = "y"
|
|
79
|
+
send_success("Your AI module is on now. Please don't forget to use <notee setup_ai> if you haven't yet.")
|
|
80
|
+
else:
|
|
81
|
+
config["settings"]["AI_module"] = "n"
|
|
82
|
+
send_success("Your AI module is off now. Use <notee ai_module --on> to turn it on again.")
|
|
83
|
+
|
|
84
|
+
save_config(config)
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import configparser
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_config() -> configparser.ConfigParser:
|
|
8
|
+
config = configparser.ConfigParser()
|
|
9
|
+
app_dir = typer.get_app_dir("notee")
|
|
10
|
+
config_path = Path(app_dir) / "config.ini"
|
|
11
|
+
|
|
12
|
+
if not config_path.exists():
|
|
13
|
+
raise FileNotFoundError("Can't find config. Please use <notee setup> to create it.")
|
|
14
|
+
|
|
15
|
+
config.read(config_path, encoding="utf-8")
|
|
16
|
+
return config
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def save_config(config: configparser.ConfigParser) -> None:
|
|
20
|
+
app_dir = typer.get_app_dir("notee")
|
|
21
|
+
config_path = Path(app_dir) / "config.ini"
|
|
22
|
+
|
|
23
|
+
with open(config_path, "w", encoding="utf-8") as configfile:
|
|
24
|
+
config.write(configfile)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import typer
|
|
3
|
+
|
|
4
|
+
def is_folder_exists(path):
|
|
5
|
+
return Path(path).is_dir()
|
|
6
|
+
|
|
7
|
+
def create_config_file() -> str:
|
|
8
|
+
app_dir = typer.get_app_dir("notee")
|
|
9
|
+
config_path = Path(app_dir) / "config.ini"
|
|
10
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
11
|
+
Path.touch(config_path)
|
|
12
|
+
|
|
13
|
+
return config_path
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from rich.text import Text
|
|
2
|
+
from rich import print
|
|
3
|
+
|
|
4
|
+
def send_success(text) -> None:
|
|
5
|
+
message = Text(text, style="bold spring_green1")
|
|
6
|
+
print(message)
|
|
7
|
+
|
|
8
|
+
def send_warning(text) -> None:
|
|
9
|
+
message = Text(text, style="bold orange1")
|
|
10
|
+
print(message)
|
|
11
|
+
|
|
12
|
+
def send_error(text) -> None:
|
|
13
|
+
message = Text(text, style="bold deep_pink1")
|
|
14
|
+
print(message)
|
|
15
|
+
|
|
16
|
+
def send_process(text) -> None:
|
|
17
|
+
message = Text(text, style="italic slate_blue1")
|
|
18
|
+
print(message)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from notee.cli.utils.config import get_config
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
def process_tags(tags: str):
|
|
5
|
+
config = get_config()
|
|
6
|
+
obsidian = config["settings"]["obsidian_mode"]
|
|
7
|
+
|
|
8
|
+
separated = tags.split(" ")
|
|
9
|
+
result = "Tags: "
|
|
10
|
+
for tag in separated:
|
|
11
|
+
if obsidian == "y":
|
|
12
|
+
result += f" - [[{tag}]]"
|
|
13
|
+
else:
|
|
14
|
+
result += f"#{tag} "
|
|
15
|
+
|
|
16
|
+
return result
|
|
17
|
+
|
|
18
|
+
def process_links(links: str):
|
|
19
|
+
separated = links.split(" ")
|
|
20
|
+
result = ""
|
|
21
|
+
for link in separated:
|
|
22
|
+
result += f"{link} \n"
|
|
23
|
+
|
|
24
|
+
return result
|
|
25
|
+
|
|
26
|
+
def process_if_exists(cond, text):
|
|
27
|
+
if cond:
|
|
28
|
+
return text
|
|
29
|
+
return ""
|
|
30
|
+
|
|
31
|
+
def convert_obsidian_tags(text):
|
|
32
|
+
pattern = r"\[\[(.*?)\]\]"
|
|
33
|
+
|
|
34
|
+
def replace_with_hash(match):
|
|
35
|
+
tag = match.group(1).strip()
|
|
36
|
+
return f"#{tag}"
|
|
37
|
+
|
|
38
|
+
return re.sub(pattern, replace_with_hash, text)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from notee.db.add_file import add_file_to_db
|
|
2
|
+
from notee.db.get_file_patches import get_file_pathes
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
def add_from_file(path):
|
|
6
|
+
with open(path, "r", encoding="utf-8") as file:
|
|
7
|
+
text = file.read()
|
|
8
|
+
add_file_to_db(text, path)
|
|
9
|
+
file.close()
|
|
10
|
+
|
|
11
|
+
def scan(folder_path: str):
|
|
12
|
+
names = get_file_pathes()
|
|
13
|
+
n_new_files = 0
|
|
14
|
+
with os.scandir(folder_path) as directory:
|
|
15
|
+
for entity in directory:
|
|
16
|
+
if entity.is_dir():
|
|
17
|
+
n_new_files += scan(entity.path)
|
|
18
|
+
else:
|
|
19
|
+
if entity.path not in names:
|
|
20
|
+
add_from_file(entity.path)
|
|
21
|
+
n_new_files += 1
|
|
22
|
+
return n_new_files
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from notee.cli.utils.process_text import convert_obsidian_tags
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.markdown import Markdown
|
|
4
|
+
|
|
5
|
+
def view_md_file(path):
|
|
6
|
+
console = Console()
|
|
7
|
+
custom_theme = {
|
|
8
|
+
"markdown.header": "bold magenta",
|
|
9
|
+
"markdown.code": "italic cyan",
|
|
10
|
+
"markdown.item": "yellow",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
14
|
+
text = f.read()
|
|
15
|
+
text = convert_obsidian_tags(text)
|
|
16
|
+
markdown = Markdown(text, justify="left", code_theme="dracula")
|
|
17
|
+
console.print(markdown)
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from notee.cli.utils.config import get_config
|
|
2
|
+
import chromadb
|
|
3
|
+
import uuid
|
|
4
|
+
|
|
5
|
+
def add_file_to_db(text: str, path: str, created_at = ""):
|
|
6
|
+
config = get_config()
|
|
7
|
+
db_path = config["settings"]["db_path"]
|
|
8
|
+
client = chromadb.PersistentClient(db_path)
|
|
9
|
+
collection = client.get_collection("notee")
|
|
10
|
+
collection.add(ids=str(uuid.uuid4()), documents=[text], metadatas=[{
|
|
11
|
+
"path": path,
|
|
12
|
+
"created_at": created_at
|
|
13
|
+
}])
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from notee.cli.utils.config import get_config
|
|
2
|
+
import chromadb
|
|
3
|
+
|
|
4
|
+
def get_file_pathes() -> list:
|
|
5
|
+
config = get_config()
|
|
6
|
+
db_path = config["settings"]["db_path"]
|
|
7
|
+
client = chromadb.PersistentClient(db_path)
|
|
8
|
+
collection = client.get_collection("notee")
|
|
9
|
+
result = collection.get(include=["metadatas"], limit=10000)
|
|
10
|
+
pathes = []
|
|
11
|
+
for metadata in result["metadatas"]:
|
|
12
|
+
pathes.append(metadata["path"])
|
|
13
|
+
|
|
14
|
+
return pathes
|
|
15
|
+
|
|
16
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from notee.cli.utils.config import get_config
|
|
2
|
+
import chromadb
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def search_in_db(query: str) -> list:
|
|
6
|
+
config = get_config()
|
|
7
|
+
db_path = config["settings"]["db_path"]
|
|
8
|
+
client = chromadb.PersistentClient(db_path)
|
|
9
|
+
collection = client.get_collection("notee")
|
|
10
|
+
result = collection.query(query_texts=query, n_results=3)
|
|
11
|
+
|
|
12
|
+
responce = []
|
|
13
|
+
for metadata in result["metadatas"][0]:
|
|
14
|
+
path = metadata["path"]
|
|
15
|
+
responce.append(path)
|
|
16
|
+
|
|
17
|
+
return responce
|
|
18
|
+
|
|
19
|
+
def get_context_for_ai(query: str) -> str:
|
|
20
|
+
config = get_config()
|
|
21
|
+
db_path = config["settings"]["db_path"]
|
|
22
|
+
client = chromadb.PersistentClient(db_path)
|
|
23
|
+
collection = client.get_collection("notee")
|
|
24
|
+
result = collection.query(query_texts=query, n_results=5)
|
|
25
|
+
context = ""
|
|
26
|
+
for document in result["documents"][0]:
|
|
27
|
+
context += document
|
|
28
|
+
|
|
29
|
+
return context
|
|
30
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from notee.cli.commands.create_book import app as book_app
|
|
2
|
+
from notee.cli.commands.create_idea import app as idea_app
|
|
3
|
+
from notee.cli.commands.create_movie import app as movie_app
|
|
4
|
+
from notee.cli.commands.create_source import app as source_app
|
|
5
|
+
from notee.cli.commands.create_todo import app as todo_app
|
|
6
|
+
from notee.cli.commands.setup import app as setup_app
|
|
7
|
+
from notee.cli.commands.search import app as search_app
|
|
8
|
+
from notee.cli.commands.toggle import app as togle_app
|
|
9
|
+
from notee.cli.commands.scan import app as scan_app
|
|
10
|
+
from notee.cli.commands.open import app as open_app
|
|
11
|
+
from notee.cli.commands.ask import app as ask_app
|
|
12
|
+
from notee.cli.commands.change_ai_model import app as ai_model_app
|
|
13
|
+
from notee.cli.commands.change_ai_setup import app as ai_setup_app
|
|
14
|
+
from typer import Typer
|
|
15
|
+
|
|
16
|
+
app = Typer()
|
|
17
|
+
|
|
18
|
+
app.add_typer(setup_app)
|
|
19
|
+
app.add_typer(book_app)
|
|
20
|
+
app.add_typer(idea_app)
|
|
21
|
+
app.add_typer(movie_app)
|
|
22
|
+
app.add_typer(source_app)
|
|
23
|
+
app.add_typer(todo_app)
|
|
24
|
+
app.add_typer(search_app)
|
|
25
|
+
app.add_typer(scan_app)
|
|
26
|
+
app.add_typer(open_app)
|
|
27
|
+
app.add_typer(togle_app)
|
|
28
|
+
app.add_typer(ask_app)
|
|
29
|
+
app.add_typer(ai_setup_app)
|
|
30
|
+
app.add_typer(ai_model_app)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
if __name__ == "__main__":
|
|
35
|
+
app()
|
|
File without changes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from notee.cli.utils.process_text import process_tags, process_if_exists
|
|
2
|
+
from notee.cli.utils.config import get_config
|
|
3
|
+
from mdutils.fileutils.fileutils import MarkDownFile
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
def create_book_note(title: str, author: str = None, notes: str = None, tags: str = None, rating: int = None):
|
|
7
|
+
date = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
8
|
+
data = f"""
|
|
9
|
+
{date}
|
|
10
|
+
{process_tags(tags) if tags else ""}
|
|
11
|
+
|
|
12
|
+
# {title}
|
|
13
|
+
|
|
14
|
+
{process_if_exists(author, f"By **{author}**")}
|
|
15
|
+
|
|
16
|
+
## Notes
|
|
17
|
+
|
|
18
|
+
{process_if_exists(notes, notes)}
|
|
19
|
+
|
|
20
|
+
{process_if_exists(rating, f"**{rating}/10⭐**")}
|
|
21
|
+
"""
|
|
22
|
+
config = get_config()
|
|
23
|
+
file = MarkDownFile(name=title, dirname=config["patches"]["book"])
|
|
24
|
+
file.append_end(data)
|
|
25
|
+
|
|
26
|
+
return [data, f"{config["patches"]["book"]}/{title}.md", date]
|
|
27
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from notee.cli.utils.process_text import process_tags
|
|
2
|
+
from mdutils.fileutils.fileutils import MarkDownFile
|
|
3
|
+
from notee.cli.utils.config import get_config
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
def create_idea_note(title, description: str = None, tags: str = None):
|
|
7
|
+
date = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
8
|
+
data = f"""
|
|
9
|
+
{date}
|
|
10
|
+
{process_tags(tags) if tags else ""}
|
|
11
|
+
|
|
12
|
+
# {title}
|
|
13
|
+
|
|
14
|
+
{description}
|
|
15
|
+
"""
|
|
16
|
+
config = get_config()
|
|
17
|
+
file = MarkDownFile(name=title, dirname=config["patches"]["idea"])
|
|
18
|
+
file.append_end(data)
|
|
19
|
+
|
|
20
|
+
return [data, f"{config["patches"]["idea"]}/{title}.md", date]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from notee.cli.utils.process_text import process_tags, process_if_exists
|
|
2
|
+
from notee.cli.utils.config import get_config
|
|
3
|
+
from mdutils.fileutils.fileutils import MarkDownFile
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
def create_movie_note(title: str, notes: str = None, tags: str = None, rating: int = None):
|
|
7
|
+
date = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
8
|
+
data = f"""
|
|
9
|
+
{date}
|
|
10
|
+
{process_tags(tags) if tags else ""}
|
|
11
|
+
|
|
12
|
+
# {title}
|
|
13
|
+
|
|
14
|
+
{process_if_exists(notes, notes)}
|
|
15
|
+
|
|
16
|
+
{process_if_exists(rating, f"**{rating}/10⭐**")}
|
|
17
|
+
"""
|
|
18
|
+
config = get_config()
|
|
19
|
+
file = MarkDownFile(name=title, dirname=config["patches"]["movie"])
|
|
20
|
+
file.append_end(data)
|
|
21
|
+
|
|
22
|
+
return [data, f"{config["patches"]["movie"]}/{title}.md", date]
|
|
23
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from notee.cli.utils.process_text import process_tags, process_if_exists, process_links
|
|
2
|
+
from notee.cli.utils.config import get_config
|
|
3
|
+
from mdutils.fileutils.fileutils import MarkDownFile
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
def create_source_note(title: str, notes: str = None, tags: str = None, links: str = None):
|
|
7
|
+
date = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
8
|
+
data = f"""
|
|
9
|
+
{date}
|
|
10
|
+
{process_tags(tags) if tags else ""}
|
|
11
|
+
|
|
12
|
+
# {title}
|
|
13
|
+
|
|
14
|
+
{process_if_exists(notes, notes)}
|
|
15
|
+
|
|
16
|
+
## Links
|
|
17
|
+
|
|
18
|
+
{process_links if links else ""}
|
|
19
|
+
"""
|
|
20
|
+
config = get_config()
|
|
21
|
+
file = MarkDownFile(name=title, dirname=config["patches"]["source"])
|
|
22
|
+
file.append_end(data)
|
|
23
|
+
|
|
24
|
+
return [data, f"{config["patches"]["source"]}/{title}.md", date]
|
|
25
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from notee.cli.utils.process_text import process_tags
|
|
2
|
+
from mdutils.fileutils.fileutils import MarkDownFile
|
|
3
|
+
from notee.cli.utils.config import get_config
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
def create_todo_note(title, description: str = None, tags: str = None):
|
|
7
|
+
date = datetime.now().strftime("%Y-%m-%d %H:%M")
|
|
8
|
+
data = f"""
|
|
9
|
+
{date}
|
|
10
|
+
{process_tags(tags) if tags else ""}
|
|
11
|
+
|
|
12
|
+
# {title}
|
|
13
|
+
|
|
14
|
+
{description}
|
|
15
|
+
"""
|
|
16
|
+
config = get_config()
|
|
17
|
+
file = MarkDownFile(name=title, dirname=config["patches"]["todo"])
|
|
18
|
+
file.append_end(data)
|
|
19
|
+
|
|
20
|
+
return [data, f"{config["patches"]["todo"]}/{title}.md", date]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "notee"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Python CLI to create md files with terminal in seconds"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = {file = "LICENSE"}
|
|
7
|
+
requires-python = ">=3.12, <3.14.2"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"mdutils>=1.8.1",
|
|
10
|
+
"typer>=0.24.1",
|
|
11
|
+
"chromadb>=1.5.2",
|
|
12
|
+
"InquirerPy>=0.3.4",
|
|
13
|
+
"openai>=2.26.0",
|
|
14
|
+
"google-genai>=1.66.0",
|
|
15
|
+
"openrouter>=0.7.11"
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
notee = "notee.main:app"
|
|
20
|
+
|
|
21
|
+
[build-system]
|
|
22
|
+
requires = ["flit_core<4"]
|
|
23
|
+
build-backend = "flit_core.buildapi"
|