docent-cli 1.0.0__py3-none-any.whl

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.
docent/utils/paths.py ADDED
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ _ROOT_ENV = "DOCENT_HOME"
6
+
7
+
8
+ def root_dir() -> Path:
9
+ import os
10
+
11
+ override = os.environ.get(_ROOT_ENV)
12
+ return Path(override).expanduser() if override else Path.home() / ".docent"
13
+
14
+
15
+ def config_dir() -> Path:
16
+ return root_dir()
17
+
18
+
19
+ def config_file() -> Path:
20
+ return config_dir() / "config.toml"
21
+
22
+
23
+ def cache_dir() -> Path:
24
+ return root_dir() / "cache"
25
+
26
+
27
+ def logs_dir() -> Path:
28
+ return root_dir() / "logs"
29
+
30
+
31
+ def data_dir() -> Path:
32
+ return root_dir() / "data"
33
+
34
+
35
+ def plugins_dir() -> Path:
36
+ return root_dir() / "plugins"
docent/utils/prompt.py ADDED
@@ -0,0 +1,63 @@
1
+ """Minimal interactive prompt utilities.
2
+
3
+ Single function for now: `prompt_for_path`. The escape-hatch convention is the
4
+ `DOCENT_NO_INTERACTIVE` env var - when set to anything truthy, prompts raise
5
+ `NoInteractiveError` immediately so CI / scripted use never blocks waiting on
6
+ stdin. Tools that prompt should always have an env-var or flag-based path that
7
+ bypasses the prompt entirely.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import os
12
+ from pathlib import Path
13
+
14
+ from rich.prompt import Prompt
15
+
16
+ from docent.ui import get_console
17
+
18
+
19
+ _NO_INTERACTIVE_ENV = "DOCENT_NO_INTERACTIVE"
20
+
21
+
22
+ class NoInteractiveError(RuntimeError):
23
+ """Raised when an interactive prompt is required but DOCENT_NO_INTERACTIVE is set.
24
+
25
+ Carries the prompt text so callers can format a clear "set X env var or
26
+ pass --flag" remediation hint.
27
+ """
28
+
29
+ def __init__(self, prompt_text: str):
30
+ super().__init__(
31
+ f"Interactive prompt required but {_NO_INTERACTIVE_ENV} is set: {prompt_text!r}"
32
+ )
33
+ self.prompt_text = prompt_text
34
+
35
+
36
+ def _no_interactive() -> bool:
37
+ val = os.environ.get(_NO_INTERACTIVE_ENV, "").strip().lower()
38
+ return val not in ("", "0", "false", "no")
39
+
40
+
41
+ def prompt_for_path(message: str, *, allow_create: bool = True, default: str | None = None) -> Path | None:
42
+ """Ask the user for a directory path, with `~` expansion.
43
+
44
+ Returns the resolved Path on success, or None if the user types 'cancel'.
45
+ If `allow_create` is True, the user can type 'create' to scaffold the
46
+ default location.
47
+ Raises `NoInteractiveError` if `DOCENT_NO_INTERACTIVE` is set.
48
+ """
49
+ if _no_interactive():
50
+ raise NoInteractiveError(message)
51
+
52
+ console = get_console()
53
+ raw = Prompt.ask(message, default=default or "", console=console).strip()
54
+ # Windows users reflexively wrap paths with spaces in quotes when pasting.
55
+ if len(raw) >= 2 and raw[0] == raw[-1] and raw[0] in ('"', "'"):
56
+ raw = raw[1:-1].strip()
57
+ if not raw or raw.lower() == "cancel":
58
+ return None
59
+ if allow_create and raw.lower() == "create" and default:
60
+ path = Path(default).expanduser()
61
+ path.mkdir(parents=True, exist_ok=True)
62
+ return path
63
+ return Path(raw).expanduser()
@@ -0,0 +1,174 @@
1
+ Metadata-Version: 2.3
2
+ Name: docent-cli
3
+ Version: 1.0.0
4
+ Summary: A personal control center for grad school workflows.
5
+ Keywords: cli,research,reading-queue,mendeley,mcp
6
+ Author: John David K. T. Kudadjie
7
+ Author-email: John David K. T. Kudadjie <75898705+Kudadjie@users.noreply.github.com>
8
+ License: MIT
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Utilities
16
+ Requires-Dist: litellm>=1.83.0
17
+ Requires-Dist: mcp>=1.0,<2
18
+ Requires-Dist: pydantic-settings>=2.14.0
19
+ Requires-Dist: rich>=15.0.0
20
+ Requires-Dist: tomli-w>=1.2.0
21
+ Requires-Dist: typer>=0.24.2
22
+ Requires-Python: >=3.11
23
+ Project-URL: Homepage, https://github.com/Kudadjie/docent
24
+ Project-URL: Repository, https://github.com/Kudadjie/docent
25
+ Project-URL: Bug Tracker, https://github.com/Kudadjie/docent/issues
26
+ Description-Content-Type: text/markdown
27
+
28
+ # Docent
29
+
30
+ A personal CLI control center for grad-school workflows — papers, research, writing tools, subprocess wrappers — all behind a single `docent <tool>` command. Built so a web dashboard can wrap the same tool registry later without rewriting anything.
31
+
32
+ > **Built with AI, Architected by a Human:** Much of the codebase for Docent was written by Claude Code (Opus 4.7) and OpenCode Go subscription models (Kimi K2.6, DeepSeek V4 Pro, Qwen 3.5 Plus, MiniMax M2.7), but the architecture was strictly human-directed — designed, planned, tested, and iterated over many sessions.
33
+
34
+ ## What works today
35
+
36
+ - `docent --version`, `docent --help`, `docent list`, `docent info <tool>`
37
+ - **Tool contract — two shapes:**
38
+ - *Single-action*: subclass `Tool`, set `input_schema`, override `run()`. CLI: `docent <tool> --flag ...`
39
+ - *Multi-action*: decorate methods with `@action(...)`. CLI: `docent <tool> <action> --flag ...`
40
+ - A tool is one or the other — registry enforces mutual exclusivity at import time.
41
+ - **Auto-discovery**: drop a file in `src/docent/tools/`, decorate with `@register_tool`, and Typer commands generate at startup from the Pydantic input schema. No CLI edits.
42
+ - **Context plumbing**: `context.settings` (Pydantic + `~/.docent/config.toml` + env overrides), `context.llm` (lazy litellm wrapper), `context.executor` (list-args subprocess, no shell-injection surface).
43
+ - **`docent.learning.RunLog`**: per-namespace JSONL run-log with cap-and-roll, for tools that want a "what did I do recently" history (used by `paper`'s mutators).
44
+ - **`reading` tool**: reading queue CRUD (`next / show / search / stats / remove / edit / done / start / export`); `add` (guidance mode — ingestion goes through Mendeley); `sync-from-mendeley` (reconciles the configured Mendeley collection into the local queue, overlays fresh metadata on display); `sync-pull` (Unpaywall OA download); `sync-status`; `move-up / move-down / move-to`; deadline notifications at startup; `config-show / config-set`; `queue-clear`. Mendeley is the source of truth for title/authors/year/doi — the reading tool is a thin workflow layer on top.
45
+ - **`ReadingQueueStore`**: persistence seam in `reading_store.py`. Actions mutate queue state through the store, never by reaching into JSON directly.
46
+ - **`MendeleyCache`**: read-through file-backed cache (5-min TTL) used by `next / show / search` to overlay live Mendeley metadata. Degrades gracefully to queue snapshot on auth/transport failure.
47
+ - Themed Rich console singleton; tools never touch it directly (they return typed data; CLI renders).
48
+
49
+ ## Install
50
+
51
+ Requires [uv](https://docs.astral.sh/uv/) and Python 3.11+.
52
+
53
+ ```bash
54
+ uv sync
55
+ uv run docent --version
56
+ ```
57
+
58
+ For a global install once you want `docent` on your PATH:
59
+
60
+ ```bash
61
+ uv tool install .
62
+ docent --version
63
+ ```
64
+
65
+ ## Architecture
66
+
67
+ See [`Docent_Architecture.md`](Docent_Architecture.md) for the full design. The short version:
68
+
69
+ - **Tool registry** — tools self-register via `@register_tool` at import time. Registry stores the class, not an instance, so nothing runs until the tool is actually invoked.
70
+ - **Context object** — frozen dataclass passed to every tool. Provides `settings`, `llm` (lazy litellm), and `executor` (subprocess wrapper).
71
+ - **UI / logic boundary** — tools return typed Pydantic data. They never import `docent.ui` and never touch Rich. The CLI renders; the future dashboard will serialize the same data to JSON.
72
+ - **Plugin system** — drop a file in `src/docent/tools/`, decorate with `@register_tool`, and Typer commands generate at startup. No CLI edits needed.
73
+
74
+ ## Tools
75
+
76
+ ### `reading` — Reading Queue
77
+
78
+ Manages your academic reading queue and syncs with Mendeley.
79
+
80
+ **Workflow:** Drop a PDF in your `database_dir` → Mendeley auto-imports it → drag it into your `Docent-Queue` collection in Mendeley → run `docent reading sync-from-mendeley`. The category of each entry is automatically detected from Mendeley sub-collections (e.g. a paper in `Docent-Queue/TestCourse701/ParticularTopic` gets `category="TestCourse701/ParticularTopic"`).
81
+
82
+ **Queue management**
83
+
84
+ | Command | Description |
85
+ |---|---|
86
+ | `docent reading next` | Show the next paper to read (lowest order number) |
87
+ | `docent reading next --course-name CES701` | Next paper for a specific course |
88
+ | `docent reading show <id>` | Show full details for one entry |
89
+ | `docent reading search <query>` | Search by title, authors, notes, tags, or id |
90
+ | `docent reading stats` | Counts by status, category, and course |
91
+ | `docent reading export` | Export queue as JSON (default) or Markdown table |
92
+ | `docent reading export --format markdown --status queued` | Filtered export, sorted by reading order |
93
+
94
+ **Status transitions**
95
+
96
+ | Command | Description |
97
+ |---|---|
98
+ | `docent reading start <id>` | Mark as currently reading (stamps `started` timestamp) |
99
+ | `docent reading done <id>` | Mark as finished (stamps `finished` timestamp) |
100
+ | `docent reading remove <id>` | Remove entry from queue |
101
+
102
+ **Editing**
103
+
104
+ | Command | Description |
105
+ |---|---|
106
+ | `docent reading edit <id> --order 1` | Set reading priority (1 = read first) |
107
+ | `docent reading set-deadline <id> --deadline 2026-06-15` | Set a reading deadline |
108
+ | `docent reading set-deadline <id> --deadline ''` | Clear a deadline |
109
+ | `docent reading edit <id> --notes "Key paper for lit review"` | Add notes |
110
+ | `docent reading edit <id> --tags tag1 tag2` | Set tags |
111
+ | `docent reading edit <id> --type book_chapter` | Set entry type (paper / book / book_chapter) |
112
+ | `docent reading move-up <id>` | Move one position earlier |
113
+ | `docent reading move-down <id>` | Move one position later |
114
+ | `docent reading move-to <id> --position 3` | Move to a specific position |
115
+
116
+ **Deadlines:** Set via `set-deadline --deadline YYYY-MM-DD`. Docent prints a startup warning for entries due within 3 days or overdue — once per calendar day.
117
+
118
+ **Entry types:** Automatically detected from Mendeley document type on sync (`journal_article` → paper, `book` → book, `book_section` → book chapter). Override with `edit --type book_chapter`.
119
+
120
+ **Mendeley sync**
121
+
122
+ | Command | Description |
123
+ |---|---|
124
+ | `docent reading sync-from-mendeley` | Pull from your Mendeley Docent-Queue collection |
125
+ | `docent reading sync-from-mendeley --dry-run` | Preview changes without writing |
126
+ | `docent reading sync-status` | Report queue size and PDFs in database_dir |
127
+
128
+ **Configuration**
129
+
130
+ | Command | Description |
131
+ |---|---|
132
+ | `docent reading config-show` | Show current reading settings |
133
+ | `docent reading config-set database_dir ~/path/to/Papers` | Set the PDF database folder |
134
+ | `docent reading config-set queue_collection "Docent-Queue"` | Set the Mendeley collection name |
135
+
136
+ **Other**
137
+
138
+ | Command | Description |
139
+ |---|---|
140
+ | `docent reading queue-clear --yes` | Wipe the entire queue (irreversible) |
141
+
142
+ ## Adding a tool
143
+
144
+ Single-action tools are the simplest shape:
145
+
146
+ ```python
147
+ # src/docent/tools/echo.py
148
+ from pydantic import BaseModel, Field
149
+ from docent.core import Context, Tool, register_tool
150
+
151
+
152
+ class EchoInputs(BaseModel):
153
+ msg: str = Field(..., description="Message to echo.")
154
+ count: int = Field(1, description="Times to repeat.")
155
+
156
+
157
+ @register_tool
158
+ class Echo(Tool):
159
+ name = "echo"
160
+ description = "Repeat a message N times."
161
+ category = "demo"
162
+ input_schema = EchoInputs
163
+
164
+ def run(self, inputs: EchoInputs, context: Context) -> str:
165
+ return (inputs.msg + " ") * inputs.count
166
+ ```
167
+
168
+ Then `docent echo --msg hi --count 3` just works. No CLI edits, no registration code — the decorator is enough.
169
+
170
+ For tools with several related operations on shared state (a reading queue, a research notebook, a browser session), use the multi-action shape — decorate methods with `@action(...)` instead of overriding `run()`. Each action gets its own Pydantic input schema and becomes `docent <tool> <action> --flag ...`. See `src/docent/tools/reading.py` for the reference implementation.
171
+
172
+ ## Why
173
+
174
+ I have a pile of Claude Code skills I actually use (research-to-notebook, paper-pipeline, feynman wrappers, literature-review, etc.) but they only work inside a Claude session. Docent is the terminal-first home for the same workflows — scriptable, pipeable, cron-able, and eventually a dashboard. MCP is not a replacement; Docent can later expose itself *through* MCP, but that's a late-stage adapter, not the core.
@@ -0,0 +1,35 @@
1
+ docent/__init__.py,sha256=kUR5RAFc7HCeiqdlX36dZOHkUI5wI6V_43RpEcD8b-0,22
2
+ docent/bundled_plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ docent/bundled_plugins/reading/__init__.py,sha256=R8a5FCFNmFsMeoylRJWr9-xelI5gRtZlqM0m-OBTxg4,52063
4
+ docent/bundled_plugins/reading/mendeley_cache.py,sha256=x8ROHcwXrZNvAQVHM8Btw1clRCNURfGNdzlco9Q68xY,6943
5
+ docent/bundled_plugins/reading/mendeley_client.py,sha256=d9nwWZJEOnk4D3M0YELqi1CMZYK8G_Hqr4WuWzZExt8,5115
6
+ docent/bundled_plugins/reading/reading_notify.py,sha256=0U8e6uHtciO03A9W5FG28w8sTp7maQ7VEjUfAZMGEvs,2742
7
+ docent/bundled_plugins/reading/reading_store.py,sha256=W-Lk_uMJjsUk2OHAD1vnqTWwbsUuK1DoOIXRyPxuqOA,3487
8
+ docent/cli.py,sha256=Xv09cDfrgoF07WBw1WAhiuGgnVzx-144i32Motv9hCM,10264
9
+ docent/config/__init__.py,sha256=T6T1Ih6Sixm5TclvD1l0Gxl03BgMPttmq6kBmn6nIAc,200
10
+ docent/config/loader.py,sha256=oyibLSWHAMSYbLEpeee-qNK1xyB3d7cdAXVLtF7WLpA,2215
11
+ docent/config/settings.py,sha256=n_QOHOsVuK627zN8dNIdZw4nLXgWkn1RPsy_GeYUtjo,1800
12
+ docent/core/__init__.py,sha256=Fck8Va27z3IH_QHf2IM1H4cbZc2EkErC_hEm0lVjEJo,503
13
+ docent/core/context.py,sha256=SU-gUI_dtUOy-NmvocjyRtGo86kKT31rLq-WtVf0X60,283
14
+ docent/core/events.py,sha256=siue9SjP5_zISiPgqxFISrglNxMFpB_oESyHAVeehlk,1341
15
+ docent/core/plugin_loader.py,sha256=THcTl63XDYaKjqrMxNgdPN10_jjr_RGR2N8heJdirTY,3093
16
+ docent/core/registry.py,sha256=4Y9sAEpqz44hBiY92NujppQHPFx95yXy172yijo9vUg,3224
17
+ docent/core/tool.py,sha256=DdN6TmfDluf402Do-po8MormXjU83aAZq8IeQC54Lbk,2889
18
+ docent/execution/__init__.py,sha256=5k7V2CJUj7HBraqqX8Igecn3PWxHYMFa6c-SFuEZBz4,151
19
+ docent/execution/executor.py,sha256=EA8ffyGuFcUFeo5ca-NKahCFeKvr0WsOnpr7K5cWZeY,1824
20
+ docent/learning/__init__.py,sha256=t2Jd6wNf3-kQJ6tzRPtJFI63d9nxNhhgYZonwGWqlAI,65
21
+ docent/learning/run_log.py,sha256=A5JTSGTp9VbaesQ0CpEWX0T4L7-pqbrF3VnOreKUNGM,2479
22
+ docent/llm/__init__.py,sha256=I7267-g94nMCUqT1J7mRK1mVTJvIGJJ6mlr2_NZkVKQ,93
23
+ docent/llm/client.py,sha256=O2wdGVAYNvW1PzSiHRAnuRHok1gHS_5TiuAOj1cVemI,1845
24
+ docent/mcp_server.py,sha256=MvNH7FD8RUmBNBv9OJtSPsgzUP8NU8UyRZcBazSdBI4,6141
25
+ docent/tools/__init__.py,sha256=y3OIGjYq6aazeWsyqFV04xYvH8rXPlotVO8-ZR9hRgM,516
26
+ docent/ui/__init__.py,sha256=yhiKUHzkiXZiZAP0NjfoWaS7tdNQ6lk4JXUO_KCj-Fg,109
27
+ docent/ui/console.py,sha256=1-LYrArKwnMQ5kNYAhd9T55rGODGusQUf3b-iH5LS_k,663
28
+ docent/ui/theme.py,sha256=UrAsK3ltEicQW7pZ2SMVTC4wAxl15Pgp1X1YHRV5Ei8,338
29
+ docent/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
30
+ docent/utils/paths.py,sha256=-4JW3k7Er8uj2auM6b-vkYiESmlc37E61JmPlYykZhY,605
31
+ docent/utils/prompt.py,sha256=5uWS3WYOU-fS8X90D5uZTrYd0jrHjetTntPbsJepRFg,2226
32
+ docent_cli-1.0.0.dist-info/WHEEL,sha256=iCTolw4aw2dP3yfM-EQCGTDsFCXL_ymmbYnBRVH7plA,81
33
+ docent_cli-1.0.0.dist-info/entry_points.txt,sha256=BXFGTBbAdk3EyaVPnfyMYwXUc4NKcRN3KzM1Ad99a6M,43
34
+ docent_cli-1.0.0.dist-info/METADATA,sha256=GF1ifzlJx8RUvOHuV_FXSJfyU2sHMfNCmkL4Zg7Vae8,9404
35
+ docent_cli-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.11.11
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ docent = docent.cli:app
3
+