dotagents-cli 0.6.3__tar.gz → 0.7.0__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.6.3 → dotagents_cli-0.7.0}/CHANGELOG.md +30 -1
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/PKG-INFO +8 -8
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/README.md +7 -7
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/guide/commands.md +23 -14
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/guide/install.md +1 -1
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/guide/overlays.md +8 -4
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/index.md +2 -1
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/AGENTS.md +38 -12
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/__init__.py +1 -1
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_agents.py +14 -2
- dotagents_cli-0.7.0/src/dotagents/_ignore.py +146 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_overlays.py +90 -59
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/cli/__init__.py +23 -2
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/cli/overlays.py +32 -33
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_cli_main.py +2 -2
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_cmds_discovery.py +38 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_fix_overlays.py +59 -20
- dotagents_cli-0.7.0/tests/test_ignore.py +58 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_overlays_cli.py +5 -10
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_overlays_command.py +7 -5
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_wire_hooks.py +25 -1
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/.gitignore +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/LICENSE +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/api/agents.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/api/cli.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/api/commands.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/api/context.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/api/env.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/api/fs.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/api/hooks.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/api/merge.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/api/overlays.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/api/reference.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/api/resources.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/api/scope.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/api/skills.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/api/sources.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/api/wrappers.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/changelog.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/guide/authoring.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/docs/guide/private-sync.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/install.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/mkdocs.yml +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/pyproject.toml +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/__main__.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_context.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_env.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_fs.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_hooks.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_merge.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_overlay/dotagents/cmds/findings.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_overlay/dotagents/cmds/launch.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_overlay/dotagents/hooks/preinvocation_antigravity_context.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_overlay/dotagents/hooks/pretooluse_codex_env.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_overlay/dotagents/hooks/sessionstart_codex_context.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_overlay/dotagents/templates/AGENTS.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_overlay/dotagents/templates/PROJECT.md +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_resources.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_scope.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_skills.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_sources.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/_wrappers.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/cli/_common.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/cli/about.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/cli/build_pyz.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/cli/context.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/cli/env.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/cli/init.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/cli/path.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/src/dotagents/py.typed +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/_helpers.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/_shell.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/conftest.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_about.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_agents.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_api_header.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_audit_leak.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_build_pyz.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_cloud_setup.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_context.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_env.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_env_cli_scope.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_env_format.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_env_into.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_env_regressions.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_env_safety.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_findings_cmd.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_fix_adapters.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_fix_cli.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_fix_env.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_fix_hooks.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_fix_init.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_fix_packaging.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_fix_scope_context.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_fix_skills_adapters.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_hooks.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_init_base.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_last_mile.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_launch_cmd.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_mcp.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_overlay_libs.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_path_timeout_frontmatter.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_powershell_env_hook.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_review_security.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_scope.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_sources.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_sources_uri.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tests/test_wrappers.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tools/audit.py +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tools/cloud-setup.sh +0 -0
- {dotagents_cli-0.6.3 → dotagents_cli-0.7.0}/tools/pyz_smoke.sh +0 -0
|
@@ -7,6 +7,34 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.0] - 2026-10-01
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- `overlays sync`, and `overlays add` of an overlay already installed, make the
|
|
15
|
+
install exactly what the source ships: changed files are replaced and files the
|
|
16
|
+
source does not ship are removed, including ones edited or added in the install
|
|
17
|
+
(each backed up under `<store>/install_backup/` first). What the `.gitignore` /
|
|
18
|
+
`.ignore` at the overlay's root match (read as git reads a `.gitignore`; one in a
|
|
19
|
+
subdirectory is an ordinary file), and tool caches, are the install's own and left
|
|
20
|
+
alone. Previously an edited file was kept unless `--overwrite`.
|
|
21
|
+
- `--prune` (on `sync`, and now on `add`) clears what `.gitignore` / `.ignore`
|
|
22
|
+
protect and the tool caches too, leaving exactly the upstream files.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- A command module whose class declares no `_parsername_` is named by its
|
|
27
|
+
class in kebab case again (`LeakCheck` -> `dotagents leak-check`). Since 0.6.2
|
|
28
|
+
(duho 0.6.0) such a command was only reachable as `dotagents LeakCheck`.
|
|
29
|
+
- The Claude Code CwdChanged hook keeps one `AGENTS_PROJECT_ROOT` pin in
|
|
30
|
+
`$CLAUDE_ENV_FILE`, replacing the previous one, instead of appending a line on
|
|
31
|
+
every `cd` (the file is inlined into every Bash command). Other lines in the file
|
|
32
|
+
are kept. Re-run `dotagents init -g` to update an installed hook.
|
|
33
|
+
|
|
34
|
+
### Removed
|
|
35
|
+
|
|
36
|
+
- `overlays sync --overwrite`: replacing is what `sync` always does now.
|
|
37
|
+
|
|
10
38
|
## [0.6.3] - 2026-09-30
|
|
11
39
|
|
|
12
40
|
### Added
|
|
@@ -1334,7 +1362,8 @@ Patch release: the PATH/POSIX-conversion fix above (the only change since 0.3.0)
|
|
|
1334
1362
|
affected module sources (`dotagents.cli`, `duho.presets`) to extracted temp files
|
|
1335
1363
|
before dispatch; a no-op for a plain install.
|
|
1336
1364
|
|
|
1337
|
-
[Unreleased]: https://github.com/jose-pr/dotagents/compare/v0.
|
|
1365
|
+
[Unreleased]: https://github.com/jose-pr/dotagents/compare/v0.7.0...HEAD
|
|
1366
|
+
[0.7.0]: https://github.com/jose-pr/dotagents/compare/v0.6.3...v0.7.0
|
|
1338
1367
|
[0.6.3]: https://github.com/jose-pr/dotagents/compare/v0.6.2...v0.6.3
|
|
1339
1368
|
[0.6.2]: https://github.com/jose-pr/dotagents/compare/v0.6.1...v0.6.2
|
|
1340
1369
|
[0.6.1]: https://github.com/jose-pr/dotagents/compare/v0.6.0...v0.6.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: dotagents-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
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
|
|
@@ -203,14 +203,14 @@ installed first, its `routing`/`rules` merge into `AGENTS.md`'s managed block, a
|
|
|
203
203
|
`skills/<name>/` are symlinked (or `--copy`'d, for Windows / no-symlink) into the
|
|
204
204
|
store's shared `skills/` so every agent sees the same skills.
|
|
205
205
|
|
|
206
|
-
- `add` and `sync`
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
- `add` and `sync` keep an installed overlay exactly as its source ships it: changed
|
|
207
|
+
files are replaced and files the source does not ship are removed, anything edited
|
|
208
|
+
there backed up under `<store>/install_backup/` first. What the `.gitignore` /
|
|
209
|
+
`.ignore` at the overlay's root match (setup output, local state) and tool caches are left
|
|
210
|
+
alone; `--prune` clears them too. A fresh `add` whose setup script fails is rolled
|
|
211
|
+
back.
|
|
209
212
|
- `sync` refreshes each overlay from the repo it was installed from (`--repo` replaces
|
|
210
|
-
that source),
|
|
211
|
-
`requires` added upstream, and re-merges the managed block. `--overwrite` also
|
|
212
|
-
replaces edited files and `--prune` removes edited files the source dropped; both
|
|
213
|
-
back each file up under `<store>/install_backup/` first.
|
|
213
|
+
that source), installs a `requires` added upstream, and re-merges the managed block.
|
|
214
214
|
- `remove` deletes the overlay's directory, unpublishes only the skills **it**
|
|
215
215
|
published, and recomposes `AGENTS.md`'s managed block over the overlays that remain,
|
|
216
216
|
so its rules and routing leave with it. It refuses an overlay another installed
|
|
@@ -153,14 +153,14 @@ installed first, its `routing`/`rules` merge into `AGENTS.md`'s managed block, a
|
|
|
153
153
|
`skills/<name>/` are symlinked (or `--copy`'d, for Windows / no-symlink) into the
|
|
154
154
|
store's shared `skills/` so every agent sees the same skills.
|
|
155
155
|
|
|
156
|
-
- `add` and `sync`
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
- `add` and `sync` keep an installed overlay exactly as its source ships it: changed
|
|
157
|
+
files are replaced and files the source does not ship are removed, anything edited
|
|
158
|
+
there backed up under `<store>/install_backup/` first. What the `.gitignore` /
|
|
159
|
+
`.ignore` at the overlay's root match (setup output, local state) and tool caches are left
|
|
160
|
+
alone; `--prune` clears them too. A fresh `add` whose setup script fails is rolled
|
|
161
|
+
back.
|
|
159
162
|
- `sync` refreshes each overlay from the repo it was installed from (`--repo` replaces
|
|
160
|
-
that source),
|
|
161
|
-
`requires` added upstream, and re-merges the managed block. `--overwrite` also
|
|
162
|
-
replaces edited files and `--prune` removes edited files the source dropped; both
|
|
163
|
-
back each file up under `<store>/install_backup/` first.
|
|
163
|
+
that source), installs a `requires` added upstream, and re-merges the managed block.
|
|
164
164
|
- `remove` deletes the overlay's directory, unpublishes only the skills **it**
|
|
165
165
|
published, and recomposes `AGENTS.md`'s managed block over the overlays that remain,
|
|
166
166
|
so its rules and routing leave with it. It refuses an overlay another installed
|
|
@@ -88,7 +88,7 @@ the store's, else `dotagents` on `PATH`.
|
|
|
88
88
|
| Hook | Command | Why |
|
|
89
89
|
| --- | --- | --- |
|
|
90
90
|
| `SessionStart` | writes `dotagents env --diff --format export` into `$CLAUDE_ENV_FILE` (`--into`: one block, replaced on every run; `PATH` as a prefix on the shell's own), then runs `dotagents context` | Claude sources `$CLAUDE_ENV_FILE` before each Bash command, so the env layers reach every command in the session; and it injects the hook's **stdout into the session context**, which is how the assembled context reaches the model. |
|
|
91
|
-
| `CwdChanged` | when the new directory has a `.agents/`,
|
|
91
|
+
| `CwdChanged` | when the new directory has a `.agents/`, writes `export AGENTS_PROJECT_ROOT=<it>` into `$CLAUDE_ENV_FILE`, replacing the pin an earlier `cd` wrote; then prints the directory's `AGENTS.md` if there is one | Re-pins the project root after a `cd` into another project, and surfaces that directory's `AGENTS.md`. |
|
|
92
92
|
|
|
93
93
|
On Windows each event gets a second, PowerShell-native handler (`shell:
|
|
94
94
|
"powershell"`) that runs only when Claude Code would find no Git Bash, so a machine
|
|
@@ -105,11 +105,16 @@ without a prompt on each one requires `permissionDecision: "allow"`, which skips
|
|
|
105
105
|
approval prompt (your deny and ask rules still apply). It is off by default, and a
|
|
106
106
|
plain `init` removes one an earlier release wired.
|
|
107
107
|
|
|
108
|
-
The env half
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
The env half never truncates the file and is guarded by `[ -n "$CLAUDE_ENV_FILE" ]`
|
|
109
|
+
(see the [hooks docs](https://code.claude.com/docs/en/hooks#persist-environment-variables)):
|
|
110
|
+
another hook may write to the same file, so each hook replaces only its own part --
|
|
111
|
+
SessionStart its `env --into` block, CwdChanged its one `AGENTS_PROJECT_ROOT` line --
|
|
112
|
+
and keeps every other line. Appending instead grew the file on every re-run (resume,
|
|
113
|
+
compact, clear) and every `cd`, and Claude Code inlines it into each Bash command, which
|
|
114
|
+
Git for Windows' bash cuts off at about 8 KB. Each run starts from a fresh environment
|
|
115
|
+
(the hooks docs: previously sourced env files are not loaded in the hook's process), so
|
|
116
|
+
SessionStart recomputes the whole diff: a value changed since the last run is updated,
|
|
117
|
+
and one no env layer sets any more drops out. `$CLAUDE_ENV_FILE` exists only inside SessionStart/Setup/CwdChanged/FileChanged
|
|
113
118
|
hook processes — it is absent from the session's own shell, so checking for it with
|
|
114
119
|
`env | grep` proves nothing.
|
|
115
120
|
|
|
@@ -163,7 +168,7 @@ Manages opt-in overlays **by name**. See [Overlays](overlays.md) for the full mo
|
|
|
163
168
|
dotagents overlays add python engineering # install into the scope, publish skills, merge rules/routing
|
|
164
169
|
dotagents overlays list # installed (discovered) + available from the repos
|
|
165
170
|
dotagents overlays sync 'py*' # refresh installed overlays matching a glob
|
|
166
|
-
dotagents overlays sync --
|
|
171
|
+
dotagents overlays sync --prune # also clear what .gitignore/.ignore protect: exactly upstream
|
|
167
172
|
dotagents overlays remove python # delete the overlay dir, unpublish its skills, un-merge its rules
|
|
168
173
|
dotagents overlays show python # describe one: manifest, requires, setup, skills, source (--json)
|
|
169
174
|
```
|
|
@@ -184,13 +189,17 @@ dotagents overlays show python # describe one: manifest, requires, s
|
|
|
184
189
|
it as done.
|
|
185
190
|
- **`sync`** refreshes each installed overlay from the repo it was installed from
|
|
186
191
|
(recorded in `<overlay>/.dotagents-install.json`); `--repo` replaces that recorded
|
|
187
|
-
source for the run.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
`--
|
|
192
|
+
source for the run. The install is made to match the source exactly: new and changed
|
|
193
|
+
files land, `overlay.toml` is refreshed, and files the source does not ship are
|
|
194
|
+
removed. A file you edited (or added) there is copied to
|
|
195
|
+
`<store>/install_backup/<timestamp>/overlays/<name>/` before it is replaced or
|
|
196
|
+
removed. What the `.gitignore` / `.ignore` at the overlay's root match — setup
|
|
197
|
+
output, local state — and tool caches (`__pycache__`, `.venv`, …) are the install's own and
|
|
198
|
+
left alone; `--prune` clears them too, leaving exactly the upstream files. A
|
|
199
|
+
`requires` added upstream is installed, and published skill copies are refreshed.
|
|
200
|
+
`add` of an overlay already installed does the same (with `--prune` too). Keep your
|
|
201
|
+
own additions outside `<store>/overlays/` — a `kb/` file of your own, an
|
|
202
|
+
`AGENTS.local.md` — where a sync never touches them.
|
|
194
203
|
- **`remove`** deletes the overlay's directory, unpublishes the skills it published
|
|
195
204
|
(and their links in Claude's skills dir), and recomposes `AGENTS.md`'s managed block over the overlays that remain, so its
|
|
196
205
|
rules and routing leave with it. It refuses an overlay another installed overlay
|
|
@@ -96,7 +96,7 @@ python -m dotagents build-pyz --out dist/dotagents.pyz # or build it (needs a
|
|
|
96
96
|
holds the git checkouts and remote sources the repos materialize (the directory carries a
|
|
97
97
|
`.gitignore` of `*`, so a store kept in git never records them).
|
|
98
98
|
- `install_backup/<timestamp>/` — what `init --force` replaced, and what
|
|
99
|
-
`overlays
|
|
99
|
+
`overlays add` / `sync` replaced or removed that had been edited in an installed overlay.
|
|
100
100
|
- `dotagents/cmds/` — only if you create it: your own command modules (see
|
|
101
101
|
[Authoring → Custom commands](authoring.md#custom-commands)).
|
|
102
102
|
- `dotagents/config.toml` — only after `init --from`: records that base, so a later
|
|
@@ -73,10 +73,14 @@ store). Each overlay:
|
|
|
73
73
|
reads that dir sees the same skills.
|
|
74
74
|
|
|
75
75
|
`sync` refreshes each overlay from the repo it was installed from (`--repo` replaces
|
|
76
|
-
that source for the run)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
that source for the run) and makes the install exactly the source: changed files are
|
|
77
|
+
replaced and files the source does not ship are removed, anything edited there backed
|
|
78
|
+
up under `<store>/install_backup/` first. What the `.gitignore` / `.ignore` at the
|
|
79
|
+
overlay's root match (an ignore file in a subdirectory is just a file it ships), and
|
|
80
|
+
tool caches, belong to the install (a setup script's output, local
|
|
81
|
+
state) and are left alone; `--prune` clears them too. A `requires` added upstream is
|
|
82
|
+
installed. An installed overlay is upstream's copy, not a place for your edits: keep
|
|
83
|
+
those in your own files (a `kb/` file, `AGENTS.local.md`).
|
|
80
84
|
|
|
81
85
|
Removing an overlay deletes only its directory and unpublishes only the skills **it**
|
|
82
86
|
published (a copy the user edited is left alone). Its lines in `AGENTS.md`'s managed
|
|
@@ -23,7 +23,8 @@ carry the opinions.
|
|
|
23
23
|
composable **overlays** you layer in explicitly with `dotagents overlays add
|
|
24
24
|
<name>`. The overlays in this repo are examples — payloads riding on dotagents,
|
|
25
25
|
swappable for your own; see [Overlays](guide/overlays.md) for what each ships.
|
|
26
|
-
|
|
26
|
+
An installed overlay is kept exactly as upstream ships it (what the `.gitignore` /
|
|
27
|
+
`.ignore` at its root protect stays yours), so your own changes live in your own files.
|
|
27
28
|
- **Two scopes.** Config installs into a **user** store (`~/.agents`, configurable)
|
|
28
29
|
or a **project** store (`<project>/.agents`). Overlays, skills, commands, and env
|
|
29
30
|
files all resolve across the same scope precedence.
|
|
@@ -31,9 +31,9 @@ and an error saying a command is required, and exits 2.
|
|
|
31
31
|
| Command | Contract |
|
|
32
32
|
| --- | --- |
|
|
33
33
|
| `init [-g] [--dest D] [--from SRC] [--bin-dir D] [--agents a,b] [--no-hooks] [--powershell-env-hook] [--force] [--dry-run]` | Merge the base block into `<store>/AGENTS.md`, write `<store>/bin/dotagents[.cmd]`, and per active agent (`--agents`, else detected + `claude`) write its include and hooks. `--force` replaces `AGENTS.md`'s content, backed up under `<store>/install_backup/<timestamp>/`. `--from` records its base in `<store>/dotagents/config.toml`. |
|
|
34
|
-
| `overlays add NAME... [-g] [--repo SPEC]... [--copy] [--no-setup] [--setup-timeout S] [--no-requires] [--dry-run]` | Install each overlay and its `requires` into `<store>/overlays/<name
|
|
34
|
+
| `overlays add NAME... [-g] [--repo SPEC]... [--copy] [--no-setup] [--setup-timeout S] [--no-requires] [--prune] [--dry-run]` | Install each overlay and its `requires` into `<store>/overlays/<name>/` (an installed one is made to match its source again, as `sync` does; `--prune` clears its ignored files too), publish its skills, run its `setup.py`, recompose the managed block. Every name is resolved before anything is written; a source inside `<store>/overlays/` is refused; a fresh install whose setup fails is rolled back. No name: exit 2. |
|
|
35
35
|
| `overlays remove NAME... [-g] [--force] [--dry-run]` | Delete the overlay dirs, unpublish the skills they published, recompose the block. Refuses an overlay another installed overlay requires unless `--force`. No name: exit 2. |
|
|
36
|
-
| `overlays sync [GLOB] [-g] [--repo SPEC]... [--copy] [--
|
|
36
|
+
| `overlays sync [GLOB] [-g] [--repo SPEC]... [--copy] [--prune] [--no-setup] [--setup-timeout S] [--dry-run]` | Refresh installed overlays from the repo recorded at install (`--repo` replaces it): the install is made to match the source exactly: new and changed files land, `overlay.toml` is refreshed, files the source does not ship are removed; a file edited here is backed up first under `<store>/install_backup/<timestamp>/overlays/<name>/`. What the `.gitignore` / `.ignore` at the overlay's root match (setup output, local state) and tool caches are left alone; `--prune` clears them too. Installs new `requires`. Exits 1 when an overlay's repo cannot be loaded, its source lies inside `<store>/overlays/`, or its setup fails. |
|
|
37
37
|
| `overlays list [-g] [--repo SPEC]... [--json]` | Installed overlays per store (shadowed copies marked, unmet `requires` flagged) and what the repos offer. |
|
|
38
38
|
| `overlays show NAME [-g] [--repo SPEC]... [--json]` | One overlay: the copy a session would use, else the source's; manifest, setup, skills, file count, recorded source. No name: exit 2. |
|
|
39
39
|
| `context [OUT] [-g] [--agents a,b] [--format markdown\|system-reminder\|json] [--write-agent] [--inline]` | Print (or write to `OUT`) the assembled context. `--write-agent` merges it into each agent's `context_target` under the project root (not with `-g`). Exits 2 when `--agents` names no known agent. |
|
|
@@ -68,6 +68,10 @@ are skipped.
|
|
|
68
68
|
- `class Dotagents(LoggingArgs, Cli)` — the umbrella; field `cmdspath: list[str]`.
|
|
69
69
|
- `CMDS_PATH_ENV = "AGENTS_CMDS_PATH"`.
|
|
70
70
|
- Re-exported for command modules: `DotAgentsArgs`, `_write_stdout` (see below).
|
|
71
|
+
- `kebab_name(class_name) -> str` — a discovered command's default name when its
|
|
72
|
+
class declares no `_parsername_`: `LeakCheck` -> `leak-check`, `HTTPServer` ->
|
|
73
|
+
`http-server`, `my_cmd` -> `my-cmd`. `_parsername_` is optional in a command
|
|
74
|
+
module; an explicit one always wins.
|
|
71
75
|
|
|
72
76
|
## `dotagents.cli._common`
|
|
73
77
|
|
|
@@ -75,7 +79,8 @@ are skipped.
|
|
|
75
79
|
`global_scope: bool = False` (`--global`, `-g`) and
|
|
76
80
|
`agents_dir: Optional[Path] = None` (`--agents-dir`); `resolve_scope(*,
|
|
77
81
|
project_root=None) -> Scope` (via `_scope.resolve_scope`). Subclass it alone
|
|
78
|
-
(`class X(DotAgentsArgs)`), define `
|
|
82
|
+
(`class X(DotAgentsArgs)`), define `__call__(self) -> int` and optionally
|
|
83
|
+
`_parsername_` (default: `kebab_name` of the class, for a discovered module). A
|
|
79
84
|
subclass may redeclare a field with the same type and default to change its help.
|
|
80
85
|
- `_write_stdout(text) -> None` — write to stdout as UTF-8 whatever the console's
|
|
81
86
|
encoding (a bare `print` fails on a non-Latin-1 character under cp1252).
|
|
@@ -95,6 +100,23 @@ are skipped.
|
|
|
95
100
|
`OVERLAY_ROOT_NOTE` (`dotagents._overlays`), `resolve_user_store`
|
|
96
101
|
(`dotagents._scope`).
|
|
97
102
|
|
|
103
|
+
## `dotagents._ignore`
|
|
104
|
+
|
|
105
|
+
The `.gitignore` / `.ignore` at the ROOT of a directory, read as git reads a
|
|
106
|
+
`.gitignore` (pure stdlib) -- what an installed overlay keeps as its own when `add`
|
|
107
|
+
/ `sync` mirror its source. An ignore file in a subdirectory is an ordinary file;
|
|
108
|
+
nothing above the root is read.
|
|
109
|
+
|
|
110
|
+
- `IGNORE_FILES = (".gitignore", ".ignore")` — read at the root in that order, so
|
|
111
|
+
`.ignore` wins (ripgrep's precedence).
|
|
112
|
+
- `parse(text, base="") -> list` — one file's rules (`base`: its directory):
|
|
113
|
+
comments, `\#` / `\!` escapes, `!` negation, leading/middle `/` anchoring,
|
|
114
|
+
trailing `/` directory-only, `*`, `?`, `[...]`, `**`.
|
|
115
|
+
- `class IgnoreRules(rules=())` — `for_root(root)` reads `root`'s two files;
|
|
116
|
+
`ignored(rel, is_dir=False) -> bool` decides a `/`-joined path (a pattern without
|
|
117
|
+
a slash matches at any depth), a path under an ignored directory staying ignored
|
|
118
|
+
whatever a later `!` says, as in git.
|
|
119
|
+
|
|
98
120
|
## `dotagents._resources`
|
|
99
121
|
|
|
100
122
|
The package's own data; library code, importing nothing from `dotagents.cli`.
|
|
@@ -198,9 +220,9 @@ returns the exit code.
|
|
|
198
220
|
string arrays including multi-line strings, an integer); `None` plus one warning
|
|
199
221
|
when it is not valid TOML.
|
|
200
222
|
- `class InstallResult` — what `install_to` did: `written`, `unchanged`,
|
|
201
|
-
`
|
|
202
|
-
|
|
203
|
-
|
|
223
|
+
`replaced` (edited here, replaced by the source's file), `removed` (not in the
|
|
224
|
+
source, or cleared by `prune`), `backed_up`, `lines`; property `skipped`
|
|
225
|
+
(= unchanged). Unpacks as `(written, skipped, lines)`.
|
|
204
226
|
- `class Overlay(path, store=None)` — one overlay directory; `os.PathLike`; equal
|
|
205
227
|
and hashed by `path`; nothing read at construction.
|
|
206
228
|
- Class data: `NAME_RE`, `RESERVED_NAMES` (Windows device names),
|
|
@@ -246,12 +268,16 @@ returns the exit code.
|
|
|
246
268
|
- `rule_blocks(rel_paths) -> tuple[list[str], list[str]]` — `(blocks, warnings)`:
|
|
247
269
|
each file's leading `- **` bullets up to its next `## ` heading; a path outside
|
|
248
270
|
the overlay, missing or unreadable is a warning.
|
|
249
|
-
- `
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
271
|
+
- `ignore_rules() -> IgnoreRules` — the rules of the `.gitignore` / `.ignore`
|
|
272
|
+
at the overlay's root (`dotagents._ignore`).
|
|
273
|
+
- `install_to(dest_overlay_dir, dry_run, *, prune=False, backup_root=None,
|
|
274
|
+
source=None) -> InstallResult` — make the install exactly the overlay:
|
|
275
|
+
`files()` installed or replaced, `overlay.toml` refreshed, every other file
|
|
276
|
+
removed; what `ignore_rules()` matches, the install record and `SKIP_PARTS`
|
|
277
|
+
caches are never copied, replaced or removed unless `prune` (which clears
|
|
278
|
+
them, without backup); a replaced or removed file that is not what dotagents
|
|
279
|
+
installed is copied under `backup_root` first; writes the install record
|
|
280
|
+
(`source`, or the previous one when `None`).
|
|
255
281
|
- `OVERLAY_ROOT_NOTE: str` — the line emitted once above overlay routing lines that
|
|
256
282
|
use `$<NAME>_OVERLAY_ROOT`.
|
|
257
283
|
- `recompose_overlay_block(agents_md, base_block, overlays, dry_run, logger) -> bool`
|
|
@@ -553,10 +553,22 @@ class ClaudeAgent(Agent):
|
|
|
553
553
|
# `%q` quotes the path for the bash that sources the file: a `'` in a
|
|
554
554
|
# directory name used to leave an unterminated quote that broke every
|
|
555
555
|
# later source of $CLAUDE_ENV_FILE (and a crafted name ran commands).
|
|
556
|
+
# The file keeps ONE pin, the latest: an earlier `export
|
|
557
|
+
# AGENTS_PROJECT_ROOT=` line is dropped and every other line (another
|
|
558
|
+
# hook's) kept. Appending grew the file by a line per `cd`, and Claude Code
|
|
559
|
+
# inlines it into every Bash command (see SESSION_START_COMMAND). Pure bash,
|
|
560
|
+
# 3.2-compatible (macOS): no mapfile, no external command, so a `cd` stays
|
|
561
|
+
# instant.
|
|
556
562
|
CWD_CHANGED_COMMAND = (
|
|
557
563
|
'if [ -n "$CLAUDE_ENV_FILE" ] && [ -d .agents ]; then '
|
|
558
|
-
"
|
|
559
|
-
'
|
|
564
|
+
"kept=(); "
|
|
565
|
+
'if [ -f "$CLAUDE_ENV_FILE" ]; then '
|
|
566
|
+
'while IFS= read -r line || [ -n "$line" ]; do '
|
|
567
|
+
'case "$line" in "export AGENTS_PROJECT_ROOT="*) ;; *) kept+=("$line") ;; esac; '
|
|
568
|
+
'done < "$CLAUDE_ENV_FILE"; fi; '
|
|
569
|
+
'{ for line in ${kept[@]+"${kept[@]}"}; do printf \'%s\\n\' "$line"; done; '
|
|
570
|
+
"printf 'export AGENTS_PROJECT_ROOT=%q\\n' \"$(pwd -W 2>/dev/null || pwd)\"; "
|
|
571
|
+
'} > "$CLAUDE_ENV_FILE"; fi; '
|
|
560
572
|
"[ -f AGENTS.md ] && cat AGENTS.md || true"
|
|
561
573
|
)
|
|
562
574
|
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""The ``.gitignore`` / ``.ignore`` at the ROOT of a directory, read as git
|
|
2
|
+
reads a ``.gitignore`` -- what an installed overlay keeps as its own (setup
|
|
3
|
+
output, caches, local state) when ``overlays add`` / ``sync`` make the rest
|
|
4
|
+
of it match upstream.
|
|
5
|
+
|
|
6
|
+
Only the root's two files count: an ignore file in a subdirectory is an
|
|
7
|
+
ordinary file (the engineering overlay ships a ``references/.gitignore``
|
|
8
|
+
template), and nothing above the root is read. ``.ignore`` is the ripgrep /
|
|
9
|
+
fd convention, same syntax, read after ``.gitignore`` so its rules win.
|
|
10
|
+
|
|
11
|
+
Pure stdlib. Supported: blank lines and ``#`` comments, ``\\#`` / ``\\!``
|
|
12
|
+
escapes, ``!`` negation, a leading or middle ``/`` anchoring the pattern to
|
|
13
|
+
the root, a trailing ``/`` matching directories only, ``*``, ``?``,
|
|
14
|
+
``[...]`` and ``**``; a pattern without a slash matches at any depth. As in
|
|
15
|
+
git, a file under an ignored directory stays ignored whatever a later ``!``
|
|
16
|
+
says.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import re
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import Iterable, List, Optional
|
|
24
|
+
|
|
25
|
+
#: The ignore files read at the root, in order: a later one's rules win.
|
|
26
|
+
IGNORE_FILES = (".gitignore", ".ignore")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _translate(pattern: str) -> str:
|
|
30
|
+
"""A gitignore glob (without anchoring) as a regex for a ``/``-joined path."""
|
|
31
|
+
out, i, n = [], 0, len(pattern)
|
|
32
|
+
while i < n:
|
|
33
|
+
c = pattern[i]
|
|
34
|
+
if c == "*":
|
|
35
|
+
if pattern[i:i + 2] == "**":
|
|
36
|
+
i += 2
|
|
37
|
+
if i < n and pattern[i] == "/": # "**/": zero or more directories
|
|
38
|
+
out.append("(?:.*/)?")
|
|
39
|
+
i += 1
|
|
40
|
+
else: # a trailing "**": everything below
|
|
41
|
+
out.append(".*")
|
|
42
|
+
continue
|
|
43
|
+
out.append("[^/]*")
|
|
44
|
+
elif c == "?":
|
|
45
|
+
out.append("[^/]")
|
|
46
|
+
elif c == "[":
|
|
47
|
+
j = pattern.find("]", i + 1)
|
|
48
|
+
if j < 0:
|
|
49
|
+
out.append(re.escape(c))
|
|
50
|
+
else:
|
|
51
|
+
body = pattern[i + 1:j]
|
|
52
|
+
if body.startswith("!"):
|
|
53
|
+
body = "^" + body[1:]
|
|
54
|
+
out.append("[%s]" % body.replace("\\", "\\\\"))
|
|
55
|
+
i = j
|
|
56
|
+
elif c == "\\" and i + 1 < n:
|
|
57
|
+
i += 1
|
|
58
|
+
out.append(re.escape(pattern[i]))
|
|
59
|
+
else:
|
|
60
|
+
out.append(re.escape(c))
|
|
61
|
+
i += 1
|
|
62
|
+
return "".join(out)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class _Rule:
|
|
66
|
+
__slots__ = ("base", "regex", "negate", "dir_only")
|
|
67
|
+
|
|
68
|
+
def __init__(self, base: str, regex: "re.Pattern[str]", negate: bool, dir_only: bool) -> None:
|
|
69
|
+
self.base = base # the .gitignore's directory, "" or "a/b"
|
|
70
|
+
self.regex = regex
|
|
71
|
+
self.negate = negate
|
|
72
|
+
self.dir_only = dir_only
|
|
73
|
+
|
|
74
|
+
def matches(self, rel: str, is_dir: bool) -> bool:
|
|
75
|
+
if self.dir_only and not is_dir:
|
|
76
|
+
return False
|
|
77
|
+
if self.base:
|
|
78
|
+
if not rel.startswith(self.base + "/"):
|
|
79
|
+
return False
|
|
80
|
+
rel = rel[len(self.base) + 1:]
|
|
81
|
+
return bool(self.regex.fullmatch(rel))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def parse(text: str, base: str = "") -> "List[_Rule]":
|
|
85
|
+
"""The rules of one ``.gitignore`` whose directory is ``base``."""
|
|
86
|
+
rules: "List[_Rule]" = []
|
|
87
|
+
for raw in text.splitlines():
|
|
88
|
+
line = raw.rstrip("\r")
|
|
89
|
+
# Trailing spaces are ignored unless escaped.
|
|
90
|
+
stripped = line.rstrip(" ")
|
|
91
|
+
if stripped.endswith("\\") and len(line) > len(stripped):
|
|
92
|
+
stripped += " "
|
|
93
|
+
line = stripped
|
|
94
|
+
if not line or line.startswith("#"):
|
|
95
|
+
continue
|
|
96
|
+
negate = line.startswith("!")
|
|
97
|
+
if negate:
|
|
98
|
+
line = line[1:]
|
|
99
|
+
if line.startswith(("\\#", "\\!")):
|
|
100
|
+
line = line[1:]
|
|
101
|
+
dir_only = line.endswith("/")
|
|
102
|
+
line = line.rstrip("/")
|
|
103
|
+
if not line:
|
|
104
|
+
continue
|
|
105
|
+
anchored = "/" in line
|
|
106
|
+
line = line.lstrip("/")
|
|
107
|
+
body = _translate(line)
|
|
108
|
+
regex = re.compile(body if anchored else "(?:.*/)?" + body)
|
|
109
|
+
rules.append(_Rule(base, regex, negate, dir_only))
|
|
110
|
+
return rules
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class IgnoreRules:
|
|
114
|
+
"""The root ``.gitignore`` / ``.ignore`` rules of a directory, applied as
|
|
115
|
+
git applies a ``.gitignore``."""
|
|
116
|
+
|
|
117
|
+
def __init__(self, rules: "Iterable[_Rule]" = ()) -> None:
|
|
118
|
+
self.rules = list(rules)
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def for_root(cls, root: Path) -> "IgnoreRules":
|
|
122
|
+
"""The rules of ``root/.gitignore`` then ``root/.ignore`` (absent
|
|
123
|
+
files contribute nothing); subdirectories' ignore files are not read."""
|
|
124
|
+
rules: "List[_Rule]" = []
|
|
125
|
+
for name in IGNORE_FILES:
|
|
126
|
+
try:
|
|
127
|
+
rules.extend(parse((Path(root) / name).read_text(encoding="utf-8", errors="replace")))
|
|
128
|
+
except OSError:
|
|
129
|
+
continue
|
|
130
|
+
return cls(rules)
|
|
131
|
+
|
|
132
|
+
def _decide(self, rel: str, is_dir: bool) -> Optional[bool]:
|
|
133
|
+
verdict = None
|
|
134
|
+
for rule in self.rules:
|
|
135
|
+
if rule.matches(rel, is_dir):
|
|
136
|
+
verdict = not rule.negate
|
|
137
|
+
return verdict
|
|
138
|
+
|
|
139
|
+
def ignored(self, rel: str, is_dir: bool = False) -> bool:
|
|
140
|
+
"""Is ``rel`` (``/``-separated, relative to the root) ignored? A path
|
|
141
|
+
under an ignored directory is, whatever a later ``!`` says."""
|
|
142
|
+
parts = rel.strip("/").split("/")
|
|
143
|
+
for depth in range(1, len(parts)):
|
|
144
|
+
if self._decide("/".join(parts[:depth]), True):
|
|
145
|
+
return True
|
|
146
|
+
return bool(self._decide("/".join(parts), is_dir))
|