odcli 0.1.5__tar.gz → 0.1.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: odcli
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: A small CLI for reading and writing notes in a local Obsidian vault.
5
5
  Author: odcli contributors
6
6
  Keywords: obsidian,cli,markdown,notes,vault
@@ -18,68 +18,160 @@ Description-Content-Type: text/markdown
18
18
 
19
19
  # odcli
20
20
 
21
- `odcli` is a local Python CLI for reading and writing notes in an Obsidian vault.
22
- 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.
21
+ `odcli` is a command-line tool for reading and writing notes in a local Obsidian vault.
23
22
 
24
- ## Features
23
+ It works directly on Markdown files inside the vault, so you can use it without any private Obsidian API.
25
24
 
26
- - Validate whether a vault path is available
27
- - List Markdown notes in the vault
28
- - Read a specific note
29
- - Read a specific line range from a note
30
- - Overwrite a note or create it automatically
31
- - Replace a specific line range in a note
32
- - Append content to a note
33
- - Full-text search across the vault
34
- - Auto-discover the default vault from Obsidian config or common macOS and Windows locations
35
- - Install odcli helper skills into Codex or Claude Code skill directories
25
+ ## Install
36
26
 
37
- ## Using uv
27
+ From PyPI:
38
28
 
39
29
  ```bash
40
- cd path/to/obsidian_cli
41
- uv sync
42
- uv run odcli --help
30
+ pip install odcli
43
31
  ```
44
32
 
45
- Run tests:
33
+ Or with `uv`:
46
34
 
47
35
  ```bash
48
- cd path/to/obsidian_cli
49
- uv run python -m unittest discover -s tests
36
+ uv tool install odcli
50
37
  ```
51
38
 
52
- Build distributions:
39
+ After installation, both command names are available:
53
40
 
54
41
  ```bash
55
- cd path/to/obsidian_cli
56
- uv build
42
+ odcli --help
43
+ obsidian-cli --help
57
44
  ```
58
45
 
59
- The published package name on PyPI is `odcli`.
60
- After installation, both `odcli` and `obsidian-cli` are available as command names.
46
+ ## Quickstart
61
47
 
62
- ## Run Locally
48
+ If your vault is already in a common location, `odcli` can usually find it automatically:
63
49
 
64
50
  ```bash
65
- cd path/to/obsidian_cli
66
- ./odcli --help
51
+ odcli check
52
+ odcli list
67
53
  ```
68
54
 
69
- The compatibility entry point is still available:
55
+ If you want to set the vault explicitly:
70
56
 
71
57
  ```bash
72
- cd path/to/obsidian_cli
73
- ./obsidian-cli --help
58
+ export OBSIDIAN_VAULT="/path/to/MyVault"
59
+ odcli check
60
+ ```
61
+
62
+ You can also override the vault per command:
63
+
64
+ ```bash
65
+ odcli --vault "/path/to/MyVault" list
66
+ ```
67
+
68
+ ## Remote Vault over SSH
69
+
70
+ You can operate on an Obsidian vault stored on another machine over SSH.
71
+
72
+ Example:
73
+
74
+ ```bash
75
+ odcli \
76
+ --ssh-host your-server \
77
+ --ssh-user your-user \
78
+ --vault /path/to/ObsidianVault \
79
+ list
80
+ ```
81
+
82
+ Read a remote note:
83
+
84
+ ```bash
85
+ odcli \
86
+ --ssh-host your-server \
87
+ --ssh-user your-user \
88
+ --vault /path/to/ObsidianVault \
89
+ read Inbox/today.md
90
+ ```
91
+
92
+ Write a remote note:
93
+
94
+ ```bash
95
+ odcli \
96
+ --ssh-host your-server \
97
+ --ssh-user your-user \
98
+ --vault /path/to/ObsidianVault \
99
+ write Inbox/today.md --content "# Remote note"
100
+ ```
101
+
102
+ Optional SSH flags:
103
+
104
+ - `--ssh-port`
105
+ - `--ssh-identity`
106
+
107
+ In SSH mode, `--vault` or `OBSIDIAN_VAULT` should point to the remote vault path.
108
+
109
+ ## Common Commands
110
+
111
+ Read a note:
112
+
113
+ ```bash
114
+ odcli read Inbox/today.md
115
+ ```
116
+
117
+ Read specific lines:
118
+
119
+ ```bash
120
+ odcli read-lines Inbox/today.md 3 8
121
+ ```
122
+
123
+ Create or overwrite a note:
124
+
125
+ ```bash
126
+ odcli write Inbox/today.md --content "# Today"
74
127
  ```
