odcli 0.1.2__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.2 → odcli-0.1.3}/PKG-INFO +50 -56
- odcli-0.1.3/README.md +176 -0
- {odcli-0.1.2 → odcli-0.1.3}/pyproject.toml +3 -1
- {odcli-0.1.2 → odcli-0.1.3}/src/obsidian_cli/__init__.py +1 -2
- {odcli-0.1.2 → 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.2 → odcli-0.1.3}/src/obsidian_cli/vault.py +6 -2
- {odcli-0.1.2 → odcli-0.1.3}/src/odcli.egg-info/PKG-INFO +50 -56
- {odcli-0.1.2 → odcli-0.1.3}/src/odcli.egg-info/SOURCES.txt +2 -0
- {odcli-0.1.2 → odcli-0.1.3}/tests/test_cli.py +37 -21
- odcli-0.1.2/README.md +0 -183
- odcli-0.1.2/src/obsidian_cli/cli.py +0 -265
- {odcli-0.1.2 → odcli-0.1.3}/setup.cfg +0 -0
- {odcli-0.1.2 → odcli-0.1.3}/src/odcli.egg-info/dependency_links.txt +0 -0
- {odcli-0.1.2 → odcli-0.1.3}/src/odcli.egg-info/entry_points.txt +0 -0
- {odcli-0.1.2 → odcli-0.1.3}/src/odcli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: odcli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: A small CLI for reading and writing notes in a local Obsidian vault.
|
|
5
5
|
Author: Chang LeHung
|
|
6
6
|
Project-URL: Homepage, https://github.com/Chang-LeHung/obsidian-cli
|
|
@@ -10,6 +10,7 @@ Keywords: obsidian,cli,markdown,notes,vault
|
|
|
10
10
|
Classifier: Development Status :: 3 - Alpha
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Operating System :: MacOS
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
13
14
|
Classifier: Programming Language :: Python :: 3
|
|
14
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -20,20 +21,22 @@ Description-Content-Type: text/markdown
|
|
|
20
21
|
|
|
21
22
|
# odcli
|
|
22
23
|
|
|
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.
|
|
24
26
|
|
|
25
|
-
##
|
|
27
|
+
## Features
|
|
26
28
|
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
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
|
|
35
38
|
|
|
36
|
-
##
|
|
39
|
+
## Using uv
|
|
37
40
|
|
|
38
41
|
```bash
|
|
39
42
|
cd /Users/huchang/agents/obsidian_cli
|
|
@@ -41,62 +44,53 @@ uv sync
|
|
|
41
44
|
uv run odcli --help
|
|
42
45
|
```
|
|
43
46
|
|
|
44
|
-
|
|
47
|
+
Run tests:
|
|
45
48
|
|
|
46
49
|
```bash
|
|
47
50
|
cd /Users/huchang/agents/obsidian_cli
|
|
48
51
|
uv run python -m unittest discover -s tests
|
|
49
52
|
```
|
|
50
53
|
|
|
51
|
-
|
|
54
|
+
Build distributions:
|
|
52
55
|
|
|
53
56
|
```bash
|
|
54
57
|
cd /Users/huchang/agents/obsidian_cli
|
|
55
58
|
uv build
|
|
56
59
|
```
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
The published package name on PyPI is `odcli`.
|
|
62
|
+
After installation, both `odcli` and `obsidian-cli` are available as command names.
|
|
60
63
|
|
|
61
|
-
##
|
|
64
|
+
## Run Locally
|
|
62
65
|
|
|
63
66
|
```bash
|
|
64
67
|
cd /Users/huchang/agents/obsidian_cli
|
|
65
68
|
./odcli --help
|
|
66
69
|
```
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
The compatibility entry point is still available:
|
|
69
72
|
|
|
70
73
|
```bash
|
|
71
74
|
cd /Users/huchang/agents/obsidian_cli
|
|
72
75
|
./obsidian-cli --help
|
|
73
76
|
```
|
|
74
77
|
|
|
75
|
-
|
|
78
|
+
If you prefer module execution:
|
|
76
79
|
|
|
77
80
|
```bash
|
|
78
81
|
PYTHONPATH=src python3 -m obsidian_cli --help
|
|
79
82
|
```
|
|
80
83
|
|
|
81
|
-
##
|
|
84
|
+
## Vault Resolution
|
|
82
85
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
cd /Users/huchang/agents/obsidian_cli
|
|
87
|
-
uv sync
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## 指定 vault
|
|
91
|
-
|
|
92
|
-
优先级如下:
|
|
86
|
+
Resolution priority:
|
|
93
87
|
|
|
94
88
|
1. `--vault /path/to/vault`
|
|
95
|
-
2.
|
|
96
|
-
3.
|
|
97
|
-
4.
|
|
89
|
+
2. `OBSIDIAN_VAULT`
|
|
90
|
+
3. The most recently opened vault recorded by local Obsidian config
|
|
91
|
+
4. Common default directories
|
|
98
92
|
|
|
99
|
-
|
|
93
|
+
Built-in default locations:
|
|
100
94
|
|
|
101
95
|
- macOS: `~/Documents/Obsidian Vault`
|
|
102
96
|
- macOS: `~/Documents/Obsidian`
|
|
@@ -104,7 +98,7 @@ uv sync
|
|
|
104
98
|
- Windows: `%USERPROFILE%\\Documents\\Obsidian Vault`
|
|
105
99
|
- Windows: `%USERPROFILE%\\Documents\\Obsidian`
|
|
106
100
|
|
|
107
|
-
|
|
101
|
+
Example:
|
|
108
102
|
|
|
109
103
|
```bash
|
|
110
104
|
export OBSIDIAN_VAULT="/Users/your-name/Documents/MyVault"
|
|
@@ -118,47 +112,47 @@ export OBSIDIAN_VAULT="/Users/your-name/Documents/MyVault"
|
|
|
118
112
|
./odcli search "project alpha"
|
|
119
113
|
```
|
|
120
114
|
|
|
121
|
-
##
|
|
115
|
+
## Commands
|
|
122
116
|
|
|
123
117
|
### `check`
|
|
124
118
|
|
|
125
|
-
|
|
119
|
+
Validate that the vault exists and report whether `.obsidian` is present.
|
|
126
120
|
|
|
127
121
|
### `list`
|
|
128
122
|
|
|
129
|
-
|
|
123
|
+
List Markdown notes in the vault.
|
|
130
124
|
|
|
131
|
-
|
|
125
|
+
Optional arguments:
|
|
132
126
|
|
|
133
127
|
- `--limit N`
|
|
134
128
|
|
|
135
129
|
### `read`
|
|
136
130
|
|
|
137
|
-
|
|
131
|
+
Read a note.
|
|
138
132
|
|
|
139
|
-
|
|
133
|
+
Arguments:
|
|
140
134
|
|
|
141
|
-
- `note_path
|
|
135
|
+
- `note_path`: path relative to the vault root
|
|
142
136
|
|
|
143
137
|
### `write`
|
|
144
138
|
|
|
145
|
-
|
|
139
|
+
Overwrite a note. Parent directories are created automatically if needed.
|
|
146
140
|
|
|
147
|
-
|
|
141
|
+
Arguments:
|
|
148
142
|
|
|
149
143
|
- `note_path`
|
|
150
144
|
- `--content TEXT`
|
|
151
|
-
- `--stdin
|
|
145
|
+
- `--stdin`
|
|
152
146
|
|
|
153
|
-
|
|
147
|
+
Optional arguments:
|
|
154
148
|
|
|
155
|
-
- `--create-only
|
|
149
|
+
- `--create-only`
|
|
156
150
|
|
|
157
151
|
### `read-lines`
|
|
158
152
|
|
|
159
|
-
|
|
153
|
+
Read a line range. Line numbers are 1-based and inclusive.
|
|
160
154
|
|
|
161
|
-
|
|
155
|
+
Arguments:
|
|
162
156
|
|
|
163
157
|
- `note_path`
|
|
164
158
|
- `start_line`
|
|
@@ -166,9 +160,9 @@ export OBSIDIAN_VAULT="/Users/your-name/Documents/MyVault"
|
|
|
166
160
|
|
|
167
161
|
### `write-lines`
|
|
168
162
|
|
|
169
|
-
|
|
163
|
+
Replace a line range. Line numbers are 1-based and inclusive.
|
|
170
164
|
|
|
171
|
-
|
|
165
|
+
Arguments:
|
|
172
166
|
|
|
173
167
|
- `note_path`
|
|
174
168
|
- `start_line`
|
|
@@ -178,9 +172,9 @@ export OBSIDIAN_VAULT="/Users/your-name/Documents/MyVault"
|
|
|
178
172
|
|
|
179
173
|
### `append`
|
|
180
174
|
|
|
181
|
-
|
|
175
|
+
Append content to the end of a note.
|
|
182
176
|
|
|
183
|
-
|
|
177
|
+
Arguments:
|
|
184
178
|
|
|
185
179
|
- `note_path`
|
|
186
180
|
- `--content TEXT`
|
|
@@ -188,14 +182,14 @@ export OBSIDIAN_VAULT="/Users/your-name/Documents/MyVault"
|
|
|
188
182
|
|
|
189
183
|
### `search`
|
|
190
184
|
|
|
191
|
-
|
|
185
|
+
Search across all Markdown notes in the vault.
|
|
192
186
|
|
|
193
|
-
|
|
187
|
+
Arguments:
|
|
194
188
|
|
|
195
189
|
- `query`
|
|
196
190
|
- `--case-sensitive`
|
|
197
191
|
|
|
198
|
-
##
|
|
192
|
+
## Testing
|
|
199
193
|
|
|
200
194
|
```bash
|
|
201
195
|
cd /Users/huchang/agents/obsidian_cli
|
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",
|
|
@@ -36,6 +37,7 @@ obsidian-cli = "obsidian_cli.cli:main"
|
|
|
36
37
|
[dependency-groups]
|
|
37
38
|
dev = [
|
|
38
39
|
"build>=1.2.2",
|
|
40
|
+
"ruff>=0.14.0",
|
|
39
41
|
"twine>=6.1.0",
|
|
40
42
|
]
|
|
41
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
|