bearcli 1.2.1__tar.gz → 1.3.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.2.1 → bearcli-1.3.0}/PKG-INFO +23 -10
- {bearcli-1.2.1 → bearcli-1.3.0}/README.md +21 -9
- {bearcli-1.2.1 → bearcli-1.3.0}/pyproject.toml +3 -1
- {bearcli-1.2.1 → bearcli-1.3.0}/src/bearcli/cli.py +90 -0
- {bearcli-1.2.1 → bearcli-1.3.0}/src/bearcli/db.py +12 -0
- bearcli-1.3.0/src/bearcli/tui.py +83 -0
- {bearcli-1.2.1 → bearcli-1.3.0}/LICENSE +0 -0
- {bearcli-1.2.1 → bearcli-1.3.0}/src/bearcli/__init__.py +0 -0
- {bearcli-1.2.1 → bearcli-1.3.0}/src/bearcli/actions.py +0 -0
- {bearcli-1.2.1 → bearcli-1.3.0}/src/bearcli/export.py +0 -0
- {bearcli-1.2.1 → bearcli-1.3.0}/src/bearcli/gitsync.py +0 -0
- {bearcli-1.2.1 → bearcli-1.3.0}/src/bearcli/markdown.py +0 -0
- {bearcli-1.2.1 → bearcli-1.3.0}/src/bearcli/search.py +0 -0
- {bearcli-1.2.1 → bearcli-1.3.0}/src/bearcli/secrets.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bearcli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -14,6 +14,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
14
14
|
Classifier: Topic :: Utilities
|
|
15
15
|
Requires-Dist: detect-secrets>=1.5.0
|
|
16
16
|
Requires-Dist: rapidfuzz>=3.14.5
|
|
17
|
+
Requires-Dist: textual>=8.2.8
|
|
17
18
|
Requires-Dist: typer>=0.27.0
|
|
18
19
|
Requires-Python: >=3.13
|
|
19
20
|
Project-URL: Documentation, https://github.com/michel-tricot/bearcli/blob/main/docs/IMPLEMENTATION.md
|
|
@@ -23,9 +24,9 @@ Description-Content-Type: text/markdown
|
|
|
23
24
|
|
|
24
25
|
<div align="center">
|
|
25
26
|
|
|
26
|
-
# 🐻 bearcli
|
|
27
|
+
# 🐻 `bearcli`
|
|
27
28
|
|
|
28
|
-
**The missing CLI for [Bear](https://bear.app) notes**
|
|
29
|
+
**The missing CLI for [Bear](https://bear.app) notes** - read, search, export, and
|
|
29
30
|
manage your notes from the terminal.
|
|
30
31
|
|
|
31
32
|
[](https://github.com/michel-tricot/bearcli/actions/workflows/ci.yml)
|
|
@@ -79,6 +80,8 @@ bearcli get C44D09DC-... --meta # with YAML-style frontmatter
|
|
|
79
80
|
bearcli get C44D09DC-... -r # rewrite attachment refs to absolute paths
|
|
80
81
|
bearcli get C44D09DC-... --redact-secrets # secrets replaced by placeholders
|
|
81
82
|
bearcli open C44D09DC-... # open in the Bear app
|
|
83
|
+
bearcli browse # interactive: type to filter, Enter opens
|
|
84
|
+
bearcli stats # counts, words, top tags, notes per year
|
|
82
85
|
```
|
|
83
86
|
|
|
84
87
|
### Search
|
|
@@ -90,7 +93,7 @@ bearcli search "quarterly planing" --fuzzy # typo-tolerant, ranked by score
|
|
|
90
93
|
|
|
91
94
|
### Write
|
|
92
95
|
|
|
93
|
-
Writes go through Bear's x-callback-url API
|
|
96
|
+
Writes go through Bear's x-callback-url API - the database itself is never
|
|
94
97
|
written. These launch the Bear app if needed.
|
|
95
98
|
|
|
96
99
|
```sh
|
|
@@ -115,19 +118,19 @@ bearcli tag delete old-name # across all notes (asks first)
|
|
|
115
118
|
|
|
116
119
|
### Export
|
|
117
120
|
|
|
118
|
-
Every note becomes a self-contained directory
|
|
119
|
-
attachments
|
|
121
|
+
Every note becomes a self-contained directory - `<slug>/README.md` plus its
|
|
122
|
+
attachments - with a generated index, so GitHub renders the whole export as a
|
|
120
123
|
browsable tree.
|
|
121
124
|
|
|
122
125
|
Before anything is written, the notes are scanned for potential secrets
|
|
123
126
|
(token formats, key blocks, credential assignments, high-entropy strings);
|
|
124
127
|
findings block the export with a list of the affected notes. Override with
|
|
125
128
|
`--allow-secrets` (export as-is) or `--redact-secrets` (replace each secret
|
|
126
|
-
with a `[redacted: <rule>]` placeholder
|
|
129
|
+
with a `[redacted: <rule>]` placeholder - notes in Bear are untouched).
|
|
127
130
|
|
|
128
|
-
> **⚠️ Warning**
|
|
131
|
+
> **⚠️ Warning** - detection and redaction are best-effort: a secret that
|
|
129
132
|
> reads like ordinary text (a password written as prose, an account number)
|
|
130
|
-
> will not be caught. Ideally, don't keep secrets in notes at all
|
|
133
|
+
> will not be caught. Ideally, don't keep secrets in notes at all - use a
|
|
131
134
|
> password manager, or at least Bear's encrypted notes, which never leave
|
|
132
135
|
> the app.
|
|
133
136
|
|
|
@@ -136,7 +139,7 @@ bearcli export ~/bear-backup
|
|
|
136
139
|
bearcli export ~/bear-backup --sync # only rewrite notes that changed
|
|
137
140
|
bearcli export ~/bear-backup --redact-secrets # secrets become [redacted: <rule>]
|
|
138
141
|
|
|
139
|
-
# Mirror to a git repository (clone it first; use a *private* repo
|
|
142
|
+
# Mirror to a git repository (clone it first; use a *private* repo - these are
|
|
140
143
|
# your notes). Bear is the source of truth: remote or manual edits are kept in
|
|
141
144
|
# git history but overwritten in HEAD. Never force-pushes, never gets stuck.
|
|
142
145
|
git clone git@github.com:you/bear-notes.git ~/bear-notes
|
|
@@ -158,6 +161,16 @@ defaults to Bear's standard location and can be overridden with `--db` or the
|
|
|
158
161
|
`BEAR_DB_PATH` environment variable. Encrypted notes are listed but their
|
|
159
162
|
content cannot be read.
|
|
160
163
|
|
|
164
|
+
## Agent skill
|
|
165
|
+
|
|
166
|
+
An [Agent Skill](https://docs.claude.com/en/docs/agents-and-tools/agent-skills)
|
|
167
|
+
teaching AI agents (Claude Code, etc.) how to use `bearcli` ships in
|
|
168
|
+
[`skills/bear-notes`](skills/bear-notes/SKILL.md):
|
|
169
|
+
|
|
170
|
+
```sh
|
|
171
|
+
cp -r skills/bear-notes ~/.claude/skills/ # or a project's .claude/skills/
|
|
172
|
+
```
|
|
173
|
+
|
|
161
174
|
## Development
|
|
162
175
|
|
|
163
176
|
```sh
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
# 🐻 bearcli
|
|
3
|
+
# 🐻 `bearcli`
|
|
4
4
|
|
|
5
|
-
**The missing CLI for [Bear](https://bear.app) notes**
|
|
5
|
+
**The missing CLI for [Bear](https://bear.app) notes** - read, search, export, and
|
|
6
6
|
manage your notes from the terminal.
|
|
7
7
|
|
|
8
8
|
[](https://github.com/michel-tricot/bearcli/actions/workflows/ci.yml)
|
|
@@ -56,6 +56,8 @@ bearcli get C44D09DC-... --meta # with YAML-style frontmatter
|
|
|
56
56
|
bearcli get C44D09DC-... -r # rewrite attachment refs to absolute paths
|
|
57
57
|
bearcli get C44D09DC-... --redact-secrets # secrets replaced by placeholders
|
|
58
58
|
bearcli open C44D09DC-... # open in the Bear app
|
|
59
|
+
bearcli browse # interactive: type to filter, Enter opens
|
|
60
|
+
bearcli stats # counts, words, top tags, notes per year
|
|
59
61
|
```
|
|
60
62
|
|
|
61
63
|
### Search
|
|
@@ -67,7 +69,7 @@ bearcli search "quarterly planing" --fuzzy # typo-tolerant, ranked by score
|
|
|
67
69
|
|
|
68
70
|
### Write
|
|
69
71
|
|
|
70
|
-
Writes go through Bear's x-callback-url API
|
|
72
|
+
Writes go through Bear's x-callback-url API - the database itself is never
|
|
71
73
|
written. These launch the Bear app if needed.
|
|
72
74
|
|
|
73
75
|
```sh
|
|
@@ -92,19 +94,19 @@ bearcli tag delete old-name # across all notes (asks first)
|
|
|
92
94
|
|
|
93
95
|
### Export
|
|
94
96
|
|
|
95
|
-
Every note becomes a self-contained directory
|
|
96
|
-
attachments
|
|
97
|
+
Every note becomes a self-contained directory - `<slug>/README.md` plus its
|
|
98
|
+
attachments - with a generated index, so GitHub renders the whole export as a
|
|
97
99
|
browsable tree.
|
|
98
100
|
|
|
99
101
|
Before anything is written, the notes are scanned for potential secrets
|
|
100
102
|
(token formats, key blocks, credential assignments, high-entropy strings);
|
|
101
103
|
findings block the export with a list of the affected notes. Override with
|
|
102
104
|
`--allow-secrets` (export as-is) or `--redact-secrets` (replace each secret
|
|
103
|
-
with a `[redacted: <rule>]` placeholder
|
|
105
|
+
with a `[redacted: <rule>]` placeholder - notes in Bear are untouched).
|
|
104
106
|
|
|
105
|
-
> **⚠️ Warning**
|
|
107
|
+
> **⚠️ Warning** - detection and redaction are best-effort: a secret that
|
|
106
108
|
> reads like ordinary text (a password written as prose, an account number)
|
|
107
|
-
> will not be caught. Ideally, don't keep secrets in notes at all
|
|
109
|
+
> will not be caught. Ideally, don't keep secrets in notes at all - use a
|
|
108
110
|
> password manager, or at least Bear's encrypted notes, which never leave
|
|
109
111
|
> the app.
|
|
110
112
|
|
|
@@ -113,7 +115,7 @@ bearcli export ~/bear-backup
|
|
|
113
115
|
bearcli export ~/bear-backup --sync # only rewrite notes that changed
|
|
114
116
|
bearcli export ~/bear-backup --redact-secrets # secrets become [redacted: <rule>]
|
|
115
117
|
|
|
116
|
-
# Mirror to a git repository (clone it first; use a *private* repo
|
|
118
|
+
# Mirror to a git repository (clone it first; use a *private* repo - these are
|
|
117
119
|
# your notes). Bear is the source of truth: remote or manual edits are kept in
|
|
118
120
|
# git history but overwritten in HEAD. Never force-pushes, never gets stuck.
|
|
119
121
|
git clone git@github.com:you/bear-notes.git ~/bear-notes
|
|
@@ -135,6 +137,16 @@ defaults to Bear's standard location and can be overridden with `--db` or the
|
|
|
135
137
|
`BEAR_DB_PATH` environment variable. Encrypted notes are listed but their
|
|
136
138
|
content cannot be read.
|
|
137
139
|
|
|
140
|
+
## Agent skill
|
|
141
|
+
|
|
142
|
+
An [Agent Skill](https://docs.claude.com/en/docs/agents-and-tools/agent-skills)
|
|
143
|
+
teaching AI agents (Claude Code, etc.) how to use `bearcli` ships in
|
|
144
|
+
[`skills/bear-notes`](skills/bear-notes/SKILL.md):
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
cp -r skills/bear-notes ~/.claude/skills/ # or a project's .claude/skills/
|
|
148
|
+
```
|
|
149
|
+
|
|
138
150
|
## Development
|
|
139
151
|
|
|
140
152
|
```sh
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "bearcli"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.3.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"]
|
|
@@ -20,6 +20,7 @@ requires-python = ">=3.13"
|
|
|
20
20
|
dependencies = [
|
|
21
21
|
"detect-secrets>=1.5.0",
|
|
22
22
|
"rapidfuzz>=3.14.5",
|
|
23
|
+
"textual>=8.2.8",
|
|
23
24
|
"typer>=0.27.0",
|
|
24
25
|
]
|
|
25
26
|
|
|
@@ -37,6 +38,7 @@ build-backend = "uv_build"
|
|
|
37
38
|
|
|
38
39
|
[dependency-groups]
|
|
39
40
|
dev = [
|
|
41
|
+
"pytest>=9.1.1",
|
|
40
42
|
"ruff>=0.16.0",
|
|
41
43
|
"ty>=0.0.64",
|
|
42
44
|
]
|
|
@@ -841,3 +841,93 @@ _alias("search", "note search", search)
|
|
|
841
841
|
_alias("get", "note get", get)
|
|
842
842
|
_alias("open", "note open", open_note)
|
|
843
843
|
_alias("create", "note create", create)
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
@app.command()
|
|
847
|
+
def stats(
|
|
848
|
+
fmt: Annotated[
|
|
849
|
+
OutputFormat,
|
|
850
|
+
typer.Option("--format", "-f", help="Output format: table or json."),
|
|
851
|
+
] = OutputFormat.table,
|
|
852
|
+
db_path: DbPathOption = DEFAULT_DB_PATH,
|
|
853
|
+
) -> None:
|
|
854
|
+
"""Show statistics about the note library."""
|
|
855
|
+
db = _open_db(db_path)
|
|
856
|
+
try:
|
|
857
|
+
notes = db.list_notes(limit=None, include_trashed=True, include_archived=True, with_text=True)
|
|
858
|
+
tag_counts = db.list_tags()
|
|
859
|
+
attachment_count, attachment_bytes = db.attachment_stats()
|
|
860
|
+
finally:
|
|
861
|
+
db.close()
|
|
862
|
+
|
|
863
|
+
active = [n for n in notes if not n.trashed and not n.archived]
|
|
864
|
+
by_year: dict[str, int] = {}
|
|
865
|
+
for n in notes:
|
|
866
|
+
if not n.trashed and n.created:
|
|
867
|
+
year = str(n.created.year)
|
|
868
|
+
by_year[year] = by_year.get(year, 0) + 1
|
|
869
|
+
|
|
870
|
+
data = {
|
|
871
|
+
"notes": len(notes),
|
|
872
|
+
"active": len(active),
|
|
873
|
+
"archived": sum(1 for n in notes if n.archived and not n.trashed),
|
|
874
|
+
"trashed": sum(1 for n in notes if n.trashed),
|
|
875
|
+
"pinned": sum(1 for n in notes if n.pinned and not n.trashed),
|
|
876
|
+
"encrypted": sum(1 for n in notes if n.encrypted and not n.trashed),
|
|
877
|
+
"words": sum(len((n.text or "").split()) for n in notes if not n.trashed),
|
|
878
|
+
"tags": len(tag_counts),
|
|
879
|
+
"attachments": attachment_count,
|
|
880
|
+
"attachment_bytes": attachment_bytes,
|
|
881
|
+
"notes_by_year": dict(sorted(by_year.items())),
|
|
882
|
+
"top_tags": [{"tag": t, "notes": c} for t, c in sorted(tag_counts, key=lambda tc: -tc[1])[:10]],
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
if fmt is OutputFormat.json:
|
|
886
|
+
print(json.dumps(data, indent=2, ensure_ascii=False))
|
|
887
|
+
return
|
|
888
|
+
|
|
889
|
+
table = Table(box=box.ROUNDED, show_header=False)
|
|
890
|
+
table.add_column(style="bold")
|
|
891
|
+
table.add_column(justify="right")
|
|
892
|
+
table.add_row("Notes", str(data["notes"]))
|
|
893
|
+
table.add_row(" active", str(data["active"]))
|
|
894
|
+
table.add_row(" archived", str(data["archived"]))
|
|
895
|
+
table.add_row(" trashed", str(data["trashed"]))
|
|
896
|
+
table.add_row(" pinned", str(data["pinned"]))
|
|
897
|
+
table.add_row(" encrypted", str(data["encrypted"]))
|
|
898
|
+
table.add_row("Words", f"{data['words']:,}")
|
|
899
|
+
table.add_row("Tags", str(data["tags"]))
|
|
900
|
+
table.add_row("Attachments", f"{attachment_count} ({attachment_bytes / 1_000_000:.1f} MB)")
|
|
901
|
+
console.print(table)
|
|
902
|
+
|
|
903
|
+
if data["top_tags"]:
|
|
904
|
+
tag_table = Table(box=box.ROUNDED, header_style="bold")
|
|
905
|
+
tag_table.add_column("Top tags", style="cyan")
|
|
906
|
+
tag_table.add_column("Notes", justify="right")
|
|
907
|
+
for entry in data["top_tags"]:
|
|
908
|
+
tag_table.add_row(str(entry["tag"]), str(entry["notes"]))
|
|
909
|
+
console.print(tag_table)
|
|
910
|
+
|
|
911
|
+
year_table = Table(box=box.ROUNDED, header_style="bold")
|
|
912
|
+
year_table.add_column("Created", style="cyan")
|
|
913
|
+
year_table.add_column("Notes", justify="right")
|
|
914
|
+
for year, count in data["notes_by_year"].items():
|
|
915
|
+
year_table.add_row(year, str(count))
|
|
916
|
+
console.print(year_table)
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
@app.command()
|
|
920
|
+
def browse(
|
|
921
|
+
fuzzy: Annotated[bool, typer.Option("--fuzzy", help="Typo-tolerant ranked filtering.")] = False,
|
|
922
|
+
tag_filter: Annotated[str | None, typer.Option("--tag", "-t", help="Restrict to notes with this tag.")] = None,
|
|
923
|
+
db_path: DbPathOption = DEFAULT_DB_PATH,
|
|
924
|
+
) -> None:
|
|
925
|
+
"""Browse notes interactively: type to filter, Enter opens the note in Bear."""
|
|
926
|
+
from bearcli.tui import browse as run_browser
|
|
927
|
+
|
|
928
|
+
db = _open_db(db_path)
|
|
929
|
+
try:
|
|
930
|
+
notes = db.list_notes(limit=None, tag=tag_filter, with_text=True)
|
|
931
|
+
finally:
|
|
932
|
+
db.close()
|
|
933
|
+
run_browser(notes, fuzzy=fuzzy)
|
|
@@ -144,6 +144,18 @@ class BearDB:
|
|
|
144
144
|
).fetchall()
|
|
145
145
|
return [(row[0], row[1]) for row in rows]
|
|
146
146
|
|
|
147
|
+
def attachment_stats(self) -> tuple[int, int]:
|
|
148
|
+
"""(count, total bytes) of attachments on non-deleted notes."""
|
|
149
|
+
row = self.conn.execute(
|
|
150
|
+
"""
|
|
151
|
+
SELECT COUNT(*), COALESCE(SUM(f.ZFILESIZE), 0)
|
|
152
|
+
FROM ZSFNOTEFILE f
|
|
153
|
+
JOIN ZSFNOTE n ON n.Z_PK = f.ZNOTE
|
|
154
|
+
WHERE f.ZPERMANENTLYDELETED = 0 AND n.ZPERMANENTLYDELETED = 0 AND n.ZTRASHED = 0
|
|
155
|
+
"""
|
|
156
|
+
).fetchone()
|
|
157
|
+
return row[0], row[1]
|
|
158
|
+
|
|
147
159
|
def _attachments_for_note(self, note_pk: int) -> list[Attachment]:
|
|
148
160
|
rows = self.conn.execute(
|
|
149
161
|
"""
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Interactive note browser (Textual)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from textual.app import App, ComposeResult
|
|
6
|
+
from textual.containers import Horizontal
|
|
7
|
+
from textual.widgets import Input, OptionList, Static
|
|
8
|
+
from textual.widgets.option_list import Option
|
|
9
|
+
|
|
10
|
+
from bearcli import actions
|
|
11
|
+
from bearcli.db import Note
|
|
12
|
+
from bearcli.search import naive_search, search_notes
|
|
13
|
+
|
|
14
|
+
MAX_RESULTS = 50
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BrowseApp(App):
|
|
18
|
+
"""Type to filter, arrows to move, Enter to open in Bear, Esc to quit."""
|
|
19
|
+
|
|
20
|
+
CSS = """
|
|
21
|
+
#query { dock: top; }
|
|
22
|
+
#status { dock: bottom; height: 1; color: $text-muted; padding: 0 1; }
|
|
23
|
+
#results { height: 1fr; }
|
|
24
|
+
#preview { width: 45%; border-left: solid $surface; padding: 0 1; color: $text-muted; }
|
|
25
|
+
"""
|
|
26
|
+
BINDINGS = [("escape", "quit", "Quit")]
|
|
27
|
+
|
|
28
|
+
def __init__(self, notes: list[Note], fuzzy: bool = False):
|
|
29
|
+
super().__init__()
|
|
30
|
+
self.notes = notes
|
|
31
|
+
self.fuzzy = fuzzy
|
|
32
|
+
self.shown: list[Note] = []
|
|
33
|
+
|
|
34
|
+
def compose(self) -> ComposeResult:
|
|
35
|
+
yield Input(placeholder="Search notes…", id="query")
|
|
36
|
+
with Horizontal():
|
|
37
|
+
yield OptionList(id="results")
|
|
38
|
+
yield Static(id="preview")
|
|
39
|
+
yield Static(id="status")
|
|
40
|
+
|
|
41
|
+
def on_mount(self) -> None:
|
|
42
|
+
self._refresh("")
|
|
43
|
+
|
|
44
|
+
def on_input_changed(self, event: Input.Changed) -> None:
|
|
45
|
+
self._refresh(event.value)
|
|
46
|
+
|
|
47
|
+
def _refresh(self, query: str) -> None:
|
|
48
|
+
if not query.strip():
|
|
49
|
+
self.shown = self.notes[:MAX_RESULTS]
|
|
50
|
+
elif self.fuzzy:
|
|
51
|
+
self.shown = [r.note for r in search_notes(self.notes, query)[:MAX_RESULTS]]
|
|
52
|
+
else:
|
|
53
|
+
self.shown = [r.note for r in naive_search(self.notes, query)[:MAX_RESULTS]]
|
|
54
|
+
|
|
55
|
+
results = self.query_one("#results", OptionList)
|
|
56
|
+
results.clear_options()
|
|
57
|
+
for note in self.shown:
|
|
58
|
+
date = note.modified.strftime("%Y-%m-%d") if note.modified else " "
|
|
59
|
+
tags = f" #{' #'.join(note.tags)}" if note.tags else ""
|
|
60
|
+
results.add_option(Option(f"{date} {note.title}{tags}", id=note.id))
|
|
61
|
+
if self.shown:
|
|
62
|
+
results.highlighted = 0
|
|
63
|
+
self.query_one("#status", Static).update(
|
|
64
|
+
f"{len(self.shown)} shown / {len(self.notes)} notes · Enter: open in Bear · Esc: quit"
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def on_option_list_option_highlighted(self, event: OptionList.OptionHighlighted) -> None:
|
|
68
|
+
note = next((n for n in self.shown if n.id == event.option_id), None)
|
|
69
|
+
preview = "\n".join((note.text or "").splitlines()[:40]) if note else ""
|
|
70
|
+
self.query_one("#preview", Static).update(preview)
|
|
71
|
+
|
|
72
|
+
def on_option_list_option_selected(self, event: OptionList.OptionSelected) -> None:
|
|
73
|
+
if event.option_id:
|
|
74
|
+
actions.open_note(event.option_id)
|
|
75
|
+
|
|
76
|
+
def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
77
|
+
results = self.query_one("#results", OptionList)
|
|
78
|
+
if self.shown and results.highlighted is not None:
|
|
79
|
+
actions.open_note(self.shown[results.highlighted].id)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def browse(notes: list[Note], fuzzy: bool = False) -> None:
|
|
83
|
+
BrowseApp(notes, fuzzy=fuzzy).run()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|