75
128
 
76
- If you prefer module execution:
129
+ Replace a line range:
77
130
 
78
131
  ```bash
79
- PYTHONPATH=src python3 -m obsidian_cli --help
132
+ odcli write-lines Inbox/today.md 3 4 --content "- replaced\n- lines\n"
80
133
  ```
81
134
 
82
- ## Vault Resolution
135
+ Append content:
136
+
137
+ ```bash
138
+ odcli append Inbox/today.md --content "\n- new item"
139
+ ```
140
+
141
+ Search across the vault:
142
+
143
+ ```bash
144
+ odcli search "project alpha"
145
+ ```
146
+
147
+ ## Skill Install
148
+
149
+ `odcli` can install helper skills for local coding tools.
150
+
151
+ Install into Codex:
152
+
153
+ ```bash
154
+ odcli plugin install codex-skill
155
+ ```
156
+
157
+ Install into Claude Code:
158
+
159
+ ```bash
160
+ odcli plugin install claude-skill
161
+ ```
162
+
163
+ Install both:
164
+
165
+ ```bash
166
+ odcli plugin install all-skills
167
+ ```
168
+
169
+ Installed paths:
170
+
171
+ - Codex: `~/.codex/skills/odcli/SKILL.md`
172
+ - Claude Code: `~/.claude/skills/odcli/SKILL.md`
173
+
174
+ ## Vault Discovery
83
175
 
84
176
  Resolution priority:
85
177
 
@@ -96,21 +188,7 @@ Built-in default locations:
96
188
  - Windows: `%USERPROFILE%\\Documents\\Obsidian Vault`
97
189
  - Windows: `%USERPROFILE%\\Documents\\Obsidian`
98
190
 
99
- Example:
100
-
101
- ```bash
102
- export OBSIDIAN_VAULT="/path/to/MyVault"
103
- ./odcli check
104
- ./odcli list
105
- ./odcli read Inbox/today.md
106
- ./odcli read-lines Inbox/today.md 3 8
107
- ./odcli write Inbox/today.md --content "# Today"
108
- ./odcli write-lines Inbox/today.md 3 4 --content "- replaced\n- lines\n"
109
- ./odcli append Inbox/today.md --content "\n- new item"
110
- ./odcli search "project alpha"
111
- ```
112
-
113
- ## Commands
191
+ ## Command Summary
114
192
 
115
193
  ### `check`
116
194
 
@@ -187,27 +265,34 @@ Arguments:
187
265
  - `query`
188
266
  - `--case-sensitive`
189
267
 
190
- ### `plugin install`
191
-
192
- Install odcli helper skills for local coding tools.
268
+ ## Global Options
193
269
 
194
- Targets:
270
+ - `--vault`
271
+ - `--ssh-host`
272
+ - `--ssh-user`
273
+ - `--ssh-port`
274
+ - `--ssh-identity`
195
275
 
196
- - `codex-skill`: installs to `~/.codex/skills/odcli/SKILL.md`
197
- - `claude-skill`: installs to `~/.claude/skills/odcli/SKILL.md`
198
- - `all-skills`: installs both
276
+ ## For Developers
199
277
 
200
- Examples:
278
+ Run from source:
201
279
 
202
280
  ```bash
203
- odcli plugin install codex-skill
204
- odcli plugin install claude-skill
205
- odcli plugin install all-skills
281
+ cd path/to/obsidian_cli
282
+ uv sync
283
+ uv run odcli --help
206
284
  ```
207
285
 
208
- ## Testing
286
+ Run tests:
209
287
 
210
288
  ```bash
211
289
  cd path/to/obsidian_cli
212
290
  uv run python -m unittest discover -s tests
213
291
  ```
