bearcli 1.5.0__tar.gz → 1.6.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.
- {bearcli-1.5.0 → bearcli-1.6.0}/PKG-INFO +27 -6
- {bearcli-1.5.0 → bearcli-1.6.0}/README.md +25 -3
- {bearcli-1.5.0 → bearcli-1.6.0}/pyproject.toml +11 -3
- {bearcli-1.5.0 → bearcli-1.6.0}/src/bearcli/cli/common.py +8 -21
- {bearcli-1.5.0 → bearcli-1.6.0}/src/bearcli/cli/export.py +14 -16
- {bearcli-1.5.0 → bearcli-1.6.0}/src/bearcli/cli/misc.py +8 -8
- {bearcli-1.5.0 → bearcli-1.6.0}/src/bearcli/cli/notes.py +84 -83
- {bearcli-1.5.0 → bearcli-1.6.0}/src/bearcli/cli/tags.py +24 -26
- {bearcli-1.5.0 → bearcli-1.6.0}/src/bearcli/export.py +8 -8
- {bearcli-1.5.0 → bearcli-1.6.0}/src/bearcli/gitsync.py +3 -2
- bearcli-1.6.0/src/bearcli/py.typed +0 -0
- {bearcli-1.5.0 → bearcli-1.6.0}/src/bearcli/tui.py +50 -67
- bearcli-1.5.0/src/bearcli/actions.py +0 -71
- bearcli-1.5.0/src/bearcli/db.py +0 -319
- bearcli-1.5.0/src/bearcli/markdown.py +0 -45
- bearcli-1.5.0/src/bearcli/ops.py +0 -98
- bearcli-1.5.0/src/bearcli/search.py +0 -121
- bearcli-1.5.0/src/bearcli/secrets.py +0 -203
- {bearcli-1.5.0 → bearcli-1.6.0}/LICENSE +0 -0
- {bearcli-1.5.0 → bearcli-1.6.0}/src/bearcli/__init__.py +0 -0
- {bearcli-1.5.0 → bearcli-1.6.0}/src/bearcli/cli/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bearcli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.0
|
|
4
4
|
Summary: The missing CLI for Bear notes — read, search, export, and manage your notes from the terminal
|
|
5
5
|
Keywords: bear,notes,cli,markdown,macos
|
|
6
6
|
Author: Michel Tricot
|
|
@@ -12,8 +12,7 @@ Classifier: Environment :: Console
|
|
|
12
12
|
Classifier: Operating System :: MacOS
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
14
|
Classifier: Topic :: Utilities
|
|
15
|
-
Requires-Dist:
|
|
16
|
-
Requires-Dist: rapidfuzz>=3.14.5
|
|
15
|
+
Requires-Dist: bearkit==1.6.0
|
|
17
16
|
Requires-Dist: textual>=8.2.8
|
|
18
17
|
Requires-Dist: typer>=0.27.0
|
|
19
18
|
Requires-Python: >=3.13
|
|
@@ -105,8 +104,8 @@ bearcli search "quarterly planing" --fuzzy # typo-tolerant, ranked by score
|
|
|
105
104
|
|
|
106
105
|
### Write
|
|
107
106
|
|
|
108
|
-
Writes go through Bear
|
|
109
|
-
|
|
107
|
+
Writes go through the Bear app itself (launching it if needed), and every
|
|
108
|
+
change is verified before the command reports success.
|
|
110
109
|
|
|
111
110
|
```sh
|
|
112
111
|
bearcli create "Meeting notes" --text "agenda..." --tag work
|
|
@@ -183,6 +182,28 @@ teaching AI agents (Claude Code, etc.) how to use `bearcli` ships in
|
|
|
183
182
|
cp -r skills/bear-notes ~/.claude/skills/ # or a project's .claude/skills/
|
|
184
183
|
```
|
|
185
184
|
|
|
185
|
+
## Use as a library
|
|
186
|
+
|
|
187
|
+
The fundamentals ship as their own package on PyPI, `bearkit` - reading
|
|
188
|
+
notes, verified writes, search, and secret detection - with no CLI or TUI
|
|
189
|
+
dependencies (`pip install bearkit`):
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from bearkit import Bear, BearWriteError
|
|
193
|
+
|
|
194
|
+
with Bear() as bear:
|
|
195
|
+
for note in bear.list_notes(tag="work", limit=10):
|
|
196
|
+
print(note.title, note.tags)
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
bear.add_tag(bear.get_note("C44D09DC"), "from-python")
|
|
200
|
+
except BearWriteError:
|
|
201
|
+
print("Bear did not apply the change")
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Full reference: [docs/BEARKIT.md](docs/BEARKIT.md). The package ships typed
|
|
205
|
+
(`py.typed`).
|
|
206
|
+
|
|
186
207
|
## Development
|
|
187
208
|
|
|
188
209
|
```sh
|
|
@@ -192,7 +213,7 @@ uv run ty check src/
|
|
|
192
213
|
uv run python scripts/check_docs.py # docs must cover every command
|
|
193
214
|
```
|
|
194
215
|
|
|
195
|
-
Design notes and
|
|
216
|
+
Design notes and internals: [docs/IMPLEMENTATION.md](docs/IMPLEMENTATION.md).
|
|
196
217
|
Contributor/agent guidelines: [AGENTS.md](AGENTS.md).
|
|
197
218
|
|
|
198
219
|
## License
|
|
@@ -81,8 +81,8 @@ bearcli search "quarterly planing" --fuzzy # typo-tolerant, ranked by score
|
|
|
81
81
|
|
|
82
82
|
### Write
|
|
83
83
|
|
|
84
|
-
Writes go through Bear
|
|
85
|
-
|
|
84
|
+
Writes go through the Bear app itself (launching it if needed), and every
|
|
85
|
+
change is verified before the command reports success.
|
|
86
86
|
|
|
87
87
|
```sh
|
|
88
88
|
bearcli create "Meeting notes" --text "agenda..." --tag work
|
|
@@ -159,6 +159,28 @@ teaching AI agents (Claude Code, etc.) how to use `bearcli` ships in
|
|
|
159
159
|
cp -r skills/bear-notes ~/.claude/skills/ # or a project's .claude/skills/
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
+
## Use as a library
|
|
163
|
+
|
|
164
|
+
The fundamentals ship as their own package on PyPI, `bearkit` - reading
|
|
165
|
+
notes, verified writes, search, and secret detection - with no CLI or TUI
|
|
166
|
+
dependencies (`pip install bearkit`):
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from bearkit import Bear, BearWriteError
|
|
170
|
+
|
|
171
|
+
with Bear() as bear:
|
|
172
|
+
for note in bear.list_notes(tag="work", limit=10):
|
|
173
|
+
print(note.title, note.tags)
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
bear.add_tag(bear.get_note("C44D09DC"), "from-python")
|
|
177
|
+
except BearWriteError:
|
|
178
|
+
print("Bear did not apply the change")
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Full reference: [docs/BEARKIT.md](docs/BEARKIT.md). The package ships typed
|
|
182
|
+
(`py.typed`).
|
|
183
|
+
|
|
162
184
|
## Development
|
|
163
185
|
|
|
164
186
|
```sh
|
|
@@ -168,7 +190,7 @@ uv run ty check src/
|
|
|
168
190
|
uv run python scripts/check_docs.py # docs must cover every command
|
|
169
191
|
```
|
|
170
192
|
|
|
171
|
-
Design notes and
|
|
193
|
+
Design notes and internals: [docs/IMPLEMENTATION.md](docs/IMPLEMENTATION.md).
|
|
172
194
|
Contributor/agent guidelines: [AGENTS.md](AGENTS.md).
|
|
173
195
|
|
|
174
196
|
## License
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "bearcli"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.6.0"
|
|
4
4
|
description = "The missing CLI for Bear notes — read, search, export, and manage your notes from the terminal"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
keywords = ["bear", "notes", "cli", "markdown", "macos"]
|
|
@@ -18,8 +18,7 @@ license = "MIT"
|
|
|
18
18
|
license-files = ["LICENSE"]
|
|
19
19
|
requires-python = ">=3.13"
|
|
20
20
|
dependencies = [
|
|
21
|
-
"
|
|
22
|
-
"rapidfuzz>=3.14.5",
|
|
21
|
+
"bearkit==1.6.0",
|
|
23
22
|
"textual>=8.2.8",
|
|
24
23
|
"typer>=0.27.0",
|
|
25
24
|
]
|
|
@@ -32,6 +31,12 @@ Documentation = "https://github.com/michel-tricot/bearcli/blob/main/docs/IMPLEME
|
|
|
32
31
|
[project.scripts]
|
|
33
32
|
bearcli = "bearcli.cli:app"
|
|
34
33
|
|
|
34
|
+
[tool.uv.workspace]
|
|
35
|
+
members = ["packages/bearkit"]
|
|
36
|
+
|
|
37
|
+
[tool.uv.sources]
|
|
38
|
+
bearkit = { workspace = true }
|
|
39
|
+
|
|
35
40
|
[build-system]
|
|
36
41
|
requires = ["uv_build>=0.8.22,<0.9.0"]
|
|
37
42
|
build-backend = "uv_build"
|
|
@@ -48,3 +53,6 @@ line-length = 120
|
|
|
48
53
|
|
|
49
54
|
[tool.ruff.lint]
|
|
50
55
|
select = ["E", "F", "W", "I", "UP", "B"]
|
|
56
|
+
|
|
57
|
+
[tool.ruff.lint.isort]
|
|
58
|
+
known-first-party = ["bearcli", "bearkit"]
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import sys
|
|
6
|
-
from collections.abc import Callable
|
|
7
6
|
from datetime import datetime
|
|
8
7
|
from enum import StrEnum
|
|
9
8
|
from pathlib import Path
|
|
@@ -12,8 +11,8 @@ from typing import Annotated
|
|
|
12
11
|
import typer
|
|
13
12
|
from rich.console import Console
|
|
14
13
|
|
|
15
|
-
from
|
|
16
|
-
from
|
|
14
|
+
from bearkit import Bear
|
|
15
|
+
from bearkit.db import AmbiguousNoteId, Note, NoteFilter
|
|
17
16
|
|
|
18
17
|
app = typer.Typer(help="Read notes from the Bear note app.", no_args_is_help=True, add_completion=False)
|
|
19
18
|
|
|
@@ -34,15 +33,11 @@ class OutputFormat(StrEnum):
|
|
|
34
33
|
text = "text"
|
|
35
34
|
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
pinned = "pinned"
|
|
39
|
-
encrypted = "encrypted"
|
|
40
|
-
trashed = "trashed"
|
|
41
|
-
archived = "archived"
|
|
36
|
+
OnlyFilter = NoteFilter # typer choices come straight from the library enum
|
|
42
37
|
|
|
43
38
|
|
|
44
39
|
def _note_to_dict(note: Note, with_text: bool = False) -> dict:
|
|
45
|
-
data =
|
|
40
|
+
data = note.to_dict()
|
|
46
41
|
if with_text:
|
|
47
42
|
data["text"] = note.text
|
|
48
43
|
data["attachments"] = [
|
|
@@ -63,9 +58,9 @@ DbPathOption = Annotated[
|
|
|
63
58
|
]
|
|
64
59
|
|
|
65
60
|
|
|
66
|
-
def
|
|
61
|
+
def _open_bear(path: Path) -> Bear:
|
|
67
62
|
try:
|
|
68
|
-
return
|
|
63
|
+
return Bear(path)
|
|
69
64
|
except FileNotFoundError as exc:
|
|
70
65
|
console.print(f"[red]Error:[/red] {exc}")
|
|
71
66
|
raise typer.Exit(1) from None
|
|
@@ -90,9 +85,9 @@ def _text_or_stdin(text: str | None) -> str | None:
|
|
|
90
85
|
return text
|
|
91
86
|
|
|
92
87
|
|
|
93
|
-
def _require_note(
|
|
88
|
+
def _require_note(bear: Bear, note_id: str) -> Note:
|
|
94
89
|
try:
|
|
95
|
-
note =
|
|
90
|
+
note = bear.get_note(note_id)
|
|
96
91
|
except AmbiguousNoteId as exc:
|
|
97
92
|
console.print(f"[red]Error:[/red] note id prefix {exc.prefix!r} matches several notes:")
|
|
98
93
|
for full_id, title in exc.matches:
|
|
@@ -102,11 +97,3 @@ def _require_note(db: BearDB, note_id: str) -> Note:
|
|
|
102
97
|
console.print(f"[red]Error:[/red] no note with id {note_id!r}")
|
|
103
98
|
raise typer.Exit(1)
|
|
104
99
|
return note
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def _verify(ok: Callable[[], bool], success: str, failure: str) -> None:
|
|
108
|
-
if actions.wait_for(ok):
|
|
109
|
-
console.print(success)
|
|
110
|
-
else:
|
|
111
|
-
console.print(f"[red]Error:[/red] {failure}")
|
|
112
|
-
raise typer.Exit(1)
|
|
@@ -12,14 +12,14 @@ from rich.table import Table
|
|
|
12
12
|
|
|
13
13
|
from bearcli.cli.common import (
|
|
14
14
|
DbPathOption,
|
|
15
|
-
|
|
15
|
+
_open_bear,
|
|
16
16
|
app,
|
|
17
17
|
console,
|
|
18
18
|
)
|
|
19
|
-
from bearcli.db import DEFAULT_DB_PATH
|
|
20
19
|
from bearcli.export import export_notes
|
|
21
20
|
from bearcli.gitsync import GitError, export_and_push
|
|
22
|
-
from
|
|
21
|
+
from bearkit.db import DEFAULT_DB_PATH
|
|
22
|
+
from bearkit.secrets import ScanReport, SecretFinding
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def _report_secrets(findings: list[SecretFinding]) -> None:
|
|
@@ -76,30 +76,29 @@ def export(
|
|
|
76
76
|
if allow_secrets and redact_secrets:
|
|
77
77
|
console.print("[red]Error:[/red] --allow-secrets and --redact-secrets are mutually exclusive")
|
|
78
78
|
raise typer.Exit(2)
|
|
79
|
-
|
|
79
|
+
bear = _open_bear(db_path)
|
|
80
80
|
try:
|
|
81
|
-
redactions:
|
|
81
|
+
redactions: ScanReport | None = None
|
|
82
82
|
if not allow_secrets:
|
|
83
83
|
with console.status("Scanning notes for secrets…", spinner="dots"):
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
_report_secrets(findings)
|
|
84
|
+
report = bear.scan_secrets()
|
|
85
|
+
if report and not redact_secrets:
|
|
86
|
+
_report_secrets(report.findings)
|
|
88
87
|
raise typer.Exit(1)
|
|
89
|
-
if
|
|
90
|
-
redactions =
|
|
88
|
+
if report:
|
|
89
|
+
redactions = report
|
|
91
90
|
with console.status("Exporting…", spinner="dots") as status:
|
|
92
91
|
update = lambda msg: status.update(rich_escape(msg)) # noqa: E731
|
|
93
92
|
if push:
|
|
94
93
|
try:
|
|
95
|
-
result, outcome = export_and_push(db, dest, sync=sync, progress=update, redactions=redactions)
|
|
94
|
+
result, outcome = export_and_push(bear.db, dest, sync=sync, progress=update, redactions=redactions)
|
|
96
95
|
except GitError as exc:
|
|
97
96
|
console.print(f"[red]Error:[/red] {exc}")
|
|
98
97
|
raise typer.Exit(1) from None
|
|
99
98
|
else:
|
|
100
|
-
result = export_notes(db, dest, sync=sync, progress=update, redactions=redactions)
|
|
99
|
+
result = export_notes(bear.db, dest, sync=sync, progress=update, redactions=redactions)
|
|
101
100
|
finally:
|
|
102
|
-
|
|
101
|
+
bear.close()
|
|
103
102
|
|
|
104
103
|
parts = [f"{result.written} written"]
|
|
105
104
|
if push:
|
|
@@ -113,6 +112,5 @@ def export(
|
|
|
113
112
|
if result.index_updated:
|
|
114
113
|
parts.append("index updated")
|
|
115
114
|
if redactions:
|
|
116
|
-
|
|
117
|
-
parts.append(f"{secrets_count} secret(s) redacted in {len(redactions)} note(s)")
|
|
115
|
+
parts.append(f"{len(redactions)} secret(s) redacted in {redactions.notes_affected()} note(s)")
|
|
118
116
|
console.print(f"Exported to {dest}: " + ", ".join(parts))
|
|
@@ -12,11 +12,11 @@ from rich.table import Table
|
|
|
12
12
|
from bearcli.cli.common import (
|
|
13
13
|
DbPathOption,
|
|
14
14
|
OutputFormat,
|
|
15
|
-
|
|
15
|
+
_open_bear,
|
|
16
16
|
app,
|
|
17
17
|
console,
|
|
18
18
|
)
|
|
19
|
-
from
|
|
19
|
+
from bearkit.db import DEFAULT_DB_PATH
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
@app.command()
|
|
@@ -28,13 +28,13 @@ def stats(
|
|
|
28
28
|
db_path: DbPathOption = DEFAULT_DB_PATH,
|
|
29
29
|
) -> None:
|
|
30
30
|
"""Show statistics about the note library."""
|
|
31
|
-
|
|
31
|
+
bear = _open_bear(db_path)
|
|
32
32
|
try:
|
|
33
|
-
notes =
|
|
34
|
-
tag_counts =
|
|
35
|
-
attachment_count, attachment_bytes =
|
|
33
|
+
notes = bear.list_notes(limit=None, include_trashed=True, include_archived=True)
|
|
34
|
+
tag_counts = bear.list_tags()
|
|
35
|
+
attachment_count, attachment_bytes = bear.attachment_stats()
|
|
36
36
|
finally:
|
|
37
|
-
|
|
37
|
+
bear.close()
|
|
38
38
|
|
|
39
39
|
active = [n for n in notes if not n.trashed and not n.archived]
|
|
40
40
|
by_year: dict[str, int] = {}
|
|
@@ -101,5 +101,5 @@ def ui(
|
|
|
101
101
|
"""Bear in the terminal: search, edit, create, tag, and organize notes."""
|
|
102
102
|
from bearcli.tui import run_ui
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
_open_bear(db_path).close() # fail fast with a clear message if the db is missing
|
|
105
105
|
run_ui(fuzzy=fuzzy, db_path=db_path, tag_filter=tag_filter)
|