odcli 0.1.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: odcli
3
- Version: 0.1.3
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
@@ -35,6 +35,7 @@ It works directly on Markdown files inside the vault, so it does not depend on p
35
35
  - Append content to a note
36
36
  - Full-text search across the vault
37
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
38
39
 
39
40
  ## Using uv
40
41
 
@@ -189,6 +190,24 @@ Arguments:
189
190
  - `query`
190
191
  - `--case-sensitive`
191
192
 
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
+
192
211
  ## Testing
193
212
 
194
213
  ```bash
@@ -14,6 +14,7 @@ It works directly on Markdown files inside the vault, so it does not depend on p
14
14
  - Append content to a note
15
15
  - Full-text search across the vault
16
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
17
18
 
18
19
  ## Using uv
19
20
 
@@ -168,6 +169,24 @@ Arguments:
168
169
  - `query`
169
170
  - `--case-sensitive`
170
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
+
171
190
  ## Testing
172
191
 
173
192
  ```bash
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "odcli"
7
- version = "0.1.3"
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"
@@ -1,3 +1,3 @@
1
1
  __all__ = ["__version__"]
2
2
 
3
- __version__ = "0.1.3"
3
+ __version__ = "0.1.4"
@@ -3,3 +3,4 @@ from obsidian_cli.cli import main
3
3
 
4
4
  if __name__ == "__main__":
5
5
  raise SystemExit(main())
6
+
@@ -5,18 +5,23 @@ import sys
5
5
 
6
6
  from obsidian_cli.commands import CommandRunner
7
7
  from obsidian_cli.discovery import VaultLocator
8
+ from obsidian_cli.plugins import SkillInstaller
8
9
  from obsidian_cli.vault import ObsidianVault, VaultError
9
10
 
10
11
 
11
12
  class ObsidianCLI:
12
13
  def __init__(self, vault_locator: VaultLocator | None = None) -> None:
13
14
  self._vault_locator = vault_locator or VaultLocator()
15
+ self._skill_installer = SkillInstaller()
14
16
  self._parser = self._build_parser()
15
17
 
16
18
  def run(self, argv: list[str] | None = None) -> int:
17
19
  args = self._parser.parse_args(argv)
18
20
 
19
21
  try:
22
+ if args.command == "plugin":
23
+ return self._run_plugin_command(args)
24
+
20
25
  runner = CommandRunner(
21
26
  ObsidianVault(self._vault_locator.resolve(args.vault))
22
27
  )
@@ -145,8 +150,31 @@ class ObsidianCLI:
145
150
  help="Use case-sensitive matching.",
146
151
  )
147
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
+
148
168
  return parser
149
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
+
150
178
  @staticmethod
151
179
  def _read_content_arg(content: str | None, use_stdin: bool) -> str:
152
180
  if content is not None and use_stdin:
@@ -0,0 +1,84 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+
6
+
7
+ SKILL_BODY = """# odcli
8
+
9
+ Use `odcli` when you need to read or write notes inside a local Obsidian vault.
10
+
11
+ ## What this skill does
12
+
13
+ - Reads notes from a local Obsidian vault
14
+ - Writes or appends Markdown notes
15
+ - Replaces specific line ranges in a note
16
+ - Searches across Markdown notes in the vault
17
+ - Works with either `odcli` or `obsidian-cli`
18
+
19
+ ## Preferred workflow
20
+
21
+ 1. Check whether `OBSIDIAN_VAULT` is set.
22
+ 2. If it is not set, let `odcli` auto-discover the vault from local Obsidian config or common default locations.
23
+ 3. Use `odcli` for direct note operations instead of editing vault files manually.
24
+
25
+ ## Common commands
26
+
27
+ ```bash
28
+ odcli check
29
+ odcli list
30
+ odcli read Inbox/today.md
31
+ odcli read-lines Inbox/today.md 3 8
32
+ odcli write Inbox/today.md --content "# Today"
33
+ odcli write-lines Inbox/today.md 3 4 --content "- replaced\\n- lines\\n"
34
+ odcli append Inbox/today.md --content "\\n- new item"
35
+ odcli search "project alpha"
36
+ ```
37
+
38
+ ## Notes
39
+
40
+ - `--vault /path/to/vault` overrides auto-discovery.
41
+ - `OBSIDIAN_VAULT` is used when set.
42
+ - Line numbers for `read-lines` and `write-lines` are 1-based and inclusive.
43
+ """
44
+
45
+
46
+ @dataclass(frozen=True, slots=True)
47
+ class InstallResult:
48
+ target: str
49
+ path: Path
50
+
51
+
52
+ class SkillInstaller:
53
+ def __init__(self, home: Path | None = None) -> None:
54
+ self._home = (home or Path.home()).expanduser()
55
+
56
+ def install(self, target: str) -> list[InstallResult]:
57
+ if target == "codex-skill":
58
+ return [
59
+ self._install_skill(
60
+ "codex-skill", self._home / ".codex" / "skills" / "odcli"
61
+ )
62
+ ]
63
+ if target == "claude-skill":
64
+ return [
65
+ self._install_skill(
66
+ "claude-skill", self._home / ".claude" / "skills" / "odcli"
67
+ )
68
+ ]
69
+ if target == "all-skills":
70
+ return [
71
+ self._install_skill(
72
+ "codex-skill", self._home / ".codex" / "skills" / "odcli"
73
+ ),
74
+ self._install_skill(
75
+ "claude-skill", self._home / ".claude" / "skills" / "odcli"
76
+ ),
77
+ ]
78
+ raise ValueError(f"unsupported plugin install target: {target}")
79
+
80
+ def _install_skill(self, target: str, skill_dir: Path) -> InstallResult:
81
+ skill_dir.mkdir(parents=True, exist_ok=True)
82
+ skill_file = skill_dir / "SKILL.md"
83
+ skill_file.write_text(SKILL_BODY, encoding="utf-8")
84
+ return InstallResult(target=target, path=skill_file)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: odcli
3
- Version: 0.1.3
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
@@ -35,6 +35,7 @@ It works directly on Markdown files inside the vault, so it does not depend on p
35
35
  - Append content to a note