292
+
293
+ Build distributions:
294
+
295
+ ```bash
296
+ cd path/to/obsidian_cli
297
+ uv build
298
+ ```
odcli-0.1.7/README.md ADDED
@@ -0,0 +1,280 @@
1
+ # odcli
2
+
3
+ `odcli` is a command-line tool for reading and writing notes in a local Obsidian vault.
4
+
5
+ It works directly on Markdown files inside the vault, so you can use it without any private Obsidian API.
6
+
7
+ ## Install
8
+
9
+ From PyPI:
10
+
11
+ ```bash
12
+ pip install odcli
13
+ ```
14
+
15
+ Or with `uv`:
16
+
17
+ ```bash
18
+ uv tool install odcli
19
+ ```
20
+
21
+ After installation, both command names are available:
22
+
23
+ ```bash
24
+ odcli --help
25
+ obsidian-cli --help
26
+ ```
27
+
28
+ ## Quickstart
29
+
30
+ If your vault is already in a common location, `odcli` can usually find it automatically:
31
+
32
+ ```bash
33
+ odcli check
34
+ odcli list
35
+ ```
36
+
37
+ If you want to set the vault explicitly:
38
+
39
+ ```bash
40
+ export OBSIDIAN_VAULT="/path/to/MyVault"
41
+ odcli check
42
+ ```
43
+
44
+ You can also override the vault per command:
45
+
46
+ ```bash
47
+ odcli --vault "/path/to/MyVault" list
48
+ ```
49
+
50
+ ## Remote Vault over SSH
51
+
52
+ You can operate on an Obsidian vault stored on another machine over SSH.
53
+
54
+ Example:
55
+
56
+ ```bash
57
+ odcli \
58
+ --ssh-host your-server \
59
+ --ssh-user your-user \
60
+ --vault /path/to/ObsidianVault \
61
+ list
62
+ ```
63
+
64
+ Read a remote note:
65
+
66
+ ```bash
67
+ odcli \
68
+ --ssh-host your-server \
69
+ --ssh-user your-user \
70
+ --vault /path/to/ObsidianVault \
71
+ read Inbox/today.md
72
+ ```
73
+
74
+ Write a remote note:
75
+
76
+ ```bash
77
+ odcli \
78
+ --ssh-host your-server \
79
+ --ssh-user your-user \
80
+ --vault /path/to/ObsidianVault \
81
+ write Inbox/today.md --content "# Remote note"
82
+ ```
83
+
84
+ Optional SSH flags:
85
+
86
+ - `--ssh-port`
87
+ - `--ssh-identity`
88
+
89
+ In SSH mode, `--vault` or `OBSIDIAN_VAULT` should point to the remote vault path.
90
+
91
+ ## Common Commands
92
+
93
+ Read a note:
94
+
95
+ ```bash
96
+ odcli read Inbox/today.md
97
+ ```
98
+
99
+ Read specific lines:
100
+
101
+ ```bash
102
+ odcli read-lines Inbox/today.md 3 8
103
+ ```
104
+
105
+ Create or overwrite a note:
106
+
107
+ ```bash
108
+ odcli write Inbox/today.md --content "# Today"
109
+ ```
110
+
111
+ Replace a line range:
112
+
113
+ ```bash
114
+ odcli write-lines Inbox/today.md 3 4 --content "- replaced\n- lines\n"
115
+ ```
116
+
117
+ Append content:
118
+
119
+ ```bash
120
+ odcli append Inbox/today.md --content "\n- new item"
121
+ ```
122
+
123
+ Search across the vault:
124
+
125
+ ```bash
126
+ odcli search "project alpha"
127
+ ```
128
+
129
+ ## Skill Install
130
+
131
+ `odcli` can install helper skills for local coding tools.
132
+
133
+ Install into Codex:
134
+
135
+ ```bash
136
+ odcli plugin install codex-skill
137
+ ```
138
+
139
+ Install into Claude Code:
140
+
141
+ ```bash
142
+ odcli plugin install claude-skill
143
+ ```
144
+
145
+ Install both:
146
+
147
+ ```bash
148
+ odcli plugin install all-skills
149
+ ```
150
+
151
+ Installed paths:
152
+
153
+ - Codex: `~/.codex/skills/odcli/SKILL.md`
154
+ - Claude Code: `~/.claude/skills/odcli/SKILL.md`
155
+
156
+ ## Vault Discovery
157
+
158
+ Resolution priority:
159
+
160
+ 1. `--vault /path/to/vault`
161
+ 2. `OBSIDIAN_VAULT`
162
+ 3. The most recently opened vault recorded by local Obsidian config
163
+ 4. Common default directories
164
+
165
+ Built-in default locations:
166
+
167
+ - macOS: `~/Documents/Obsidian Vault`
168
+ - macOS: `~/Documents/Obsidian`
169
+ - macOS iCloud: `~/Library/Mobile Documents/iCloud~md~obsidian/Documents`
170
+ - Windows: `%USERPROFILE%\\Documents\\Obsidian Vault`
171
+ - Windows: `%USERPROFILE%\\Documents\\Obsidian`
172
+
173
+ ## Command Summary
174
+
175
+ ### `check`
176
+
177
+ Validate that the vault exists and report whether `.obsidian` is present.
178
+
179
+ ### `list`
180
+
181
+ List Markdown notes in the vault.
182
+
183
+ Optional arguments:
184
+
185
+ - `--limit N`
186
+
187
+ ### `read`
188
+
189
+ Read a note.
190
+
191
+ Arguments:
192
+
193
+ - `note_path`: path relative to the vault root
194
+
195
+ ### `write`
196
+
197
+ Overwrite a note. Parent directories are created automatically if needed.
198
+
199
+ Arguments:
200
+
201
+ - `note_path`
202
+ - `--content TEXT`
203
+ - `--stdin`
204
+
205
+ Optional arguments:
206
+
207
+ - `--create-only`
208
+
209
+ ### `read-lines`
210
+
211
+ Read a line range. Line numbers are 1-based and inclusive.
212
+
213
+ Arguments:
214
+
215
+ - `note_path`
216
+ - `start_line`
217
+ - `end_line`
218
+
219
+ ### `write-lines`
220
+
221
+ Replace a line range. Line numbers are 1-based and inclusive.
222
+
223
+ Arguments:
224
+
225
+ - `note_path`
226
+ - `start_line`
227
+ - `end_line`
228
+ - `--content TEXT`
229
+ - `--stdin`
230
+
231
+ ### `append`
232
+
233
+ Append content to the end of a note.
234
+
235
+ Arguments:
236
+
237
+ - `note_path`
238
+ - `--content TEXT`
239
+ - `--stdin`
240
+
241
+ ### `search`
242
+
243
+ Search across all Markdown notes in the vault.
244
+
245
+ Arguments:
246
+
247
+ - `query`
248
+ - `--case-sensitive`
249
+
250
+ ## Global Options
251
+
252
+ - `--vault`
253
+ - `--ssh-host`
254
+ - `--ssh-user`
255
+ - `--ssh-port`
256
+ - `--ssh-identity`
257
+
258
+ ## For Developers
259
+
260
+ Run from source:
261
+
262
+ ```bash
263
+ cd path/to/obsidian_cli
264
+ uv sync
265
+ uv run odcli --help
266
+ ```
267
+
268
+ Run tests:
269
+
270
+ ```bash
271
+ cd path/to/obsidian_cli
272
+ uv run python -m unittest discover -s tests
273
+ ```
274
+
275
+ Build distributions:
276
+
277
+ ```bash
278
+ cd path/to/obsidian_cli
279
+ uv build
280
+ ```
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "odcli"
7
- version = "0.1.5"
7
+ version = "0.1.7"
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"
@@ -1,3 +1,3 @@
1
1
  __all__ = ["__version__"]
