odcli 0.1.0__tar.gz → 0.1.3__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.
- odcli-0.1.3/PKG-INFO +197 -0
- odcli-0.1.3/README.md +176 -0
- {odcli-0.1.0 → odcli-0.1.3}/pyproject.toml +4 -1
- {odcli-0.1.0 → odcli-0.1.3}/src/obsidian_cli/__init__.py +1 -2
- {odcli-0.1.0 → odcli-0.1.3}/src/obsidian_cli/__main__.py +0 -1
- odcli-0.1.3/src/obsidian_cli/cli.py +162 -0
- odcli-0.1.3/src/obsidian_cli/commands.py +58 -0
- odcli-0.1.3/src/obsidian_cli/discovery.py +135 -0
- {odcli-0.1.0 → odcli-0.1.3}/src/obsidian_cli/vault.py +6 -2
- odcli-0.1.3/src/odcli.egg-info/PKG-INFO +197 -0
- {odcli-0.1.0 → odcli-0.1.3}/src/odcli.egg-info/SOURCES.txt +2 -0
- {odcli-0.1.0 → odcli-0.1.3}/src/odcli.egg-info/entry_points.txt +1 -0
- {odcli-0.1.0 → odcli-0.1.3}/tests/test_cli.py +42 -3
- odcli-0.1.0/PKG-INFO +0 -185
- odcli-0.1.0/README.md +0 -165
- odcli-0.1.0/src/obsidian_cli/cli.py +0 -187
- odcli-0.1.0/src/odcli.egg-info/PKG-INFO +0 -185
- {odcli-0.1.0 → odcli-0.1.3}/setup.cfg +0 -0
- {odcli-0.1.0 → odcli-0.1.3}/src/odcli.egg-info/dependency_links.txt +0 -0
- {odcli-0.1.0 → odcli-0.1.3}/src/odcli.egg-info/top_level.txt +0 -0
odcli-0.1.3/PKG-INFO
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: odcli
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary: A small CLI for reading and writing notes in a local Obsidian vault.
|
|
5
|
+
Author: Chang LeHung
|
|
6
|
+
Project-URL: Homepage, https://github.com/Chang-LeHung/obsidian-cli
|
|
7
|
+
Project-URL: Repository, https://github.com/Chang-LeHung/obsidian-cli
|
|
8
|
+
Project-URL: Issues, https://github.com/Chang-LeHung/obsidian-cli/issues
|
|
9
|
+
Keywords: obsidian,cli,markdown,notes,vault
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: MacOS
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Utilities
|
|
18
|
+
Classifier: Environment :: Console
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
# odcli
|
|
23
|
+
|
|
24
|
+
`odcli` is a local Python CLI for reading and writing notes in an Obsidian vault.
|
|
25
|
+
It works directly on Markdown files inside the vault, so it does not depend on private Obsidian APIs and remains portable and easy to extend.
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
- Validate whether a vault path is available
|
|
30
|
+
- List Markdown notes in the vault
|
|
31
|
+
- Read a specific note
|
|
32
|
+
- Read a specific line range from a note
|
|
33
|
+
- Overwrite a note or create it automatically
|
|
34
|
+
- Replace a specific line range in a note
|
|
35
|
+
- Append content to a note
|
|
36
|
+
- Full-text search across the vault
|
|
37
|
+
- Auto-discover the default vault from Obsidian config or common macOS and Windows locations
|
|
38
|
+
|
|
39
|
+
## Using uv
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
43
|
+
uv sync
|
|
44
|
+
uv run odcli --help
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Run tests:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
51
|
+
uv run python -m unittest discover -s tests
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Build distributions:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
58
|
+
uv build
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The published package name on PyPI is `odcli`.
|
|
62
|
+
After installation, both `odcli` and `obsidian-cli` are available as command names.
|
|
63
|
+
|
|
64
|
+
## Run Locally
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
68
|
+
./odcli --help
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The compatibility entry point is still available:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
75
|
+
./obsidian-cli --help
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
If you prefer module execution:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
PYTHONPATH=src python3 -m obsidian_cli --help
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Vault Resolution
|
|
85
|
+
|
|
86
|
+
Resolution priority:
|
|
87
|
+
|
|
88
|
+
1. `--vault /path/to/vault`
|
|
89
|
+
2. `OBSIDIAN_VAULT`
|
|
90
|
+
3. The most recently opened vault recorded by local Obsidian config
|
|
91
|
+
4. Common default directories
|
|
92
|
+
|
|
93
|
+
Built-in default locations:
|
|
94
|
+
|
|
95
|
+
- macOS: `~/Documents/Obsidian Vault`
|
|
96
|
+
- macOS: `~/Documents/Obsidian`
|
|
97
|
+
- macOS iCloud: `~/Library/Mobile Documents/iCloud~md~obsidian/Documents`
|
|
98
|
+
- Windows: `%USERPROFILE%\\Documents\\Obsidian Vault`
|
|
99
|
+
- Windows: `%USERPROFILE%\\Documents\\Obsidian`
|
|
100
|
+
|
|
101
|
+
Example:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
export OBSIDIAN_VAULT="/Users/your-name/Documents/MyVault"
|
|
105
|
+
./odcli check
|
|
106
|
+
./odcli list
|
|
107
|
+
./odcli read Inbox/today.md
|
|
108
|
+
./odcli read-lines Inbox/today.md 3 8
|
|
109
|
+
./odcli write Inbox/today.md --content "# Today"
|
|
110
|
+
./odcli write-lines Inbox/today.md 3 4 --content "- replaced\n- lines\n"
|
|
111
|
+
./odcli append Inbox/today.md --content "\n- new item"
|
|
112
|
+
./odcli search "project alpha"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Commands
|
|
116
|
+
|
|
117
|
+
### `check`
|
|
118
|
+
|
|
119
|
+
Validate that the vault exists and report whether `.obsidian` is present.
|
|
120
|
+
|
|
121
|
+
### `list`
|
|
122
|
+
|
|
123
|
+
List Markdown notes in the vault.
|
|
124
|
+
|
|
125
|
+
Optional arguments:
|
|
126
|
+
|
|
127
|
+
- `--limit N`
|
|
128
|
+
|
|
129
|
+
### `read`
|
|
130
|
+
|
|
131
|
+
Read a note.
|
|
132
|
+
|
|
133
|
+
Arguments:
|
|
134
|
+
|
|
135
|
+
- `note_path`: path relative to the vault root
|
|
136
|
+
|
|
137
|
+
### `write`
|
|
138
|
+
|
|
139
|
+
Overwrite a note. Parent directories are created automatically if needed.
|
|
140
|
+
|
|
141
|
+
Arguments:
|
|
142
|
+
|
|
143
|
+
- `note_path`
|
|
144
|
+
- `--content TEXT`
|
|
145
|
+
- `--stdin`
|
|
146
|
+
|
|
147
|
+
Optional arguments:
|
|
148
|
+
|
|
149
|
+
- `--create-only`
|
|
150
|
+
|
|
151
|
+
### `read-lines`
|
|
152
|
+
|
|
153
|
+
Read a line range. Line numbers are 1-based and inclusive.
|
|
154
|
+
|
|
155
|
+
Arguments:
|
|
156
|
+
|
|
157
|
+
- `note_path`
|
|
158
|
+
- `start_line`
|
|
159
|
+
- `end_line`
|
|
160
|
+
|
|
161
|
+
### `write-lines`
|
|
162
|
+
|
|
163
|
+
Replace a line range. Line numbers are 1-based and inclusive.
|
|
164
|
+
|
|
165
|
+
Arguments:
|
|
166
|
+
|
|
167
|
+
- `note_path`
|
|
168
|
+
- `start_line`
|
|
169
|
+
- `end_line`
|
|
170
|
+
- `--content TEXT`
|
|
171
|
+
- `--stdin`
|
|
172
|
+
|
|
173
|
+
### `append`
|
|
174
|
+
|
|
175
|
+
Append content to the end of a note.
|
|
176
|
+
|
|
177
|
+
Arguments:
|
|
178
|
+
|
|
179
|
+
- `note_path`
|
|
180
|
+
- `--content TEXT`
|
|
181
|
+
- `--stdin`
|
|
182
|
+
|
|
183
|
+
### `search`
|
|
184
|
+
|
|
185
|
+
Search across all Markdown notes in the vault.
|
|
186
|
+
|
|
187
|
+
Arguments:
|
|
188
|
+
|
|
189
|
+
- `query`
|
|
190
|
+
- `--case-sensitive`
|
|
191
|
+
|
|
192
|
+
## Testing
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
196
|
+
uv run python -m unittest discover -s tests
|
|
197
|
+
```
|
odcli-0.1.3/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# odcli
|
|
2
|
+
|
|
3
|
+
`odcli` is a local Python CLI for reading and writing notes in an Obsidian vault.
|
|
4
|
+
It works directly on Markdown files inside the vault, so it does not depend on private Obsidian APIs and remains portable and easy to extend.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- Validate whether a vault path is available
|
|
9
|
+
- List Markdown notes in the vault
|
|
10
|
+
- Read a specific note
|
|
11
|
+
- Read a specific line range from a note
|
|
12
|
+
- Overwrite a note or create it automatically
|
|
13
|
+
- Replace a specific line range in a note
|
|
14
|
+
- Append content to a note
|
|
15
|
+
- Full-text search across the vault
|
|
16
|
+
- Auto-discover the default vault from Obsidian config or common macOS and Windows locations
|
|
17
|
+
|
|
18
|
+
## Using uv
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
22
|
+
uv sync
|
|
23
|
+
uv run odcli --help
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Run tests:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
30
|
+
uv run python -m unittest discover -s tests
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Build distributions:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
37
|
+
uv build
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The published package name on PyPI is `odcli`.
|
|
41
|
+
After installation, both `odcli` and `obsidian-cli` are available as command names.
|
|
42
|
+
|
|
43
|
+
## Run Locally
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
47
|
+
./odcli --help
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The compatibility entry point is still available:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
54
|
+
./obsidian-cli --help
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
If you prefer module execution:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
PYTHONPATH=src python3 -m obsidian_cli --help
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Vault Resolution
|
|
64
|
+
|
|
65
|
+
Resolution priority:
|
|
66
|
+
|
|
67
|
+
1. `--vault /path/to/vault`
|
|
68
|
+
2. `OBSIDIAN_VAULT`
|
|
69
|
+
3. The most recently opened vault recorded by local Obsidian config
|
|
70
|
+
4. Common default directories
|
|
71
|
+
|
|
72
|
+
Built-in default locations:
|
|
73
|
+
|
|
74
|
+
- macOS: `~/Documents/Obsidian Vault`
|
|
75
|
+
- macOS: `~/Documents/Obsidian`
|
|
76
|
+
- macOS iCloud: `~/Library/Mobile Documents/iCloud~md~obsidian/Documents`
|
|
77
|
+
- Windows: `%USERPROFILE%\\Documents\\Obsidian Vault`
|
|
78
|
+
- Windows: `%USERPROFILE%\\Documents\\Obsidian`
|
|
79
|
+
|
|
80
|
+
Example:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
export OBSIDIAN_VAULT="/Users/your-name/Documents/MyVault"
|
|
84
|
+
./odcli check
|
|
85
|
+
./odcli list
|
|
86
|
+
./odcli read Inbox/today.md
|
|
87
|
+
./odcli read-lines Inbox/today.md 3 8
|
|
88
|
+
./odcli write Inbox/today.md --content "# Today"
|
|
89
|
+
./odcli write-lines Inbox/today.md 3 4 --content "- replaced\n- lines\n"
|
|
90
|
+
./odcli append Inbox/today.md --content "\n- new item"
|
|
91
|
+
./odcli search "project alpha"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Commands
|
|
95
|
+
|
|
96
|
+
### `check`
|
|
97
|
+
|
|
98
|
+
Validate that the vault exists and report whether `.obsidian` is present.
|
|
99
|
+
|
|
100
|
+
### `list`
|
|
101
|
+
|
|
102
|
+
List Markdown notes in the vault.
|
|
103
|
+
|
|
104
|
+
Optional arguments:
|
|
105
|
+
|
|
106
|
+
- `--limit N`
|
|
107
|
+
|
|
108
|
+
### `read`
|
|
109
|
+
|
|
110
|
+
Read a note.
|
|
111
|
+
|
|
112
|
+
Arguments:
|
|
113
|
+
|
|
114
|
+
- `note_path`: path relative to the vault root
|
|
115
|
+
|
|
116
|
+
### `write`
|
|
117
|
+
|
|
118
|
+
Overwrite a note. Parent directories are created automatically if needed.
|
|
119
|
+
|
|
120
|
+
Arguments:
|
|
121
|
+
|
|
122
|
+
- `note_path`
|
|
123
|
+
- `--content TEXT`
|
|
124
|
+
- `--stdin`
|
|
125
|
+
|
|
126
|
+
Optional arguments:
|
|
127
|
+
|
|
128
|
+
- `--create-only`
|
|
129
|
+
|
|
130
|
+
### `read-lines`
|
|
131
|
+
|
|
132
|
+
Read a line range. Line numbers are 1-based and inclusive.
|
|
133
|
+
|
|
134
|
+
Arguments:
|
|
135
|
+
|
|
136
|
+
- `note_path`
|
|
137
|
+
- `start_line`
|
|
138
|
+
- `end_line`
|
|
139
|
+
|
|
140
|
+
### `write-lines`
|
|
141
|
+
|
|
142
|
+
Replace a line range. Line numbers are 1-based and inclusive.
|
|
143
|
+
|
|
144
|
+
Arguments:
|
|
145
|
+
|
|
146
|
+
- `note_path`
|
|
147
|
+
- `start_line`
|
|
148
|
+
- `end_line`
|
|
149
|
+
- `--content TEXT`
|
|
150
|
+
- `--stdin`
|
|
151
|
+
|
|
152
|
+
### `append`
|
|
153
|
+
|
|
154
|
+
Append content to the end of a note.
|
|
155
|
+
|
|
156
|
+
Arguments:
|
|
157
|
+
|
|
158
|
+
- `note_path`
|
|
159
|
+
- `--content TEXT`
|
|
160
|
+
- `--stdin`
|
|
161
|
+
|
|
162
|
+
### `search`
|
|
163
|
+
|
|
164
|
+
Search across all Markdown notes in the vault.
|
|
165
|
+
|
|
166
|
+
Arguments:
|
|
167
|
+
|
|
168
|
+
- `query`
|
|
169
|
+
- `--case-sensitive`
|
|
170
|
+
|
|
171
|
+
## Testing
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
175
|
+
uv run python -m unittest discover -s tests
|
|
176
|
+
```
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "odcli"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.3"
|
|
8
8
|
description = "A small CLI for reading and writing notes in a local Obsidian vault."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -17,6 +17,7 @@ classifiers = [
|
|
|
17
17
|
"Development Status :: 3 - Alpha",
|
|
18
18
|
"Intended Audience :: Developers",
|
|
19
19
|
"Operating System :: MacOS",
|
|
20
|
+
"Operating System :: Microsoft :: Windows",
|
|
20
21
|
"Programming Language :: Python :: 3",
|
|
21
22
|
"Programming Language :: Python :: 3.11",
|
|
22
23
|
"Programming Language :: Python :: 3.12",
|
|
@@ -30,11 +31,13 @@ Repository = "https://github.com/Chang-LeHung/obsidian-cli"
|
|
|
30
31
|
Issues = "https://github.com/Chang-LeHung/obsidian-cli/issues"
|
|
31
32
|
|
|
32
33
|
[project.scripts]
|
|
34
|
+
odcli = "obsidian_cli.cli:main"
|
|
33
35
|
obsidian-cli = "obsidian_cli.cli:main"
|
|
34
36
|
|
|
35
37
|
[dependency-groups]
|
|
36
38
|
dev = [
|
|
37
39
|
"build>=1.2.2",
|
|
40
|
+
"ruff>=0.14.0",
|
|
38
41
|
"twine>=6.1.0",
|
|
39
42
|
]
|
|
40
43
|
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from obsidian_cli.commands import CommandRunner
|
|
7
|
+
from obsidian_cli.discovery import VaultLocator
|
|
8
|
+
from obsidian_cli.vault import ObsidianVault, VaultError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ObsidianCLI:
|
|
12
|
+
def __init__(self, vault_locator: VaultLocator | None = None) -> None:
|
|
13
|
+
self._vault_locator = vault_locator or VaultLocator()
|
|
14
|
+
self._parser = self._build_parser()
|
|
15
|
+
|
|
16
|
+
def run(self, argv: list[str] | None = None) -> int:
|
|
17
|
+
args = self._parser.parse_args(argv)
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
runner = CommandRunner(
|
|
21
|
+
ObsidianVault(self._vault_locator.resolve(args.vault))
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
if args.command == "check":
|
|
25
|
+
return runner.check()
|
|
26
|
+
if args.command == "list":
|
|
27
|
+
return runner.list_notes(args.limit)
|
|
28
|
+
if args.command == "read":
|
|
29
|
+
return runner.read_note(args.note_path)
|
|
30
|
+
if args.command == "read-lines":
|
|
31
|
+
return runner.read_note_lines(
|
|
32
|
+
args.note_path, args.start_line, args.end_line
|
|
33
|
+
)
|
|
34
|
+
if args.command == "write":
|
|
35
|
+
return runner.write_note(
|
|
36
|
+
args.note_path,
|
|
37
|
+
self._read_content_arg(args.content, args.stdin),
|
|
38
|
+
args.create_only,
|
|
39
|
+
)
|
|
40
|
+
if args.command == "append":
|
|
41
|
+
return runner.append_note(
|
|
42
|
+
args.note_path,
|
|
43
|
+
self._read_content_arg(args.content, args.stdin),
|
|
44
|
+
)
|
|
45
|
+
if args.command == "write-lines":
|
|
46
|
+
return runner.write_note_lines(
|
|
47
|
+
args.note_path,
|
|
48
|
+
args.start_line,
|
|
49
|
+
args.end_line,
|
|
50
|
+
self._read_content_arg(args.content, args.stdin),
|
|
51
|
+
)
|
|
52
|
+
if args.command == "search":
|
|
53
|
+
return runner.search(args.query, args.case_sensitive)
|
|
54
|
+
except VaultError as exc:
|
|
55
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
56
|
+
return 2
|
|
57
|
+
|
|
58
|
+
self._parser.error(f"unsupported command: {args.command}")
|
|
59
|
+
return 2
|
|
60
|
+
|
|
61
|
+
def _build_parser(self) -> argparse.ArgumentParser:
|
|
62
|
+
parser = argparse.ArgumentParser(
|
|
63
|
+
prog="obsidian-cli",
|
|
64
|
+
description="Read and write notes inside a local Obsidian vault.",
|
|
65
|
+
)
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
"--vault",
|
|
68
|
+
help="Path to the Obsidian vault. Falls back to OBSIDIAN_VAULT.",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
72
|
+
subparsers.add_parser("check", help="Validate the vault path.")
|
|
73
|
+
|
|
74
|
+
list_parser = subparsers.add_parser("list", help="List markdown notes.")
|
|
75
|
+
list_parser.add_argument(
|
|
76
|
+
"--limit", type=int, default=0, help="Maximum number of notes to show."
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
read_parser = subparsers.add_parser("read", help="Read a note.")
|
|
80
|
+
read_parser.add_argument(
|
|
81
|
+
"note_path", help="Path to the note relative to the vault root."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
read_lines_parser = subparsers.add_parser(
|
|
85
|
+
"read-lines", help="Read a line range from a note."
|
|
86
|
+
)
|
|
87
|
+
read_lines_parser.add_argument(
|
|
88
|
+
"note_path", help="Path to the note relative to the vault root."
|
|
89
|
+
)
|
|
90
|
+
read_lines_parser.add_argument(
|
|
91
|
+
"start_line", type=int, help="1-based start line, inclusive."
|
|
92
|
+
)
|
|
93
|
+
read_lines_parser.add_argument(
|
|
94
|
+
"end_line", type=int, help="1-based end line, inclusive."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
write_parser = subparsers.add_parser("write", help="Write a note.")
|
|
98
|
+
write_parser.add_argument(
|
|
99
|
+
"note_path", help="Path to the note relative to the vault root."
|
|
100
|
+
)
|
|
101
|
+
write_parser.add_argument("--content", help="Text content to write.")
|
|
102
|
+
write_parser.add_argument(
|
|
103
|
+
"--stdin", action="store_true", help="Read note content from stdin."
|
|
104
|
+
)
|
|
105
|
+
write_parser.add_argument(
|
|
106
|
+
"--create-only",
|
|
107
|
+
action="store_true",
|
|
108
|
+
help="Fail if the note already exists.",
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
append_parser = subparsers.add_parser("append", help="Append to a note.")
|
|
112
|
+
append_parser.add_argument(
|
|
113
|
+
"note_path", help="Path to the note relative to the vault root."
|
|
114
|
+
)
|
|
115
|
+
append_parser.add_argument("--content", help="Text content to append.")
|
|
116
|
+
append_parser.add_argument(
|
|
117
|
+
"--stdin", action="store_true", help="Read appended content from stdin."
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
write_lines_parser = subparsers.add_parser(
|
|
121
|
+
"write-lines",
|
|
122
|
+
help="Replace a line range inside a note.",
|
|
123
|
+
)
|
|
124
|
+
write_lines_parser.add_argument(
|
|
125
|
+
"note_path", help="Path to the note relative to the vault root."
|
|
126
|
+
)
|
|
127
|
+
write_lines_parser.add_argument(
|
|
128
|
+
"start_line", type=int, help="1-based start line, inclusive."
|
|
129
|
+
)
|
|
130
|
+
write_lines_parser.add_argument(
|
|
131
|
+
"end_line", type=int, help="1-based end line, inclusive."
|
|
132
|
+
)
|
|
133
|
+
write_lines_parser.add_argument(
|
|
134
|
+
"--content", help="Replacement text for the selected lines."
|
|
135
|
+
)
|
|
136
|
+
write_lines_parser.add_argument(
|
|
137
|
+
"--stdin", action="store_true", help="Read replacement text from stdin."
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
search_parser = subparsers.add_parser("search", help="Search text in notes.")
|
|
141
|
+
search_parser.add_argument("query", help="Text to search for.")
|
|
142
|
+
search_parser.add_argument(
|
|
143
|
+
"--case-sensitive",
|
|
144
|
+
action="store_true",
|
|
145
|
+
help="Use case-sensitive matching.",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
return parser
|
|
149
|
+
|
|
150
|
+
@staticmethod
|
|
151
|
+
def _read_content_arg(content: str | None, use_stdin: bool) -> str:
|
|
152
|
+
if content is not None and use_stdin:
|
|
153
|
+
raise VaultError("use either --content or --stdin, not both")
|
|
154
|
+
if content is not None:
|
|
155
|
+
return content
|
|
156
|
+
if use_stdin:
|
|
157
|
+
return sys.stdin.read()
|
|
158
|
+
raise VaultError("content is required; provide --content or --stdin")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def main(argv: list[str] | None = None) -> int:
|
|
162
|
+
return ObsidianCLI().run(argv)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from obsidian_cli.vault import ObsidianVault, VaultError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CommandRunner:
|
|
7
|
+
def __init__(self, vault: ObsidianVault) -> None:
|
|
8
|
+
self._vault = vault
|
|
9
|
+
|
|
10
|
+
def check(self) -> int:
|
|
11
|
+
if not self._vault.exists():
|
|
12
|
+
raise VaultError(f"vault does not exist: {self._vault.root}")
|
|
13
|
+
print(f"vault: {self._vault.root}")
|
|
14
|
+
if self._vault.is_obsidian_vault():
|
|
15
|
+
print("status: ok (.obsidian found)")
|
|
16
|
+
else:
|
|
17
|
+
print("status: warning (.obsidian not found)")
|
|
18
|
+
return 0
|
|
19
|
+
|
|
20
|
+
def list_notes(self, limit: int) -> int:
|
|
21
|
+
notes = self._vault.list_notes()
|
|
22
|
+
if limit > 0:
|
|
23
|
+
notes = notes[:limit]
|
|
24
|
+
for note in notes:
|
|
25
|
+
print(note)
|
|
26
|
+
return 0
|
|
27
|
+
|
|
28
|
+
def read_note(self, note_path: str) -> int:
|
|
29
|
+
print(self._vault.read_note(note_path), end="")
|
|
30
|
+
return 0
|
|
31
|
+
|
|
32
|
+
def read_note_lines(self, note_path: str, start_line: int, end_line: int) -> int:
|
|
33
|
+
print(self._vault.read_note_lines(note_path, start_line, end_line), end="")
|
|
34
|
+
return 0
|
|
35
|
+
|
|
36
|
+
def write_note(self, note_path: str, content: str, create_only: bool) -> int:
|
|
37
|
+
print(self._vault.write_note(note_path, content, create_only=create_only))
|
|
38
|
+
return 0
|
|
39
|
+
|
|
40
|
+
def append_note(self, note_path: str, content: str) -> int:
|
|
41
|
+
print(self._vault.append_note(note_path, content))
|
|
42
|
+
return 0
|
|
43
|
+
|
|
44
|
+
def write_note_lines(
|
|
45
|
+
self,
|
|
46
|
+
note_path: str,
|
|
47
|
+
start_line: int,
|
|
48
|
+
end_line: int,
|
|
49
|
+
content: str,
|
|
50
|
+
) -> int:
|
|
51
|
+
print(self._vault.write_note_lines(note_path, start_line, end_line, content))
|
|
52
|
+
return 0
|
|
53
|
+
|
|
54
|
+
def search(self, query: str, case_sensitive: bool) -> int:
|
|
55
|
+
matches = self._vault.search(query, case_sensitive=case_sensitive)
|
|
56
|
+
for match in matches:
|
|
57
|
+
print(f"{match.note_path}:{match.line_number}: {match.line_text}")
|
|
58
|
+
return 0 if matches else 1
|