makenote-cli 1.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Hunter Finch
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.
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: makenote-cli
3
+ Version: 1.1.0
4
+ Summary: Fast terminal note logging to GitHub
5
+ Author-email: Hunter Finch <finchyhunter@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/hfinchy12/makenote
8
+ Project-URL: Repository, https://github.com/hfinchy12/makenote
9
+ Project-URL: Bug Tracker, https://github.com/hfinchy12/makenote/issues
10
+ Keywords: notes,cli,github,productivity,terminal,journaling
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Utilities
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: click>=8.1
25
+ Requires-Dist: questionary>=2.0
26
+ Dynamic: license-file
27
+
28
+ # makenote
29
+
30
+ Fast terminal note logging to GitHub. Log notes from your terminal and store them as JSONL files in a GitHub repo — no database, no server, just `gh` and Git.
31
+
32
+ ## Why
33
+
34
+ Developers do a lot — shipped features, fixed bugs, unblocked teammates, learned something new — and most of it goes unrecorded. Come performance review time, or when updating your resume, that work is hard to reconstruct.
35
+
36
+ `makenote` is a lightweight habit for capturing those moments as they happen. A two-second note from the terminal is low enough friction to actually stick. Over time, your notes become a running log of your progress and impact, stored in a GitHub repo you already own and can query however you like.
37
+
38
+ ## How it works
39
+
40
+ `mn` appends timestamped notes to `notes/<subject>/notes.jsonl` in a GitHub repo you configure. Notes are stored as newline-delimited JSON records and written directly via the GitHub Contents API (through the `gh` CLI).
41
+
42
+ ## Requirements
43
+
44
+ - Python 3.9+
45
+ - [gh CLI](https://cli.github.com) — authenticated (`gh auth login`)
46
+ - A GitHub repo to store your notes in
47
+
48
+ ## Installation
49
+
50
+ ### Homebrew (recommended)
51
+
52
+ ```bash
53
+ brew tap hfinchy12/tap
54
+ brew install makenote
55
+ ```
56
+
57
+ ### pip
58
+
59
+ ```bash
60
+ pip install makenote
61
+ ```
62
+
63
+ ## Setup
64
+
65
+ On first run, `mn` launches an interactive config wizard:
66
+
67
+ ```bash
68
+ mn
69
+ ```
70
+
71
+ Or run it explicitly at any time:
72
+
73
+ ```bash
74
+ mn config
75
+ ```
76
+
77
+ You'll be prompted to set:
78
+ - **GitHub repo** — the `owner/repo` where notes will be stored
79
+ - **Subjects** — categories for your notes (e.g. `work`, `ideas`, `todo`)
80
+ - **Default subject** — used by `mn d` for fast logging
81
+
82
+ Config is saved to `~/.config/makenote/config.json`.
83
+
84
+ ## Usage
85
+
86
+ ### Interactive note (with subject picker)
87
+
88
+ ```bash
89
+ mn
90
+ ```
91
+
92
+ Prompts you to select a subject, then enter your note.
93
+
94
+ ### Quick note to default subject
95
+
96
+ ```bash
97
+ mn d "your note here"
98
+ ```
99
+
100
+ Or without an argument to be prompted:
101
+
102
+ ```bash
103
+ mn d
104
+ ```
105
+
106
+ ### List recent notes
107
+
108
+ ```bash
109
+ mn list
110
+ ```
111
+
112
+ Shows the 20 most recent notes across all subjects, sorted newest first.
113
+
114
+ To filter by a specific subject:
115
+
116
+ ```bash
117
+ mn list --subject work
118
+ mn list -s ideas
119
+ ```
120
+
121
+ ### Edit config
122
+
123
+ ```bash
124
+ mn config
125
+ ```
126
+
127
+ ### Version
128
+
129
+ ```bash
130
+ mn --version
131
+ ```
132
+
133
+ ## Contributing
134
+
135
+ Pull requests are welcome. For significant changes, please open an issue first to discuss the approach.
136
+
137
+ 1. Fork the repo
138
+ 2. Create a feature branch (`git checkout -b my-feature`)
139
+ 3. Make your changes
140
+ 4. Run the test suite (`pytest tests/ -x -q`)
141
+ 5. Open a pull request
142
+
143
+ ## License
144
+
145
+ MIT
@@ -0,0 +1,118 @@
1
+ # makenote
2
+
3
+ Fast terminal note logging to GitHub. Log notes from your terminal and store them as JSONL files in a GitHub repo — no database, no server, just `gh` and Git.
4
+
5
+ ## Why
6
+
7
+ Developers do a lot — shipped features, fixed bugs, unblocked teammates, learned something new — and most of it goes unrecorded. Come performance review time, or when updating your resume, that work is hard to reconstruct.
8
+
9
+ `makenote` is a lightweight habit for capturing those moments as they happen. A two-second note from the terminal is low enough friction to actually stick. Over time, your notes become a running log of your progress and impact, stored in a GitHub repo you already own and can query however you like.
10
+
11
+ ## How it works
12
+
13
+ `mn` appends timestamped notes to `notes/<subject>/notes.jsonl` in a GitHub repo you configure. Notes are stored as newline-delimited JSON records and written directly via the GitHub Contents API (through the `gh` CLI).
14
+
15
+ ## Requirements
16
+
17
+ - Python 3.9+
18
+ - [gh CLI](https://cli.github.com) — authenticated (`gh auth login`)
19
+ - A GitHub repo to store your notes in
20
+
21
+ ## Installation
22
+
23
+ ### Homebrew (recommended)
24
+
25
+ ```bash
26
+ brew tap hfinchy12/tap
27
+ brew install makenote
28
+ ```
29
+
30
+ ### pip
31
+
32
+ ```bash
33
+ pip install makenote
34
+ ```
35
+
36
+ ## Setup
37
+
38
+ On first run, `mn` launches an interactive config wizard:
39
+
40
+ ```bash
41
+ mn
42
+ ```
43
+
44
+ Or run it explicitly at any time:
45
+
46
+ ```bash
47
+ mn config
48
+ ```
49
+
50
+ You'll be prompted to set:
51
+ - **GitHub repo** — the `owner/repo` where notes will be stored
52
+ - **Subjects** — categories for your notes (e.g. `work`, `ideas`, `todo`)
53
+ - **Default subject** — used by `mn d` for fast logging
54
+
55
+ Config is saved to `~/.config/makenote/config.json`.
56
+
57
+ ## Usage
58
+
59
+ ### Interactive note (with subject picker)
60
+
61
+ ```bash
62
+ mn
63
+ ```
64
+
65
+ Prompts you to select a subject, then enter your note.
66
+
67
+ ### Quick note to default subject
68
+
69
+ ```bash
70
+ mn d "your note here"
71
+ ```
72
+
73
+ Or without an argument to be prompted:
74
+
75
+ ```bash
76
+ mn d
77
+ ```
78
+
79
+ ### List recent notes
80
+
81
+ ```bash
82
+ mn list
83
+ ```
84
+
85
+ Shows the 20 most recent notes across all subjects, sorted newest first.
86
+
87
+ To filter by a specific subject:
88
+
89
+ ```bash
90
+ mn list --subject work
91
+ mn list -s ideas
92
+ ```
93
+
94
+ ### Edit config
95
+
96
+ ```bash
97
+ mn config
98
+ ```
99
+
100
+ ### Version
101
+
102
+ ```bash
103
+ mn --version
104
+ ```
105
+
106
+ ## Contributing
107
+
108
+ Pull requests are welcome. For significant changes, please open an issue first to discuss the approach.
109
+
110
+ 1. Fork the repo
111
+ 2. Create a feature branch (`git checkout -b my-feature`)
112
+ 3. Make your changes
113
+ 4. Run the test suite (`pytest tests/ -x -q`)
114
+ 5. Open a pull request
115
+
116
+ ## License
117
+
118
+ MIT
@@ -0,0 +1,43 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "makenote-cli"
7
+ version = "1.1.0"
8
+ requires-python = ">=3.9"
9
+ description = "Fast terminal note logging to GitHub"
10
+ readme = "README.md"
11
+ license = "MIT"
12
+ authors = [{ name = "Hunter Finch", email = "finchyhunter@gmail.com" }]
13
+ keywords = ["notes", "cli", "github", "productivity", "terminal", "journaling"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Console",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.9",
20
+ "Programming Language :: Python :: 3.10",
21
+ "Programming Language :: Python :: 3.11",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Topic :: Utilities",
25
+ ]
26
+ dependencies = [
27
+ "click>=8.1",
28
+ "questionary>=2.0",
29
+ ]
30
+
31
+ [project.urls]
32
+ Homepage = "https://github.com/hfinchy12/makenote"
33
+ Repository = "https://github.com/hfinchy12/makenote"
34
+ "Bug Tracker" = "https://github.com/hfinchy12/makenote/issues"
35
+
36
+ [project.scripts]
37
+ mn = "makenote.cli:main"
38
+
39
+ [tool.setuptools.packages.find]
40
+ where = ["src"]
41
+
42
+ [tool.pytest.ini_options]
43
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+ from importlib.metadata import version
2
+ __version__ = version("makenote")
@@ -0,0 +1,112 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+
5
+ import click
6
+ import questionary
7
+
8
+ import makenote.config as _cfg
9
+ import makenote.github as _gh
10
+
11
+
12
+ @click.group(invoke_without_command=True)
13
+ @click.version_option(package_name="makenote", prog_name="mn", message="%(prog)s %(version)s")
14
+ @click.pass_context
15
+ def main(ctx: click.Context) -> None:
16
+ """mn — fast terminal note logging."""
17
+ if not _cfg.config_exists() and ctx.invoked_subcommand != "config":
18
+ click.echo("No config found. Running first-time setup.")
19
+ _cfg.run_config_flow()
20
+ elif ctx.invoked_subcommand is None:
21
+ # Interactive note flow (mn with no subcommand)
22
+ cfg = _cfg.load_config()
23
+ subjects = cfg.get("subjects", [])
24
+ if not subjects:
25
+ click.echo("Error: no subjects configured. Run mn config to add subjects.")
26
+ sys.exit(1)
27
+ choices = ["Add New", questionary.Separator()] + subjects
28
+ subject = questionary.select("Subject:", choices=choices).ask()
29
+ if subject is None:
30
+ sys.exit(0)
31
+ if subject == "Add New":
32
+ subject = questionary.text("New subject name:").ask()
33
+ if subject is None:
34
+ sys.exit(0)
35
+ subject = subject.strip()
36
+ cfg["subjects"].append(subject)
37
+ _cfg.save_config(cfg)
38
+ note_text = questionary.text("Note:").ask()
39
+ if note_text is None:
40
+ sys.exit(0)
41
+ try:
42
+ _gh.write_note(cfg["repo"], subject, note_text)
43
+ except _gh.GhNotInstalledError:
44
+ click.echo("Error: gh CLI not found. Install from https://cli.github.com")
45
+ sys.exit(1)
46
+ except _gh.GhNotAuthError:
47
+ click.echo("Error: gh not authenticated. Run: gh auth login")
48
+ sys.exit(1)
49
+ except _gh.ShaConflictError:
50
+ click.echo("Error: write conflict — file may have changed. Try again.")
51
+ sys.exit(1)
52
+ click.echo("Note logged.")
53
+
54
+
55
+ @main.command()
56
+ def config() -> None:
57
+ """Edit configuration."""
58
+ _cfg.run_config_flow()
59
+
60
+
61
+ @main.command(name="d")
62
+ @click.argument("note_text", required=False, default=None)
63
+ def default_note(note_text: str | None) -> None:
64
+ """Log a note using the default subject."""
65
+ cfg = _cfg.load_config()
66
+ subject = cfg["default_subject"]
67
+ if note_text is None:
68
+ note_text = questionary.text("Note:").ask()
69
+ if note_text is None:
70
+ sys.exit(0)
71
+ try:
72
+ _gh.write_note(cfg["repo"], subject, note_text)
73
+ except _gh.GhNotInstalledError:
74
+ click.echo("Error: gh CLI not found. Install from https://cli.github.com")
75
+ sys.exit(1)
76
+ except _gh.GhNotAuthError:
77
+ click.echo("Error: gh not authenticated. Run: gh auth login")
78
+ sys.exit(1)
79
+ except _gh.ShaConflictError:
80
+ click.echo("Error: write conflict — file may have changed. Try again.")
81
+ sys.exit(1)
82
+ click.echo("Note logged.")
83
+
84
+
85
+ @main.command(name="list")
86
+ @click.option("--subject", "-s", default=None, help="Filter notes to a single subject.")
87
+ def list_notes(subject: str | None) -> None:
88
+ """Print recent notes across all subjects."""
89
+ cfg = _cfg.load_config()
90
+ subjects = cfg.get("subjects", [])
91
+ if subject is not None:
92
+ if subject not in subjects:
93
+ click.echo(f"Error: unknown subject '{subject}'. Run mn config to manage subjects.")
94
+ sys.exit(1)
95
+ subjects = [subject]
96
+ try:
97
+ records = _gh.read_notes(cfg["repo"], subjects)
98
+ except _gh.GhNotInstalledError:
99
+ click.echo("Error: gh CLI not found. Install from https://cli.github.com")
100
+ sys.exit(1)
101
+ except _gh.GhNotAuthError:
102
+ click.echo("Error: gh not authenticated. Run: gh auth login")
103
+ sys.exit(1)
104
+ except _gh.ShaConflictError:
105
+ click.echo("Error: write conflict — file may have changed. Try again.")
106
+ sys.exit(1)
107
+ if not records:
108
+ click.echo("No notes found.")
109
+ return
110
+ for r in records:
111
+ note_display = r["note"][:60] + "..." if len(r["note"]) > 60 else r["note"]
112
+ click.echo(f"{r['date']} {r['subject']:<15} {note_display}")
@@ -0,0 +1,135 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import sys
5
+ import time
6
+ from pathlib import Path
7
+
8
+ import click
9
+ import questionary
10
+
11
+ from makenote.github import GhError, list_subjects
12
+
13
+ CONFIG_PATH = Path.home() / ".config" / "makenote" / "config.json"
14
+
15
+
16
+ def config_exists() -> bool:
17
+ return CONFIG_PATH.exists()
18
+
19
+
20
+ def load_config() -> dict:
21
+ with CONFIG_PATH.open() as f:
22
+ return json.load(f)
23
+
24
+
25
+ def save_config(data: dict) -> None:
26
+ CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
27
+ with CONFIG_PATH.open("w") as f:
28
+ json.dump(data, f, indent=2)
29
+
30
+
31
+ def _offer_import_subjects(data: dict) -> None:
32
+ """Offer to import subjects found in the remote repo that aren't local yet."""
33
+ try:
34
+ remote_subjects = list_subjects(data["repo"])
35
+ except GhError as e:
36
+ click.echo(f"Warning: could not fetch remote subjects: {e}")
37
+ return
38
+
39
+ new_subjects = [s for s in remote_subjects if s not in data["subjects"]]
40
+ if not new_subjects:
41
+ return
42
+
43
+ subject_list = ", ".join(new_subjects)
44
+ answer = questionary.select(
45
+ f"Found {len(new_subjects)} subject(s) in remote repo: {subject_list}. Import all?",
46
+ choices=["Yes", "No"],
47
+ ).ask()
48
+ if answer is None:
49
+ return
50
+ confirmed = answer == "Yes"
51
+
52
+ if not confirmed:
53
+ return
54
+
55
+ data["subjects"].extend(new_subjects)
56
+ click.echo(f"Imported {len(new_subjects)} subject(s).")
57
+
58
+
59
+ def run_config_flow(existing: dict | None = None) -> None:
60
+ """Interactive config editor. Loops until user selects Save and exit."""
61
+ data = existing.copy() if existing else {"repo": "", "default_subject": "", "subjects": []}
62
+
63
+ # Pre-load existing config if it exists and no data passed in
64
+ if existing is None and config_exists():
65
+ data = load_config()
66
+
67
+ while True:
68
+ action = questionary.select(
69
+ "Configure makenote:",
70
+ choices=[
71
+ "Set GitHub repo",
72
+ "Set default subject",
73
+ questionary.Separator(),
74
+ "Add subject",
75
+ "Remove subject",
76
+ "List subjects",
77
+ questionary.Separator(),
78
+ "Save and exit",
79
+ ],
80
+ ).ask()
81
+
82
+ if action is None: # Ctrl-C: exit cleanly, no traceback
83
+ sys.exit(0)
84
+
85
+ if action == "Set GitHub repo":
86
+ repo = questionary.text("GitHub repo (owner/repo):").ask()
87
+ if repo is None:
88
+ sys.exit(0)
89
+ data["repo"] = repo.strip()
90
+ _offer_import_subjects(data)
91
+
92
+ elif action == "Set default subject":
93
+ if not data["subjects"]:
94
+ click.echo("Error: add at least one subject first.")
95
+ click.echo("")
96
+ continue
97
+ subject = questionary.select(
98
+ "Default subject:", choices=data["subjects"]
99
+ ).ask()
100
+ if subject is None:
101
+ sys.exit(0)
102
+ data["default_subject"] = subject
103
+
104
+ elif action == "Add subject":
105
+ name = questionary.text("New subject name:").ask()
106
+ if name is None:
107
+ sys.exit(0)
108
+ name = name.strip()
109
+ if name and name not in data["subjects"]:
110
+ data["subjects"].append(name)
111
+
112
+ elif action == "List subjects":
113
+ if not data["subjects"]:
114
+ click.echo("No subjects configured.")
115
+ else:
116
+ click.echo("\n".join(data["subjects"]))
117
+ time.sleep(0.4)
118
+
119
+ elif action == "Remove subject":
120
+ if not data["subjects"]:
121
+ click.echo("Error: no subjects to remove.")
122
+ click.echo("")
123
+ continue
124
+ to_remove = questionary.select(
125
+ "Remove which subject?", choices=data["subjects"]
126
+ ).ask()
127
+ if to_remove is None:
128
+ sys.exit(0)
129
+ data["subjects"].remove(to_remove)
130
+
131
+ elif action == "Save and exit":
132
+ save_config(data)
133
+ break
134
+
135
+ click.echo("")
@@ -0,0 +1,3 @@
1
+ from pathlib import Path
2
+
3
+ CONFIG_PATH = Path.home() / ".config" / "makenote" / "config.json"