dotagents-cli 0.3.1__tar.gz → 0.3.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.
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/.gitignore +4 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/CHANGELOG.md +133 -2
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/PKG-INFO +3 -3
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/README.md +1 -1
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/build.py +3 -3
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/api/reference.md +0 -1
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/guide/commands.md +14 -2
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/install.py +2 -2
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/mkdocs.yml +0 -1
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/pyproject.toml +1 -1
- dotagents_cli-0.3.3/src/dotagents/AGENTS.md +220 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/__init__.py +1 -1
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/_agents.py +389 -14
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/_context.py +13 -6
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/_hooks.py +22 -4
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/_merge.py +6 -2
- dotagents_cli-0.3.3/src/dotagents/_overlay/dotagents/hooks/preinvocation_antigravity_context.py +123 -0
- dotagents_cli-0.3.3/src/dotagents/_overlay/dotagents/hooks/pretooluse_codex_env.py +90 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/_scope.py +1 -1
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/cli/__init__.py +26 -19
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/cli/_common.py +74 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/cli/build_pyz.py +37 -14
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/cli/context.py +33 -7
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/cli/env.py +192 -8
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/cli/init.py +3 -15
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/cli/overlays.py +11 -43
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/tests/test_agents.py +19 -1
- dotagents_cli-0.3.3/tests/test_build_pyz.py +69 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/tests/test_context.py +72 -4
- dotagents_cli-0.3.3/tests/test_env_cli_scope.py +263 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/tests/test_env_format.py +149 -11
- dotagents_cli-0.3.3/tests/test_wire_hooks.py +887 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/tools/audit.py +7 -5
- dotagents_cli-0.3.1/docs/api/sync.md +0 -3
- dotagents_cli-0.3.1/src/dotagents/AGENTS.md +0 -125
- dotagents_cli-0.3.1/src/dotagents/_sync.py +0 -72
- dotagents_cli-0.3.1/tests/test_wire_hooks.py +0 -439
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/LICENSE +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/api/agents.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/api/cli.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/api/context.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/api/env.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/api/hooks.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/api/merge.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/api/overlays.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/api/resolve.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/api/scope.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/api/skills.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/changelog.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/guide/authoring.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/guide/install.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/guide/overlays.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/guide/private-sync.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/docs/index.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/__main__.py +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/_env.py +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/_overlay/AGENTS.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/_overlay/CLAUDE.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/_overlay/README.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/_overlay/dotagents/DECISIONS.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/_overlay/dotagents/cmds/README.md +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/_overlays.py +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/_resolve.py +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/_skills.py +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/src/dotagents/_wrappers.py +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/tests/test_audit_leak.py +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/tests/test_cmds_discovery.py +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/tests/test_env.py +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/tests/test_hooks.py +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/tests/test_overlays_command.py +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/tests/test_scope.py +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/tests/test_wrappers.py +0 -0
- {dotagents_cli-0.3.1 → dotagents_cli-0.3.3}/tools/cloud-setup.sh +0 -0
|
@@ -7,6 +7,139 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.3] - 2026-08-16
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- feat: `env` and `context` gained **`--agents-dir`** (from the shared
|
|
15
|
+
`DotAgentsArgs` base) to override the store for one run. Their `-g/--global`
|
|
16
|
+
keeps its existing, narrower meaning here — *skip the project-level files* —
|
|
17
|
+
and now says so in `--help`.
|
|
18
|
+
|
|
19
|
+
### Removed
|
|
20
|
+
|
|
21
|
+
- **`build-pyz` no longer bundles the repo's `tools/`** as `dotagents/_tools`
|
|
22
|
+
inside the built `.pyz`, and the `--tools-dir` flag is gone with it. Nothing
|
|
23
|
+
read `_tools`: the compiled `audit`/`leak-check` wrappers that shelled out to
|
|
24
|
+
it no longer exist. Every shipped artifact carried the dead weight while
|
|
25
|
+
`tools/audit.py` claimed it was "not shipped in the `.pyz`" — now true.
|
|
26
|
+
- **`dotagents._sync`** — a `pathlib_next.PathSyncer` wrapper that existed only
|
|
27
|
+
to back the `install` subcommand's backup/copy report. `install` was removed
|
|
28
|
+
in 0.3.x; the module has had no callers since, no tests, and a return
|
|
29
|
+
annotation that disagreed with what it returned. `pathlib_next` remains a
|
|
30
|
+
dependency (duho, and `--from` URI support). Its API-reference page went with
|
|
31
|
+
it.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- docs: a cluster of "docs say X, tree does Y" corrections — `tools/audit.py`
|
|
36
|
+
no longer claims to ship as a bundled `audit` command module (it is repo CI
|
|
37
|
+
tooling and there is no `dotagents audit`) and its `--root` help names the
|
|
38
|
+
real default; `install.py`'s usage line drops the removed `install` and the
|
|
39
|
+
never-existing `audit`; the README's `tools/` row says where `leak-check`
|
|
40
|
+
actually lives; the API header names the real `_overlays` exports
|
|
41
|
+
(`install_overlay_dir` / `apply_overlay` / `run_overlay_setup`) and the real
|
|
42
|
+
package-data dirs (`_overlay` / `_overlays_src`, never a `skeleton/`); and
|
|
43
|
+
`_merge._extract_block`'s docstring now says it returns the block *including*
|
|
44
|
+
its marker lines, which is what it has always done and what callers rely on.
|
|
45
|
+
- fix: **`stamp_identity` no longer pretends to emit `AGENTS_AGENT`.** The line
|
|
46
|
+
sourced the value from `$AGENTS_AGENT` and only assigned when that same key
|
|
47
|
+
was unset, so it could never emit anything — while the docstring and the
|
|
48
|
+
shipped API header both advertised the var. Line removed; the API header's
|
|
49
|
+
"emitted by the identity/env layer" list now drops both `AGENTS_AGENT` and
|
|
50
|
+
`AGENTS_CODE_SESSION_ID` (the latter was deliberately never emitted) and says
|
|
51
|
+
so explicitly, so nothing branches on a var that never arrives.
|
|
52
|
+
- fix: **`dotagents env` and `dotagents context` now resolve their roots instead
|
|
53
|
+
of hardcoding them.** Both took `Path.cwd()` as the project root and
|
|
54
|
+
`~/.agents` as the user store, so `$AGENTS_PROJECT_ROOT` (or the agent-native
|
|
55
|
+
`$CLAUDE_PROJECT_DIR`) and `$AGENTS_HOME` were ignored by the two commands
|
|
56
|
+
that most needed them — a `SessionStart` hook runs `dotagents context` from
|
|
57
|
+
wherever the session happens to start, so a pinned project root was silently
|
|
58
|
+
dropped and a relocated store was never read. They now use
|
|
59
|
+
`_scope.project_root_default()` and the new
|
|
60
|
+
`dotagents.cli.resolve_user_store()` (`--agents-dir` → `$AGENTS_HOME` →
|
|
61
|
+
legacy `$DOTAGENTS_AGENTS_DIR` → `~/.agents`), matching what the package's own
|
|
62
|
+
`resolve_scope` docstring and command discovery already promised. Behavior is
|
|
63
|
+
unchanged when none of the vars are set.
|
|
64
|
+
|
|
65
|
+
## [0.3.2] - 2026-07-25
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- feat: **`AntigravityAgent`** — context injection for Google's Antigravity
|
|
70
|
+
CLI/IDE (a separate product from Gemini CLI, despite sharing the `~/.gemini/`
|
|
71
|
+
namespace for some files). Antigravity's hooks have no `SessionStart`
|
|
72
|
+
equivalent — only `PreToolUse`/`PostToolUse`/`PreInvocation`/`PostInvocation`/
|
|
73
|
+
`Stop` — so a `PreInvocation` hook gated on `invocationNum == 0` behaves like a
|
|
74
|
+
one-shot context load instead of resending it every model turn. Wires into
|
|
75
|
+
`~/.gemini/config/hooks.json`. No detection marker exists for Antigravity, so
|
|
76
|
+
it's explicit-`--agents antigravity`-only, never auto-detected.
|
|
77
|
+
- feat: **Codex gets a `PreToolUse` env hook**, closing the one gap Codex had
|
|
78
|
+
versus Claude: Codex has no per-session env-persistence mechanism at any hook
|
|
79
|
+
event, so a deployed script prepends a guarded env-loader to every `Bash` tool
|
|
80
|
+
call via `updatedInput.command` — the same rewrite mechanism Claude's own
|
|
81
|
+
`PreToolUse` hook uses, confirmed directly against Codex's docs.
|
|
82
|
+
- feat: **`init` wires a PowerShell `PreToolUse` env hook on Windows**, closing
|
|
83
|
+
a real gap: `$CLAUDE_ENV_FILE` only reaches Claude's *Bash* tool
|
|
84
|
+
(`$env:CLAUDE_ENV_FILE` is empty inside a live PowerShell tool call, confirmed
|
|
85
|
+
directly) — a fresh PowerShell tool call gets none of the SessionStart env.
|
|
86
|
+
The hook prepends a guarded env-loader to a PowerShell tool call's own command,
|
|
87
|
+
shipped as an inline command string (never a `.ps1` file — a script file is
|
|
88
|
+
subject to PowerShell's execution policy, and dotagents has no code-signing
|
|
89
|
+
certificate; the inline form runs even under `Restricted`).
|
|
90
|
+
- feat: **`SessionStart`/`CwdChanged` now register two handlers each** — a
|
|
91
|
+
bash-syntax one and an explicit `shell: "powershell"` one — because Claude's
|
|
92
|
+
own hooks.md says the shell "defaults to bash, or to powershell on Windows
|
|
93
|
+
when Git Bash isn't installed": bash syntax fed to `powershell -Command` on
|
|
94
|
+
such a machine is a hard parse error, silently losing both env and context for
|
|
95
|
+
the whole session.
|
|
96
|
+
- feat: **`DotAgentsArgs`** (`dotagents.cli`, re-exported for overlay-shipped
|
|
97
|
+
commands) — one shared `-g/--global` + `--agents-dir` base class. `init` and
|
|
98
|
+
all four `overlays` subcommands now inherit it instead of independently
|
|
99
|
+
redeclaring the same fields, so scope resolution can't silently drift between
|
|
100
|
+
commands (one previously defaulted `agents_dir` eagerly to `Path.home() /
|
|
101
|
+
".agents"`; harmless in practice, but needless).
|
|
102
|
+
|
|
103
|
+
### Fixed
|
|
104
|
+
|
|
105
|
+
- fix: **`dotagents env --format powershell`/`cmd` left an already-POSIX `PATH`
|
|
106
|
+
unconverted**, the mirror of the export/dotenv/fish fix below going the other
|
|
107
|
+
direction. Found live: run from a genuine Windows PowerShell terminal whose
|
|
108
|
+
own inherited `PATH` already held WSL/MSYS-mount-style entries
|
|
109
|
+
(`/mnt/c/Program Files/...`), the emitted `${env:PATH} = '...'` was
|
|
110
|
+
syntactically valid PowerShell but a single opaque colon-joined string, not
|
|
111
|
+
the `;`-split list PowerShell's own PATH lookup needs — every subsequent
|
|
112
|
+
bare-command lookup broke for that session. Also handles the MIXED case
|
|
113
|
+
(dotagents' own native bin dirs prepended onto an already-POSIX inherited
|
|
114
|
+
PATH, one string with both separators at once), caught by sourcing real
|
|
115
|
+
output into a live PowerShell session and watching `git.exe` fail to
|
|
116
|
+
resolve before the second fix. A WSL-only segment with no Windows equivalent
|
|
117
|
+
(`/usr/bin`) is dropped rather than mangled into a broken relative path.
|
|
118
|
+
- fix: **the built `.pyz` degraded `-g` on any discovered command inheriting a
|
|
119
|
+
dotagents-defined base class** (first hit by `DotAgentsArgs` above) — duho's
|
|
120
|
+
AST introspection walks the full MRO for a command's flags, but the zipapp
|
|
121
|
+
source-repoint shim only ever covered built-in command modules, not a base
|
|
122
|
+
class's own module (`dotagents.cli._common`). `--global` degraded to the
|
|
123
|
+
name-derived `--global-scope` and `-g` vanished silently. Fixed, with new CI
|
|
124
|
+
coverage asserting the exact short flag survives a real built pyz.
|
|
125
|
+
- fix: **`harness_loads` relative entries matched by bare filename, not full
|
|
126
|
+
path** — a relative entry like Codex's `"AGENTS.md"` wrongly suppressed ANY
|
|
127
|
+
file sharing that basename anywhere on disk, including the unrelated
|
|
128
|
+
`~/.agents/AGENTS.md` user-store file Codex's harness never reads.
|
|
129
|
+
`dotagents context --agents codex` emitted an empty `sources: []` even with
|
|
130
|
+
real content present. Now resolved against `project_root` and compared by
|
|
131
|
+
full path, like the absolute (`~/`, `/`) forms already were.
|
|
132
|
+
- fix: `dotagents context --format json` crashed with `UnicodeEncodeError` on
|
|
133
|
+
any character outside Latin-1 (a bare `print()` encoding with the console's
|
|
134
|
+
codepage) — the same class of bug already fixed for the markdown path, just
|
|
135
|
+
never covered for JSON.
|
|
136
|
+
- fix: PowerShell format uses `${env:NAME}` (curly-brace form), not the bare
|
|
137
|
+
`$env:NAME` sigil — a handful of real Windows env vars have parens in their
|
|
138
|
+
names (`ProgramFiles(x86)`), and `$env:FOO(X86) = ...` is a PowerShell parse
|
|
139
|
+
error; the curly-brace form is valid for every name.
|
|
140
|
+
|
|
141
|
+
## [0.3.1] - 2026-07-24
|
|
142
|
+
|
|
10
143
|
### Fixed
|
|
11
144
|
|
|
12
145
|
- fix: **`dotagents env --format export`/`dotenv`/`fish` now emit a POSIX PATH on
|
|
@@ -27,8 +160,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
27
160
|
leading slash (`/server/share/...`) during the POSIX conversion above — MSYS
|
|
28
161
|
requires the double-slash UNC root (`//server/share/...`) to resolve it.
|
|
29
162
|
|
|
30
|
-
## [0.3.1] - 2026-07-24
|
|
31
|
-
|
|
32
163
|
Patch release: the PATH/POSIX-conversion fix above (the only change since 0.3.0).
|
|
33
164
|
|
|
34
165
|
## [0.3.0] - 2026-07-24
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: dotagents-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: The dotagents CLI: install and manage a portable ~/.agents config for AI coding agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/jose-pr/dotagents/
|
|
6
6
|
Project-URL: Repository, https://github.com/jose-pr/dotagents
|
|
@@ -102,7 +102,7 @@ them. Everything else is repo infrastructure.
|
|
|
102
102
|
| --- | --- |
|
|
103
103
|
| `src/dotagents/` | The installable `dotagents` CLI (`init`/`overlays`/`context`/`env`/`build-pyz`) — that is the whole shipped surface; commands beyond it come from overlays or your own `cmds/` modules |
|
|
104
104
|
| `src/dotagents/_overlay/` | The **base overlay** `init` writes: `AGENTS.md` scaffolding, `CLAUDE.md`, `dotagents/DECISIONS.md` (empty design-log index), and an empty `dotagents/cmds/` dir — your drop-in point for your own command modules. Neutral — imposes no flows, ships no command |
|
|
105
|
-
| `tools/` |
|
|
105
|
+
| `tools/` | Repo tooling, not shipped: `audit.py` (CI structure check) and `cloud-setup.sh`. `leak-check` is not here either — it is a personal command module you keep in your own private `.agents/dotagents/cmds/` |
|
|
106
106
|
| `install.py` | Thin shim over `dotagents.cli.main()`, kept at this filename for muscle memory |
|
|
107
107
|
|
|
108
108
|
The **example overlays** — the `flows` workflow set, per-language `kb/` + templates,
|
|
@@ -41,7 +41,7 @@ them. Everything else is repo infrastructure.
|
|
|
41
41
|
| --- | --- |
|
|
42
42
|
| `src/dotagents/` | The installable `dotagents` CLI (`init`/`overlays`/`context`/`env`/`build-pyz`) — that is the whole shipped surface; commands beyond it come from overlays or your own `cmds/` modules |
|
|
43
43
|
| `src/dotagents/_overlay/` | The **base overlay** `init` writes: `AGENTS.md` scaffolding, `CLAUDE.md`, `dotagents/DECISIONS.md` (empty design-log index), and an empty `dotagents/cmds/` dir — your drop-in point for your own command modules. Neutral — imposes no flows, ships no command |
|
|
44
|
-
| `tools/` |
|
|
44
|
+
| `tools/` | Repo tooling, not shipped: `audit.py` (CI structure check) and `cloud-setup.sh`. `leak-check` is not here either — it is a personal command module you keep in your own private `.agents/dotagents/cmds/` |
|
|
45
45
|
| `install.py` | Thin shim over `dotagents.cli.main()`, kept at this filename for muscle memory |
|
|
46
46
|
|
|
47
47
|
The **example overlays** — the `flows` workflow set, per-language `kb/` + templates,
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
Thin root-level wrapper over ``dotagents build-pyz`` (``dotagents.cli.build_pyz``):
|
|
5
5
|
it vendors ``duho`` + ``pathlib_next`` into the archive (via ``pip install --target``,
|
|
6
|
-
using **the Python that runs this script**), copies the ``dotagents`` package
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
using **the Python that runs this script**), copies the ``dotagents`` package, strips
|
|
7
|
+
caches/metadata, and packages a runnable zipapp. The result needs no ``pip install``
|
|
8
|
+
on the target machine. The repo's ``tools/`` is CI tooling and is NOT bundled.
|
|
9
9
|
|
|
10
10
|
Usage:
|
|
11
11
|
python build.py # -> dist/dotagents.pyz
|
|
@@ -21,4 +21,3 @@ form its API. Generated from docstrings, organized by module:
|
|
|
21
21
|
- **[Hooks](hooks.md)** — additive, idempotent merge of dotagents' hooks into an
|
|
22
22
|
agent's own settings/config file.
|
|
23
23
|
- **[Skills](skills.md)** — publishing an overlay's skills into a scope's shared dir.
|
|
24
|
-
- **[Sync](sync.md)** — the `PathSyncer` backup/copy wrapper for `install`.
|
|
@@ -155,7 +155,14 @@ dotagents context --write-agent # write each agent's native confi
|
|
|
155
155
|
- `--write-agent` — write each agent's native config file instead of `[output]`.
|
|
156
156
|
- `--agents <a,b>` — which agents to generate for (default: the active agent).
|
|
157
157
|
- `--format markdown|system-reminder|json` — output shape.
|
|
158
|
-
- `-g` / `--global` —
|
|
158
|
+
- `-g` / `--global` — skip the project-level context files (the store is unaffected).
|
|
159
|
+
- `--agents-dir <dir>` — user store override for this run.
|
|
160
|
+
|
|
161
|
+
Roots: the store is `--agents-dir` → `$AGENTS_HOME` → `~/.agents`, and the project
|
|
162
|
+
root is `$AGENTS_PROJECT_ROOT` → `$CLAUDE_PROJECT_DIR` → the cwd. A `SessionStart`
|
|
163
|
+
hook runs this from wherever the session started, so pinning
|
|
164
|
+
`AGENTS_PROJECT_ROOT` is what keeps the assembled context the same in every
|
|
165
|
+
subdirectory.
|
|
159
166
|
|
|
160
167
|
## env
|
|
161
168
|
|
|
@@ -174,7 +181,12 @@ python -m dotagents env --diff --format json # only vars that differ from the
|
|
|
174
181
|
(`bat`/`batch`, `set "KEY=value"`), `fish` (`set -gx KEY value`). Data forms:
|
|
175
182
|
`json`, `ini`, `yaml`. An explicit `--format` always wins.
|
|
176
183
|
- `--diff` — emit only the change set vs. the caller's environment.
|
|
177
|
-
- `-g` / `--global` —
|
|
184
|
+
- `-g` / `--global` — skip the project-level env files (the store is unaffected).
|
|
185
|
+
- `--agents-dir <dir>` — user store override for this run.
|
|
186
|
+
|
|
187
|
+
Roots resolve exactly as for `context` above (`$AGENTS_HOME` /
|
|
188
|
+
`$AGENTS_PROJECT_ROOT`, both of which `env` also emits — so a subprocess reading
|
|
189
|
+
them agrees with the parent that wrote them).
|
|
178
190
|
|
|
179
191
|
!!! warning
|
|
180
192
|
`env` output is sensitive by design — it prints resolved values. Treat the
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""Entry point for the dotagents CLI (init /
|
|
2
|
+
"""Entry point for the dotagents CLI (init / overlays / context / env / build-pyz).
|
|
3
3
|
|
|
4
4
|
Thin front over ``dotagents.cli.main()``, kept at this filename so existing
|
|
5
5
|
muscle-memory/docs pointing at ``python install.py`` still work.
|
|
@@ -14,7 +14,7 @@ bare ``pip`` off PATH) guarantees the install lands in *this* Python, so the
|
|
|
14
14
|
import that follows actually sees it. When everything is already importable it
|
|
15
15
|
just dispatches -- no pip is ever run.
|
|
16
16
|
|
|
17
|
-
Usage: python install.py <init|
|
|
17
|
+
Usage: python install.py <init|overlays|context|env|build-pyz|...> [options]
|
|
18
18
|
Run `python install.py --help` for the full subcommand/flag reference.
|
|
19
19
|
"""
|
|
20
20
|
import os
|
|
@@ -9,7 +9,7 @@ build-backend = "hatchling.build"
|
|
|
9
9
|
# redirect/placeholder before someone else does, but the CLI ships under -cli.
|
|
10
10
|
[project]
|
|
11
11
|
name = "dotagents-cli"
|
|
12
|
-
version = "0.3.
|
|
12
|
+
version = "0.3.3"
|
|
13
13
|
authors = [{ name = "Jose A." }]
|
|
14
14
|
description = "The dotagents CLI: install and manage a portable ~/.agents config for AI coding agents"
|
|
15
15
|
readme = "README.md"
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# dotagents — package API header
|
|
2
|
+
|
|
3
|
+
Public API of the installed `dotagents` package: the CLI umbrella plus the `_*.py`
|
|
4
|
+
helper modules that back it. This file ships inside the package so a consuming agent
|
|
5
|
+
can read it without the source. Full docs: https://jose-pr.github.io/dotagents/
|
|
6
|
+
|
|
7
|
+
## Entry points
|
|
8
|
+
|
|
9
|
+
- `dotagents.cli.main(argv=None) -> int` — the `install.py` shim and
|
|
10
|
+
`python -m dotagents` entry point. Repoints zipapp sources, then dispatches through
|
|
11
|
+
`duho.app` with the discovered command set. Also the `dotagents` console script
|
|
12
|
+
(`[project.scripts]`).
|
|
13
|
+
- `dotagents.cli.Dotagents(LoggingArgs, Cli)` — the umbrella CLI class.
|
|
14
|
+
- `dotagents.cli.DotAgentsArgs` — mix-in base carrying the shared `-g/--global` +
|
|
15
|
+
`--agents-dir` pair and `resolve_scope()`; an overlay-shipped command module
|
|
16
|
+
should inherit it (as the FIRST base) rather than redeclare the flags.
|
|
17
|
+
- `dotagents.cli.resolve_user_store(agents_dir=None) -> Path` — the USER store
|
|
18
|
+
root: `agents_dir` (`--agents-dir`) → `$AGENTS_HOME` → legacy
|
|
19
|
+
`$DOTAGENTS_AGENTS_DIR` → `~/.agents`. Use it (not `resolve_scope`) when the
|
|
20
|
+
store is always the user store and the project scope only adds/removes tiers,
|
|
21
|
+
as in `env` / `context`.
|
|
22
|
+
- Compiled command classes live in `dotagents.cli.<name>` (`init`, `overlays`,
|
|
23
|
+
`context`, `env`, `build_pyz`); each is a `class X(LoggingArgs, Cmd)` — or
|
|
24
|
+
`class X(DotAgentsArgs)`, which is that pair transitively — with a
|
|
25
|
+
`__call__`. That is the WHOLE shipped surface — dotagents bundles no command
|
|
26
|
+
module of its own. `link` / `sync` left the package with their logic (D85): the
|
|
27
|
+
opt-in **private-sync** overlay ships them, renamed `link-project` /
|
|
28
|
+
`sync-project`, from its own `cmds/` + `lib/_link.py`. `leak-check` is likewise
|
|
29
|
+
not in the repo — a personal command module the user keeps in their private
|
|
30
|
+
`<scope>/dotagents/cmds/` (D84). `audit` is repo CI tooling (`tools/audit.py`),
|
|
31
|
+
not a command.
|
|
32
|
+
- Command discovery layers sources, later wins: built-ins < bundled `cmds` <
|
|
33
|
+
overlay `cmds` (`<overlay-root>/cmds`) < scope `cmds` dirs (user + project) <
|
|
34
|
+
`$AGENTS_CMDS_PATH` < `--cmdspath`. The overlay + scope tiers come from one
|
|
35
|
+
Contract-A `get_file_paths` walk (`cli._cmds_dirs`), the same resolver that
|
|
36
|
+
backs `bin`/PATH.
|
|
37
|
+
|
|
38
|
+
## Helper modules (public surface)
|
|
39
|
+
|
|
40
|
+
- `_agents` — `Agent` base type + per-agent adapters; `stamp_identity(...)` emits the
|
|
41
|
+
standardized `AGENTS_*` / `AGENT` identity vars.
|
|
42
|
+
- `_overlays` — `install_overlay_dir` (copy an overlay into a scope, self-describing)
|
|
43
|
+
/ `apply_overlay` (lay its files down) / `read_manifest` / `find_setup_script` /
|
|
44
|
+
`run_overlay_setup`; installs an overlay's files and collects its `routing` / `rules`
|
|
45
|
+
contributions to the managed `AGENTS.md` block. `DEFAULT_PRIORITY = 500`.
|
|
46
|
+
- `_scope` — `resolve_scope(global_scope, agents_dir=None)` and `resolve_source(...)`;
|
|
47
|
+
scope = *where installed overlays live* (user = the configurable store, project =
|
|
48
|
+
`<project>/.agents`), source = *where an overlay comes from* (bundled by default).
|
|
49
|
+
Installed overlays are **discovered** by presence, not tracked in a registry.
|
|
50
|
+
- `_context` — assemble the effective per-agent context (Plan 04); reads overlay
|
|
51
|
+
`priority` from the manifest (lower sorts earlier).
|
|
52
|
+
- `_env` — chained env-file assembly + `env.py` execution (frozen contract B):
|
|
53
|
+
`get_environment` / `get_diff` / `resolve_env_files` / `get_env_from_py` /
|
|
54
|
+
`get_env_from_file`. Bins onto PATH first, then two tiers (`pre.env*` then `env*`),
|
|
55
|
+
later-overrides-earlier. Identity seeded before the chain; proxy vars applied after.
|
|
56
|
+
- `_resolve` — `get_file_paths(*names, agents_dir, project_root, global_scope=False,
|
|
57
|
+
include_missing=False)`: the Contract-A precedence walk / filename resolution.
|
|
58
|
+
- `_merge` — managed-block merge for `init`'s `AGENTS.md` / `CLAUDE.md`, delimited by
|
|
59
|
+
`<!-- dotagents:begin -->` / `<!-- dotagents:end -->`. Detection is by marker
|
|
60
|
+
presence only, so it survives user reformatting. `begin_marker`/`end_marker`
|
|
61
|
+
override the pair for other comment syntaxes (`#` for TOML), and `append=True`
|
|
62
|
+
puts a first-time block at the END of the file — required for TOML, where a
|
|
63
|
+
`[table]` header captures every key line after it and a prepended block would
|
|
64
|
+
swallow the user's top-level keys.
|
|
65
|
+
- `_skills` — publish an overlay's `skills/<name>/` into a scope's shared skills dir
|
|
66
|
+
(symlink-preferred, copy fallback); unpublish removes only what the overlay
|
|
67
|
+
published, then sweeps broken symlinks. Pure stdlib.
|
|
68
|
+
- `_hooks` — additive, idempotent merge of our hooks into an agent's `settings.json`.
|
|
69
|
+
`hooks.<Event>` is a **list of matcher-objects** each holding its own `hooks` list,
|
|
70
|
+
not a flat command list. Foreign hooks are preserved verbatim, malformed entries
|
|
71
|
+
are dropped rather than raising, and invalid JSON raises `SystemExit` instead of
|
|
72
|
+
silently overwriting the user's file. `shell` (Claude: `"bash"`/`"powershell"`,
|
|
73
|
+
picks the interpreter for the hook's own command) and `command_windows`
|
|
74
|
+
(Codex: emitted as `commandWindows`, a separate Windows-only command OVERRIDE,
|
|
75
|
+
not an interpreter choice) are both supported. Pure stdlib. Consumed by
|
|
76
|
+
`ClaudeAgent.wire_hooks` (`~/.claude/settings.json`: env via `$CLAUDE_ENV_FILE`
|
|
77
|
+
+ context via stdout, plus `CwdChanged`, plus a `PreToolUse` env-loader for the
|
|
78
|
+
PowerShell tool) and `CodexAgent.wire_hooks` (`<CODEX_HOME|~/.codex>/hooks.json`,
|
|
79
|
+
never `config.toml`: `SessionStart` context-only, plus a `PreToolUse` env-loader
|
|
80
|
+
matched on `matcher: "Bash"`). Codex's hook JSON is structurally identical to
|
|
81
|
+
Claude's, including the SAME `updatedInput.command` rewrite mechanism on
|
|
82
|
+
`PreToolUse` — confirmed directly against Codex's own docs
|
|
83
|
+
(learn.chatgpt.com/docs/hooks), not assumed from Claude parity.
|
|
84
|
+
Gemini/Cursor/Copilot keep the base no-op — Gemini CLI proper has no hook
|
|
85
|
+
mechanism documented at all (checked directly). **Antigravity is a separate
|
|
86
|
+
product** from Gemini CLI (Antigravity CLI/IDE/SDK family, shares only the
|
|
87
|
+
`~/.gemini/` namespace for some files) and DOES wire a hook: see
|
|
88
|
+
`AntigravityAgent.wire_hooks` below. `PreToolUse` there is still allow/deny/ask
|
|
89
|
+
only — no `updatedInput`, so no command-rewrite/env-loader path exists to hang
|
|
90
|
+
on it, unlike Claude/Codex. Revisit both conclusions if either framework changes.
|
|
91
|
+
- **`AntigravityAgent.wire_hooks`** (`<AGENTS_HOME_ANTIGRAVITY|~/.gemini/config>/hooks.json`,
|
|
92
|
+
keyed under a `"dotagents"` name per the docs' own example shape — a named-entry
|
|
93
|
+
object, not Claude/Codex's flat `hooks.<Event>`): wires a single `PreInvocation`
|
|
94
|
+
entry, context-only, no env mechanism. Antigravity's hooks
|
|
95
|
+
(antigravity.google/docs/hooks) have exactly five events — `PreToolUse`,
|
|
96
|
+
`PostToolUse`, `PreInvocation`, `PostInvocation`, `Stop` — no SessionStart
|
|
97
|
+
equivalent. `PreInvocation` fires every model turn (`invocationNum`, 0-indexed),
|
|
98
|
+
so the deployed script (`preinvocation_antigravity_context.py`,
|
|
99
|
+
`_overlay/dotagents/hooks/`) gates on `invocationNum == 0` to behave like a
|
|
100
|
+
one-shot SessionStart rather than resending context every turn. Output shape is
|
|
101
|
+
a bare `{"injectSteps": [{"ephemeralMessage": "..."}]}`, no `hookSpecificOutput`
|
|
102
|
+
wrapper — confirmed against the primary docs after an earlier pass here wrongly
|
|
103
|
+
concluded no useful injection was possible; `ephemeralMessage` is the one of the
|
|
104
|
+
three step types meant for free text (`toolCall` executes a tool,
|
|
105
|
+
`userMessage` impersonates the user). No detection marker exists anywhere in
|
|
106
|
+
Antigravity's docs, so `detect_env_vars = []` — explicit `--agents antigravity`
|
|
107
|
+
only, same posture as Codex's env-block precedent (writes touching an agent's
|
|
108
|
+
own live config are opt-in, never inferred).
|
|
109
|
+
- **`SessionStart`/`CwdChanged` register TWO handlers each**, bash-syntax
|
|
110
|
+
(default shell) and a PowerShell-native equivalent (`shell: "powershell"`).
|
|
111
|
+
hooks.md: `shell` "Defaults to bash, or to powershell on Windows when Git Bash
|
|
112
|
+
isn't installed" — verified directly that bash syntax fed to `powershell
|
|
113
|
+
-Command` on such a machine is a hard parse error, not a soft failure, so
|
|
114
|
+
every session there would silently get neither env nor context. Every handler
|
|
115
|
+
in a matched group fires unconditionally (hooks.md), so both always run; the
|
|
116
|
+
one whose interpreter is absent fails harmlessly. The PowerShell
|
|
117
|
+
`SessionStart` variant is context-only (`dotagents context`), not
|
|
118
|
+
env+context: `$CLAUDE_ENV_FILE`'s documented effect is "subsequent BASH
|
|
119
|
+
commands" regardless of which shell wrote it, so writing to it from a
|
|
120
|
+
PowerShell-shelled hook would feed nothing.
|
|
121
|
+
- **Windows only**: `ClaudeAgent._wire_powershell_pretooluse` additionally wires
|
|
122
|
+
a no-matcher `PreToolUse` hook (fires on every tool call), `shell:
|
|
123
|
+
"powershell"`, running `PRETOOLUSE_POWERSHELL_COMMAND` INLINE — deliberately
|
|
124
|
+
not a `.ps1` file, since a script file is subject to PowerShell's execution
|
|
125
|
+
policy (RemoteSigned/AllSigned/Restricted) and dotagents has no code-signing
|
|
126
|
+
certificate; verified directly that the inline form runs successfully even
|
|
127
|
+
under `Restricted`, which blocks every `.ps1` file outright. Closes a real
|
|
128
|
+
gap: `$CLAUDE_ENV_FILE` is Bash-tool-only (confirmed empirically that
|
|
129
|
+
`$env:CLAUDE_ENV_FILE` is empty inside a live PowerShell tool call), so the
|
|
130
|
+
SessionStart env half never reaches the PowerShell tool. Uses `PreToolUse`'s
|
|
131
|
+
`updatedInput` to prepend a guarded env-loader (`AGENTS_RUNTIME_SET`,
|
|
132
|
+
matching the precursor's convention) to a `PowerShell` tool call's own
|
|
133
|
+
command — not by trying to persist state across hook invocations, which are
|
|
134
|
+
each their own fresh process and cannot. Whether the guard is visible across
|
|
135
|
+
separate PowerShell tool calls is UNVERIFIED. Every literal `\` in the
|
|
136
|
+
command constant must be a raw string — a bare `\b` in a normal Python string
|
|
137
|
+
literal silently becomes a backspace character, corrupting the emitted path;
|
|
138
|
+
caught once by testing a draft through a real PowerShell spawn.
|
|
139
|
+
- **`CodexAgent._deploy_pretooluse_script`** covers the same env gap for Codex,
|
|
140
|
+
which has NO env-persistence mechanism at any hook event (not Bash-only like
|
|
141
|
+
Claude — none). Ships `pretooluse_codex_env.py` (`_overlay/dotagents/hooks/`),
|
|
142
|
+
deployed to `<codex-home>/hooks/` (create-or-refresh), wired as `PreToolUse`
|
|
143
|
+
with `matcher: "Bash"` — Codex's one shell tool, so (unlike Claude's
|
|
144
|
+
no-matcher hook) filtering happens at the settings level, no runtime
|
|
145
|
+
`tool_name` check needed in the script. A FILE, not inlined like Claude's:
|
|
146
|
+
Codex's docs show every hook example as `python3 <path>`, and a `.py` file
|
|
147
|
+
has no execution-policy/signing concern (PowerShell-specific). Sets
|
|
148
|
+
`commandWindows` to `python "<path>"` (not `python3`) — verified directly
|
|
149
|
+
that `python3` resolves to the Microsoft Store app-execution-alias stub and
|
|
150
|
+
fails outright on this dev machine (exit 49), the same trap noted elsewhere
|
|
151
|
+
for `py`/venv creation.
|
|
152
|
+
|
|
153
|
+
## Environment variables
|
|
154
|
+
|
|
155
|
+
The prefix split (D80): **`AGENTS_*`** names everything about the `.agents` / agent
|
|
156
|
+
world (paths, scope, overlays, sync) — non-secret, safe to emit; **`DOTAGENTS_*`** is
|
|
157
|
+
reserved for genuinely tool-internal config and secrets, so the "never print
|
|
158
|
+
`DOTAGENTS_*` values" leak guard (D48) stays a simple blanket ban over exactly the
|
|
159
|
+
sensitive set.
|
|
160
|
+
|
|
161
|
+
Config / path / sync vars (`AGENTS_*`, non-secret — read, and some emitted):
|
|
162
|
+
|
|
163
|
+
- `AGENTS_HOME` — the configurable user-scope store path (default `~/.agents`). Also
|
|
164
|
+
**emitted** by `dotagents env` (D79) and set for overlay setup scripts / sync hooks.
|
|
165
|
+
- `AGENTS_STORE_DIR` — per-project store location (absolute paths allowed).
|
|
166
|
+
- `AGENTS_OVERLAYS_SRC` — default overlay source dir for `overlays`.
|
|
167
|
+
- `AGENTS_CMDS_PATH` — extra command-module search paths (os.pathsep-split).
|
|
168
|
+
- `AGENTS_OVERLAY_DIR` — set for an overlay's setup script (its own installed dir).
|
|
169
|
+
- `AGENTS_REMOTE` / `AGENTS_SYNC_MESSAGE` — private-store sync (tokenless remote URL /
|
|
170
|
+
commit message).
|
|
171
|
+
|
|
172
|
+
Every reader above prefers the `AGENTS_*` name and falls back to the old
|
|
173
|
+
`DOTAGENTS_*` name (`DOTAGENTS_AGENTS_DIR`, `DOTAGENTS_STORE_DIR`,
|
|
174
|
+
`DOTAGENTS_OVERLAYS_SRC`, `DOTAGENTS_CMDS_PATH`, `DOTAGENTS_OVERLAY_DIR`,
|
|
175
|
+
`DOTAGENTS_AGENTS_REMOTE`, `DOTAGENTS_SYNC_MESSAGE`) for one release — deprecated,
|
|
176
|
+
removable next. Setters emit both names this release.
|
|
177
|
+
|
|
178
|
+
Tool-internal / secret vars (`DOTAGENTS_*` — kept; read, **never printed**):
|
|
179
|
+
|
|
180
|
+
- `DOTAGENTS_AGENTS_TOKEN` — **secret** (fine-grained PAT) for private-store auth.
|
|
181
|
+
- `DOTAGENTS_CLI_INSTALL` — pip spec to install the CLI itself (tool-specific).
|
|
182
|
+
- `DOTAGENTS_AUDIT_PATTERNS` — path to the machine-local audit-pattern file (tooling).
|
|
183
|
+
|
|
184
|
+
Emitted by the identity/env layer (safe to branch on in env files):
|
|
185
|
+
`AGENTS_HARNESS`, `AGENTS_VENDOR`, `AGENTS_MODEL`, `AGENT`, `AGENTS_PROXY`,
|
|
186
|
+
`AGENTS_WEBFETCH_PROXY_URL`, plus the two
|
|
187
|
+
scope roots — `AGENTS_HOME` (the user store, `agents_dir`/`~/.agents`) and
|
|
188
|
+
`AGENTS_PROJECT_ROOT` (this project's root). Both are seeded only if unset, so a
|
|
189
|
+
harness/env can pin them. Deliberately NOT emitted, despite looking like they
|
|
190
|
+
would be: `AGENTS_AGENT` (a named persona — nothing derives one, so there is
|
|
191
|
+
nothing to emit) and `AGENTS_CODE_SESSION_ID` (dropped with the precursor's
|
|
192
|
+
blanket `CLAUDE_*`→`AGENTS_*` rewrite). Do not branch on either. `resolve_scope` READS `AGENTS_PROJECT_ROOT` (then the
|
|
193
|
+
agent-native `CLAUDE_PROJECT_DIR`, then cwd) for the project scope's root.
|
|
194
|
+
|
|
195
|
+
Every command READS both back, so the pin actually holds: `env` and `context`
|
|
196
|
+
resolve their user store through `cli.resolve_user_store()` (`--agents-dir` →
|
|
197
|
+
`$AGENTS_HOME` → legacy `$DOTAGENTS_AGENTS_DIR` → `~/.agents`) and their project
|
|
198
|
+
root through `_scope.project_root_default()` — neither is taken from the cwd or a
|
|
199
|
+
hardcoded home. This matters for hook-invoked runs: a `SessionStart` hook runs
|
|
200
|
+
`dotagents context` / `dotagents env` from wherever the session started, and only
|
|
201
|
+
the pinned root makes that cwd-independent. `-g/--global` on these two means
|
|
202
|
+
*skip the project tier*, not *use a different store*.
|
|
203
|
+
|
|
204
|
+
## Gotchas
|
|
205
|
+
|
|
206
|
+
- **Python 3.9 floor.** Files using bare `X | Y` unions in runtime-evaluated positions
|
|
207
|
+
need `from __future__ import annotations`. `Path.write_text(..., newline=...)` is
|
|
208
|
+
3.10+, so wrapper-script writers use `open(path, "w", newline="")`.
|
|
209
|
+
- **Zipapp source shim.** Inside a `.pyz`, `Path(__file__)` is not a real file, so
|
|
210
|
+
duho's AST flag/help introspection degrades (`--from` → `--from-`, positionals lost).
|
|
211
|
+
`cli.main()` calls `_repoint_zipapp_sources()` first, extracting the built-in command
|
|
212
|
+
modules to real temp files. Discovered `cmds` modules are extracted by
|
|
213
|
+
`_package_data_dir` before import, so they need no repoint.
|
|
214
|
+
- **Package data in a `.pyz`.** `_package_data_dir(name)` resolves a package-data dir
|
|
215
|
+
by name — `_overlay` (the base overlay `init` writes) and `_overlays_src` (bundled
|
|
216
|
+
example overlays, when a build includes them) — via `importlib.resources` (a
|
|
217
|
+
zip-backed `Traversable` is extracted to a temp dir once), never
|
|
218
|
+
`Path(__file__).exists()` (always False in a zipapp).
|
|
219
|
+
- **`pathlib_next` needs `typing_extensions` on Python < 3.10** (an upstream gap); a
|
|
220
|
+
3.9 environment must `pip install typing_extensions`.
|