2
2
 
3
- __version__ = "0.1.5"
3
+ __version__ = "0.1.7"
@@ -6,7 +6,13 @@ import sys
6
6
  from obsidian_cli.commands import CommandRunner
7
7
  from obsidian_cli.discovery import VaultLocator
8
8
  from obsidian_cli.plugins import SkillInstaller
9
- from obsidian_cli.vault import ObsidianVault, VaultError
9
+ from obsidian_cli.vault import (
10
+ ObsidianVault,
11
+ SshConfig,
12
+ SshObsidianVault,
13
+ VaultBackend,
14
+ VaultError,
15
+ )
10
16
 
11
17
 
12
18
  class ObsidianCLI:
@@ -22,9 +28,7 @@ class ObsidianCLI:
22
28
  if args.command == "plugin":
23
29
  return self._run_plugin_command(args)
24
30
 
25
- runner = CommandRunner(
26
- ObsidianVault(self._vault_locator.resolve(args.vault))
27
- )
31
+ runner = CommandRunner(self._build_vault(args))
28
32
 
29
33
  if args.command == "check":
30
34
  return runner.check()
@@ -72,6 +76,13 @@ class ObsidianCLI:
72
76
  "--vault",
73
77
  help="Path to the Obsidian vault. Falls back to OBSIDIAN_VAULT.",