36
36
  - Full-text search across the vault
37
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
38
39
 
39
40
  ## Using uv
40
41
 
@@ -189,6 +190,24 @@ Arguments:
189
190
  - `query`
190
191
  - `--case-sensitive`
191
192
 
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
+
192
211
  ## Testing
193
212
 
194
213
  ```bash
@@ -5,6 +5,7 @@ src/obsidian_cli/__main__.py
5
5
  src/obsidian_cli/cli.py
6
6
  src/obsidian_cli/commands.py
7
7
  src/obsidian_cli/discovery.py
8
+ src/obsidian_cli/plugins.py
8
9
  src/obsidian_cli/vault.py
9
10
  src/odcli.egg-info/PKG-INFO
10
11
  src/odcli.egg-info/SOURCES.txt
@@ -12,6 +12,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
12
12
 
13
13
  from obsidian_cli.cli import ObsidianCLI, main
14
14
  from obsidian_cli.discovery import VaultCandidate, VaultLocator
15
+ from obsidian_cli.plugins import SkillInstaller
15
16
  from obsidian_cli.vault import ObsidianVault, VaultError
16
17
 
17
18
 
@@ -132,6 +133,33 @@ class VaultTests(unittest.TestCase):
132
133
  exit_code = cli.run(["check"])
133
134
  self.assertEqual(exit_code, 0)
134
135
 
136
+ def test_skill_installer_installs_codex_skill(self) -> None:
137
+ installer = SkillInstaller(home=self.vault_root)
138
+ results = installer.install("codex-skill")
139
+ self.assertEqual(len(results), 1)
140
+ self.assertTrue(
141
+ (self.vault_root / ".codex" / "skills" / "odcli" / "SKILL.md").is_file()
142
+ )
143
+
144
+ def test_skill_installer_installs_claude_skill(self) -> None:
145
+ installer = SkillInstaller(home=self.vault_root)
146
+ results = installer.install("claude-skill")
147
+ self.assertEqual(len(results), 1)
148
+ self.assertTrue(
149
+ (self.vault_root / ".claude" / "skills" / "odcli" / "SKILL.md").is_file()
150
+ )
151
+
152
+ def test_cli_plugin_install_all_skills(self) -> None:
153
+ cli = ObsidianCLI(vault_locator=VaultLocator(env={}, home=self.vault_root))
154
+ cli._skill_installer = SkillInstaller(home=self.vault_root)
155
+ stdout = StringIO()
156
+ with redirect_stdout(stdout), redirect_stderr(StringIO()):
157
+ exit_code = cli.run(["plugin", "install", "all-skills"])
158
+ self.assertEqual(exit_code, 0)
159
+ output = stdout.getvalue()
160
+ self.assertIn("codex-skill:", output)
161
+ self.assertIn("claude-skill:", output)
162
+
135
163
 
136
164
  if __name__ == "__main__":
137
165
  unittest.main()
File without changes
File without changes