odcli 0.1.2__tar.gz → 0.1.4__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.4}/PKG-INFO +69 -56
- odcli-0.1.4/README.md +195 -0
- {odcli-0.1.2 → odcli-0.1.4}/pyproject.toml +3 -1
- {odcli-0.1.2 → odcli-0.1.4}/src/obsidian_cli/__init__.py +1 -2
- odcli-0.1.4/src/obsidian_cli/cli.py +190 -0
- odcli-0.1.4/src/obsidian_cli/commands.py +58 -0
- odcli-0.1.4/src/obsidian_cli/discovery.py +135 -0
- odcli-0.1.4/src/obsidian_cli/plugins.py +84 -0
- {odcli-0.1.2 → odcli-0.1.4}/src/obsidian_cli/vault.py +6 -2
- {odcli-0.1.2 → odcli-0.1.4}/src/odcli.egg-info/PKG-INFO +69 -56
- {odcli-0.1.2 → odcli-0.1.4}/src/odcli.egg-info/SOURCES.txt +3 -0
- {odcli-0.1.2 → odcli-0.1.4}/tests/test_cli.py +65 -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.4}/setup.cfg +0 -0
- {odcli-0.1.2 → odcli-0.1.4}/src/obsidian_cli/__main__.py +0 -0
- {odcli-0.1.2 → odcli-0.1.4}/src/odcli.egg-info/dependency_links.txt +0 -0
- {odcli-0.1.2 → odcli-0.1.4}/src/odcli.egg-info/entry_points.txt +0 -0
- {odcli-0.1.2 → odcli-0.1.4}/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.4
|
|
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,23 @@ 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
|
|
38
|
+
- Install odcli helper skills into Codex or Claude Code skill directories
|
|
35
39
|
|
|
36
|
-
##
|
|
40
|
+
## Using uv
|
|
37
41
|
|
|
38
42
|
```bash
|
|
39
43
|
cd /Users/huchang/agents/obsidian_cli
|
|
@@ -41,62 +45,53 @@ uv sync
|
|
|
41
45
|
uv run odcli --help
|
|
42
46
|
```
|
|
43
47
|
|
|
44
|
-
|
|
48
|
+
Run tests:
|
|
45
49
|
|
|
46
50
|
```bash
|
|
47
51
|
cd /Users/huchang/agents/obsidian_cli
|
|
48
52
|
uv run python -m unittest discover -s tests
|
|
49
53
|
```
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
Build distributions:
|
|
52
56
|
|
|
53
57
|
```bash
|
|
54
58
|
cd /Users/huchang/agents/obsidian_cli
|
|
55
59
|
uv build
|
|
56
60
|
```
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
The published package name on PyPI is `odcli`.
|
|
63
|
+
After installation, both `odcli` and `obsidian-cli` are available as command names.
|
|
60
64
|
|
|
61
|
-
##
|
|
65
|
+
## Run Locally
|
|
62
66
|
|
|
63
67
|
```bash
|
|
64
68
|
cd /Users/huchang/agents/obsidian_cli
|
|
65
69
|
./odcli --help
|
|
66
70
|
```
|
|
67
71
|
|
|
68
|
-
|
|
72
|
+
The compatibility entry point is still available:
|
|
69
73
|
|
|
70
74
|
```bash
|
|
71
75
|
cd /Users/huchang/agents/obsidian_cli
|
|
72
76
|
./obsidian-cli --help
|
|
73
77
|
```
|
|
74
78
|
|
|
75
|
-
|
|
79
|
+
If you prefer module execution:
|
|
76
80
|
|
|
77
81
|
```bash
|
|
78
82
|
PYTHONPATH=src python3 -m obsidian_cli --help
|
|
79
83
|
```
|
|
80
84
|
|
|
81
|
-
##
|
|
85
|
+
## Vault Resolution
|
|
82
86
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
cd /Users/huchang/agents/obsidian_cli
|
|
87
|
-
uv sync
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## 指定 vault
|
|
91
|
-
|
|
92
|
-
优先级如下:
|
|
87
|
+
Resolution priority:
|
|
93
88
|
|
|
94
89
|
1. `--vault /path/to/vault`
|
|
95
|
-
2.
|
|
96
|
-
3.
|
|
97
|
-
4.
|
|
90
|
+
2. `OBSIDIAN_VAULT`
|
|
91
|
+
3. The most recently opened vault recorded by local Obsidian config
|
|
92
|
+
4. Common default directories
|
|
98
93
|
|
|
99
|
-
|
|
94
|
+
Built-in default locations:
|
|
100
95
|
|
|
101
96
|
- macOS: `~/Documents/Obsidian Vault`
|
|
102
97
|
- macOS: `~/Documents/Obsidian`
|
|
@@ -104,7 +99,7 @@ uv sync
|
|
|
104
99
|
- Windows: `%USERPROFILE%\\Documents\\Obsidian Vault`
|
|
105
100
|
- Windows: `%USERPROFILE%\\Documents\\Obsidian`
|
|
106
101
|
|
|
107
|
-
|
|
102
|
+
Example:
|
|
108
103
|
|
|
109
104
|
```bash
|
|
110
105
|
export OBSIDIAN_VAULT="/Users/your-name/Documents/MyVault"
|
|
@@ -118,47 +113,47 @@ export OBSIDIAN_VAULT="/Users/your-name/Documents/MyVault"
|
|
|
118
113
|
./odcli search "project alpha"
|
|
119
114
|
```
|
|
120
115
|
|
|
121
|
-
##
|
|
116
|
+
## Commands
|
|
122
117
|
|
|
123
118
|
### `check`
|
|
124
119
|
|
|
125
|
-
|
|
120
|
+
Validate that the vault exists and report whether `.obsidian` is present.
|
|
126
121
|
|
|
127
122
|
### `list`
|
|
128
123
|
|
|
129
|
-
|
|
124
|
+
List Markdown notes in the vault.
|
|
130
125
|
|
|
131
|
-
|
|
126
|
+
Optional arguments:
|
|
132
127
|
|
|
133
128
|
- `--limit N`
|
|
134
129
|
|
|
135
130
|
### `read`
|
|
136
131
|
|
|
137
|
-
|
|
132
|
+
Read a note.
|
|
138
133
|
|
|
139
|
-
|
|
134
|
+
Arguments:
|
|
140
135
|
|
|
141
|
-
- `note_path
|
|
136
|
+
- `note_path`: path relative to the vault root
|
|
142
137
|
|
|
143
138
|
### `write`
|
|
144
139
|
|
|
145
|
-
|
|
140
|
+
Overwrite a note. Parent directories are created automatically if needed.
|
|
146
141
|
|
|
147
|
-
|
|
142
|
+
Arguments:
|
|
148
143
|
|
|
149
144
|
- `note_path`
|
|
150
145
|
- `--content TEXT`
|
|
151
|
-
- `--stdin
|
|
146
|
+
- `--stdin`
|
|
152
147
|
|
|
153
|
-
|
|
148
|
+
Optional arguments:
|
|
154
149
|
|
|
155
|
-
- `--create-only
|
|
150
|
+
- `--create-only`
|
|
156
151
|
|
|
157
152
|
### `read-lines`
|
|
158
153
|
|
|
159
|
-
|
|
154
|
+
Read a line range. Line numbers are 1-based and inclusive.
|
|
160
155
|
|
|
161
|
-
|
|
156
|
+
Arguments:
|
|
162
157
|
|
|
163
158
|
- `note_path`
|
|
164
159
|
- `start_line`
|
|
@@ -166,9 +161,9 @@ export OBSIDIAN_VAULT="/Users/your-name/Documents/MyVault"
|
|
|
166
161
|
|
|
167
162
|
### `write-lines`
|
|
168
163
|
|
|
169
|
-
|
|
164
|
+
Replace a line range. Line numbers are 1-based and inclusive.
|
|
170
165
|
|
|
171
|
-
|
|
166
|
+
Arguments:
|
|
172
167
|
|
|
173
168
|
- `note_path`
|
|
174
169
|
- `start_line`
|
|
@@ -178,9 +173,9 @@ export OBSIDIAN_VAULT="/Users/your-name/Documents/MyVault"
|
|
|
178
173
|
|
|
179
174
|
### `append`
|
|
180
175
|
|
|
181
|
-
|
|
176
|
+
Append content to the end of a note.
|
|
182
177
|
|
|
183
|
-
|
|
178
|
+
Arguments:
|
|
184
179
|
|
|
185
180
|
- `note_path`
|
|
186
181
|
- `--content TEXT`
|
|
@@ -188,14 +183,32 @@ export OBSIDIAN_VAULT="/Users/your-name/Documents/MyVault"
|
|
|
188
183
|
|
|
189
184
|
### `search`
|
|
190
185
|
|
|
191
|
-
|
|
186
|
+
Search across all Markdown notes in the vault.
|
|
192
187
|
|
|
193
|
-
|
|
188
|
+
Arguments:
|
|
194
189
|
|
|
195
190
|
- `query`
|
|
196
191
|
- `--case-sensitive`
|
|
197
192
|
|
|
198
|
-
|
|
193
|
+
### `plugin install`
|
|
194
|
+
|
|
195
|
+
Install odcli helper skills for local coding tools.
|
|
196
|
+
|
|
197
|
+
Targets:
|
|
198
|
+
|
|
199
|
+
- `codex-skill`: installs to `~/.codex/skills/odcli/SKILL.md`
|
|
200
|
+
- `claude-skill`: installs to `~/.claude/skills/odcli/SKILL.md`
|
|
201
|
+
- `all-skills`: installs both
|
|
202
|
+
|
|
203
|
+
Examples:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
odcli plugin install codex-skill
|
|
207
|
+
odcli plugin install claude-skill
|
|
208
|
+
odcli plugin install all-skills
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Testing
|
|
199
212
|
|
|
200
213
|
```bash
|
|
201
214
|
cd /Users/huchang/agents/obsidian_cli
|
odcli-0.1.4/README.md
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
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
|
+
- Install odcli helper skills into Codex or Claude Code skill directories
|
|
18
|
+
|
|
19
|
+
## Using uv
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
23
|
+
uv sync
|
|
24
|
+
uv run odcli --help
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Run tests:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
31
|
+
uv run python -m unittest discover -s tests
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Build distributions:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
38
|
+
uv build
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The published package name on PyPI is `odcli`.
|
|
42
|
+
After installation, both `odcli` and `obsidian-cli` are available as command names.
|
|
43
|
+
|
|
44
|
+
## Run Locally
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
48
|
+
./odcli --help
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The compatibility entry point is still available:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
55
|
+
./obsidian-cli --help
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If you prefer module execution:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
PYTHONPATH=src python3 -m obsidian_cli --help
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Vault Resolution
|
|
65
|
+
|
|
66
|
+
Resolution priority:
|
|
67
|
+
|
|
68
|
+
1. `--vault /path/to/vault`
|
|
69
|
+
2. `OBSIDIAN_VAULT`
|
|
70
|
+
3. The most recently opened vault recorded by local Obsidian config
|
|
71
|
+
4. Common default directories
|
|
72
|
+
|
|
73
|
+
Built-in default locations:
|
|
74
|
+
|
|
75
|
+
- macOS: `~/Documents/Obsidian Vault`
|
|
76
|
+
- macOS: `~/Documents/Obsidian`
|
|
77
|
+
- macOS iCloud: `~/Library/Mobile Documents/iCloud~md~obsidian/Documents`
|
|
78
|
+
- Windows: `%USERPROFILE%\\Documents\\Obsidian Vault`
|
|
79
|
+
- Windows: `%USERPROFILE%\\Documents\\Obsidian`
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
export OBSIDIAN_VAULT="/Users/your-name/Documents/MyVault"
|
|
85
|
+
./odcli check
|
|
86
|
+
./odcli list
|
|
87
|
+
./odcli read Inbox/today.md
|
|
88
|
+
./odcli read-lines Inbox/today.md 3 8
|
|
89
|
+
./odcli write Inbox/today.md --content "# Today"
|
|
90
|
+
./odcli write-lines Inbox/today.md 3 4 --content "- replaced\n- lines\n"
|
|
91
|
+
./odcli append Inbox/today.md --content "\n- new item"
|
|
92
|
+
./odcli search "project alpha"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Commands
|
|
96
|
+
|
|
97
|
+
### `check`
|
|
98
|
+
|
|
99
|
+
Validate that the vault exists and report whether `.obsidian` is present.
|
|
100
|
+
|
|
101
|
+
### `list`
|
|
102
|
+
|
|
103
|
+
List Markdown notes in the vault.
|
|
104
|
+
|
|
105
|
+
Optional arguments:
|
|
106
|
+
|
|
107
|
+
- `--limit N`
|
|
108
|
+
|
|
109
|
+
### `read`
|
|
110
|
+
|
|
111
|
+
Read a note.
|
|
112
|
+
|
|
113
|
+
Arguments:
|
|
114
|
+
|
|
115
|
+
- `note_path`: path relative to the vault root
|
|
116
|
+
|
|
117
|
+
### `write`
|
|
118
|
+
|
|
119
|
+
Overwrite a note. Parent directories are created automatically if needed.
|
|
120
|
+
|
|
121
|
+
Arguments:
|
|
122
|
+
|
|
123
|
+
- `note_path`
|
|
124
|
+
- `--content TEXT`
|
|
125
|
+
- `--stdin`
|
|
126
|
+
|
|
127
|
+
Optional arguments:
|
|
128
|
+
|
|
129
|
+
- `--create-only`
|
|
130
|
+
|
|
131
|
+
### `read-lines`
|
|
132
|
+
|
|
133
|
+
Read a line range. Line numbers are 1-based and inclusive.
|
|
134
|
+
|
|
135
|
+
Arguments:
|
|
136
|
+
|
|
137
|
+
- `note_path`
|
|
138
|
+
- `start_line`
|
|
139
|
+
- `end_line`
|
|
140
|
+
|
|
141
|
+
### `write-lines`
|
|
142
|
+
|
|
143
|
+
Replace a line range. Line numbers are 1-based and inclusive.
|
|
144
|
+
|
|
145
|
+
Arguments:
|
|
146
|
+
|
|
147
|
+
- `note_path`
|
|
148
|
+
- `start_line`
|
|
149
|
+
- `end_line`
|
|
150
|
+
- `--content TEXT`
|
|
151
|
+
- `--stdin`
|
|
152
|
+
|
|
153
|
+
### `append`
|
|
154
|
+
|
|
155
|
+
Append content to the end of a note.
|
|
156
|
+
|
|
157
|
+
Arguments:
|
|
158
|
+
|
|
159
|
+
- `note_path`
|
|
160
|
+
- `--content TEXT`
|
|
161
|
+
- `--stdin`
|
|
162
|
+
|
|
163
|
+
### `search`
|
|
164
|
+
|
|
165
|
+
Search across all Markdown notes in the vault.
|
|
166
|
+
|
|
167
|
+
Arguments:
|
|
168
|
+
|
|
169
|
+
- `query`
|
|
170
|
+
- `--case-sensitive`
|
|
171
|
+
|
|
172
|
+
### `plugin install`
|
|
173
|
+
|
|
174
|
+
Install odcli helper skills for local coding tools.
|
|
175
|
+
|
|
176
|
+
Targets:
|
|
177
|
+
|
|
178
|
+
- `codex-skill`: installs to `~/.codex/skills/odcli/SKILL.md`
|
|
179
|
+
- `claude-skill`: installs to `~/.claude/skills/odcli/SKILL.md`
|
|
180
|
+
- `all-skills`: installs both
|
|
181
|
+
|
|
182
|
+
Examples:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
odcli plugin install codex-skill
|
|
186
|
+
odcli plugin install claude-skill
|
|
187
|
+
odcli plugin install all-skills
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Testing
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
cd /Users/huchang/agents/obsidian_cli
|
|
194
|
+
uv run python -m unittest discover -s tests
|
|
195
|
+
```
|
|
@@ -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.4"
|
|
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,190 @@
|
|
|
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.plugins import SkillInstaller
|
|
9
|
+
from obsidian_cli.vault import ObsidianVault, VaultError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ObsidianCLI:
|
|
13
|
+
def __init__(self, vault_locator: VaultLocator | None = None) -> None:
|
|
14
|
+
self._vault_locator = vault_locator or VaultLocator()
|
|
15
|
+
self._skill_installer = SkillInstaller()
|
|
16
|
+
self._parser = self._build_parser()
|
|
17
|
+
|
|
18
|
+
def run(self, argv: list[str] | None = None) -> int:
|
|
19
|
+
args = self._parser.parse_args(argv)
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
if args.command == "plugin":
|
|
23
|
+
return self._run_plugin_command(args)
|
|
24
|
+
|
|
25
|
+
runner = CommandRunner(
|
|
26
|
+
ObsidianVault(self._vault_locator.resolve(args.vault))
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if args.command == "check":
|
|
30
|
+
return runner.check()
|
|
31
|
+
if args.command == "list":
|
|
32
|
+
return runner.list_notes(args.limit)
|
|
33
|
+
if args.command == "read":
|
|
34
|
+
return runner.read_note(args.note_path)
|
|
35
|
+
if args.command == "read-lines":
|
|
36
|
+
return runner.read_note_lines(
|
|
37
|
+
args.note_path, args.start_line, args.end_line
|
|
38
|
+
)
|
|
39
|
+
if args.command == "write":
|
|
40
|
+
return runner.write_note(
|
|
41
|
+
args.note_path,
|
|
42
|
+
self._read_content_arg(args.content, args.stdin),
|
|
43
|
+
args.create_only,
|
|
44
|
+
)
|
|
45
|
+
if args.command == "append":
|
|
46
|
+
return runner.append_note(
|
|
47
|
+
args.note_path,
|
|
48
|
+
self._read_content_arg(args.content, args.stdin),
|
|
49
|
+
)
|
|
50
|
+
if args.command == "write-lines":
|
|
51
|
+
return runner.write_note_lines(
|
|
52
|
+
args.note_path,
|
|
53
|
+
args.start_line,
|
|
54
|
+
args.end_line,
|
|
55
|
+
self._read_content_arg(args.content, args.stdin),
|
|
56
|
+
)
|
|
57
|
+
if args.command == "search":
|
|
58
|
+
return runner.search(args.query, args.case_sensitive)
|
|
59
|
+
except VaultError as exc:
|
|
60
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
61
|
+
return 2
|
|
62
|
+
|
|
63
|
+
self._parser.error(f"unsupported command: {args.command}")
|
|
64
|
+
return 2
|
|
65
|
+
|
|
66
|
+
def _build_parser(self) -> argparse.ArgumentParser:
|
|
67
|
+
parser = argparse.ArgumentParser(
|
|
68
|
+
prog="obsidian-cli",
|
|
69
|
+
description="Read and write notes inside a local Obsidian vault.",
|
|
70
|
+
)
|
|
71
|
+
parser.add_argument(
|
|
72
|
+
"--vault",
|
|
73
|
+
help="Path to the Obsidian vault. Falls back to OBSIDIAN_VAULT.",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
77
|
+
subparsers.add_parser("check", help="Validate the vault path.")
|
|
78
|
+
|
|
79
|
+
list_parser = subparsers.add_parser("list", help="List markdown notes.")
|
|
80
|
+
list_parser.add_argument(
|
|
81
|
+
"--limit", type=int, default=0, help="Maximum number of notes to show."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
read_parser = subparsers.add_parser("read", help="Read a note.")
|
|
85
|
+
read_parser.add_argument(
|
|
86
|
+
"note_path", help="Path to the note relative to the vault root."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
read_lines_parser = subparsers.add_parser(
|
|
90
|
+
"read-lines", help="Read a line range from a note."
|
|
91
|
+
)
|
|
92
|
+
read_lines_parser.add_argument(
|
|
93
|
+
"note_path", help="Path to the note relative to the vault root."
|
|
94
|
+
)
|
|
95
|
+
read_lines_parser.add_argument(
|
|
96
|
+
"start_line", type=int, help="1-based start line, inclusive."
|
|
97
|
+
)
|
|
98
|
+
read_lines_parser.add_argument(
|
|
99
|
+
"end_line", type=int, help="1-based end line, inclusive."
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
write_parser = subparsers.add_parser("write", help="Write a note.")
|
|
103
|
+
write_parser.add_argument(
|
|
104
|
+
"note_path", help="Path to the note relative to the vault root."
|
|
105
|
+
)
|
|
106
|
+
write_parser.add_argument("--content", help="Text content to write.")
|
|
107
|
+
write_parser.add_argument(
|
|
108
|
+
"--stdin", action="store_true", help="Read note content from stdin."
|
|
109
|
+
)
|
|
110
|
+
write_parser.add_argument(
|
|
111
|
+
"--create-only",
|
|
112
|
+
action="store_true",
|
|
113
|
+
help="Fail if the note already exists.",
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
append_parser = subparsers.add_parser("append", help="Append to a note.")
|
|
117
|
+
append_parser.add_argument(
|
|
118
|
+
"note_path", help="Path to the note relative to the vault root."
|
|
119
|
+
)
|
|
120
|
+
append_parser.add_argument("--content", help="Text content to append.")
|
|
121
|
+
append_parser.add_argument(
|
|
122
|
+
"--stdin", action="store_true", help="Read appended content from stdin."
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
write_lines_parser = subparsers.add_parser(
|
|
126
|
+
"write-lines",
|
|
127
|
+
help="Replace a line range inside a note.",
|
|
128
|
+
)
|
|
129
|
+
write_lines_parser.add_argument(
|
|
130
|
+
"note_path", help="Path to the note relative to the vault root."
|
|
131
|
+
)
|
|
132
|
+
write_lines_parser.add_argument(
|
|
133
|
+
"start_line", type=int, help="1-based start line, inclusive."
|
|
134
|
+
)
|
|
135
|
+
write_lines_parser.add_argument(
|
|
136
|
+
"end_line", type=int, help="1-based end line, inclusive."
|
|
137
|
+
)
|
|
138
|
+
write_lines_parser.add_argument(
|
|
139
|
+
"--content", help="Replacement text for the selected lines."
|
|
140
|
+
)
|
|
141
|
+
write_lines_parser.add_argument(
|
|
142
|
+
"--stdin", action="store_true", help="Read replacement text from stdin."
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
search_parser = subparsers.add_parser("search", help="Search text in notes.")
|
|
146
|
+
search_parser.add_argument("query", help="Text to search for.")
|
|
147
|
+
search_parser.add_argument(
|
|
148
|
+
"--case-sensitive",
|
|
149
|
+
action="store_true",
|
|
150
|
+
help="Use case-sensitive matching.",
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
plugin_parser = subparsers.add_parser(
|
|
154
|
+
"plugin", help="Install odcli helper skills for supported coding tools."
|
|
155
|
+
)
|
|
156
|
+
plugin_subparsers = plugin_parser.add_subparsers(
|
|
157
|
+
dest="plugin_command", required=True
|
|
158
|
+
)
|
|
159
|
+
plugin_install_parser = plugin_subparsers.add_parser(
|
|
160
|
+
"install", help="Install an odcli skill into a supported tool directory."
|
|
161
|
+
)
|
|
162
|
+
plugin_install_parser.add_argument(
|
|
163
|
+
"target",
|
|
164
|
+
choices=["codex-skill", "claude-skill", "all-skills"],
|
|
165
|
+
help="Installation target.",
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return parser
|
|
169
|
+
|
|
170
|
+
def _run_plugin_command(self, args: argparse.Namespace) -> int:
|
|
171
|
+
if args.plugin_command == "install":
|
|
172
|
+
for result in self._skill_installer.install(args.target):
|
|
173
|
+
print(f"{result.target}: {result.path}")
|
|
174
|
+
return 0
|
|
175
|
+
self._parser.error(f"unsupported plugin command: {args.plugin_command}")
|
|
176
|
+
return 2
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def _read_content_arg(content: str | None, use_stdin: bool) -> str:
|
|
180
|
+
if content is not None and use_stdin:
|
|
181
|
+
raise VaultError("use either --content or --stdin, not both")
|
|
182
|
+
if content is not None:
|
|
183
|
+
return content
|
|
184
|
+
if use_stdin:
|
|
185
|
+
return sys.stdin.read()
|
|
186
|
+
raise VaultError("content is required; provide --content or --stdin")
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def main(argv: list[str] | None = None) -> int:
|
|
190
|
+
return ObsidianCLI().run(argv)
|