74
78
  )
79
+ parser.add_argument("--ssh-host", help="SSH host for a remote vault.")
80
+ parser.add_argument("--ssh-user", help="SSH username for a remote vault.")
81
+ parser.add_argument("--ssh-port", type=int, help="SSH port for a remote vault.")
82
+ parser.add_argument(
83
+ "--ssh-identity",
84
+ help="SSH identity file used when connecting to a remote vault.",
85
+ )
75
86
 
76
87
  subparsers = parser.add_subparsers(dest="command", required=True)
77
88
  subparsers.add_parser("check", help="Validate the vault path.")
@@ -175,6 +186,20 @@ class ObsidianCLI:
175
186
  self._parser.error(f"unsupported plugin command: {args.plugin_command}")
176
187
  return 2
177
188
 
189
+ def _build_vault(self, args: argparse.Namespace) -> VaultBackend:
190
+ if args.ssh_host:
191
+ ssh_root = self._vault_locator.resolve_configured(args.vault)
192
+ return SshObsidianVault(
193
+ str(ssh_root),
194
+ SshConfig(
195
+ host=args.ssh_host,
196
+ user=args.ssh_user,
197
+ port=args.ssh_port,
198
+ identity_file=args.ssh_identity,
199
+ ),
200
+ )
201
+ return ObsidianVault(self._vault_locator.resolve(args.vault))
202
+
178
203
  @staticmethod
179
204
  def _read_content_arg(content: str | None, use_stdin: bool) -> str:
180
205
  if content is not None and use_stdin:
@@ -1,10 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
- from obsidian_cli.vault import ObsidianVault, VaultError
3
+ from obsidian_cli.vault import VaultBackend, VaultError
4
4
 
5
5
 
6
6
  class CommandRunner:
7
- def __init__(self, vault: ObsidianVault) -> None:
7
+ def __init__(self, vault: VaultBackend) -> None:
8
8
  self._vault = vault
9
9
 
10
10
  def check(self) -> int:
@@ -18,7 +18,7 @@ class VaultLocator:
18
18
  def __init__(
19
19
  self, env: dict[str, str] | None = None, home: Path | None = None
20
20
  ) -> None:
21
- self._env = dict(env or os.environ)
21
+ self._env = dict(os.environ if env is None else env)
22
22
  self._home = (home or Path.home()).expanduser()
23
23
 
24
24
  def resolve(self, cli_value: str | None) -> Path:
@@ -38,6 +38,19 @@ class VaultLocator:
38
38
  "vault path is required; use --vault, OBSIDIAN_VAULT, or place your vault in a default Obsidian location"
39
39
  )
40
40
 
41
+ def resolve_configured(self, cli_value: str | None) -> Path:
42
+ cli_path = self._path_from_string(cli_value)
43
+ if cli_path is not None:
44
+ return cli_path
45
+
46
+ env_path = self._path_from_string(self._env.get("OBSIDIAN_VAULT"))
47
+ if env_path is not None:
48
+ return env_path
49
+
50
+ raise VaultError(
51
+ "vault path is required for SSH mode; use --vault or OBSIDIAN_VAULT"
52
+ )
53
+
41
54
  def discover_default_vault(self) -> VaultCandidate | None:
42
55
  config_candidate = self._discover_from_obsidian_config()
43
56
  if config_candidate is not None:
@@ -4,7 +4,12 @@ from dataclasses import dataclass
4
4
  from pathlib import Path
5
5
 
6
6
 
7
- SKILL_BODY = """# odcli
7
+ SKILL_BODY = """---
8
+ name: "odcli"
9
+ description: "Use odcli to read, write, append, search, and patch notes inside a local Obsidian vault."
10
+ ---
11
+
12
+ # odcli
8
13
 
9
14
  Use `odcli` when you need to read or write notes inside a local Obsidian vault.
10
15