dotagents-cli 0.3.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.3.0/.gitignore +36 -0
- dotagents_cli-0.3.0/CHANGELOG.md +212 -0
- dotagents_cli-0.3.0/LICENSE +21 -0
- dotagents_cli-0.3.0/PKG-INFO +291 -0
- dotagents_cli-0.3.0/README.md +230 -0
- dotagents_cli-0.3.0/build.py +80 -0
- dotagents_cli-0.3.0/docs/api/agents.md +3 -0
- dotagents_cli-0.3.0/docs/api/cli.md +3 -0
- dotagents_cli-0.3.0/docs/api/context.md +3 -0
- dotagents_cli-0.3.0/docs/api/env.md +3 -0
- dotagents_cli-0.3.0/docs/api/hooks.md +3 -0
- dotagents_cli-0.3.0/docs/api/merge.md +3 -0
- dotagents_cli-0.3.0/docs/api/overlays.md +3 -0
- dotagents_cli-0.3.0/docs/api/reference.md +24 -0
- dotagents_cli-0.3.0/docs/api/resolve.md +3 -0
- dotagents_cli-0.3.0/docs/api/scope.md +3 -0
- dotagents_cli-0.3.0/docs/api/skills.md +3 -0
- dotagents_cli-0.3.0/docs/api/sync.md +3 -0
- dotagents_cli-0.3.0/docs/changelog.md +3 -0
- dotagents_cli-0.3.0/docs/guide/authoring.md +87 -0
- dotagents_cli-0.3.0/docs/guide/commands.md +233 -0
- dotagents_cli-0.3.0/docs/guide/install.md +54 -0
- dotagents_cli-0.3.0/docs/guide/overlays.md +100 -0
- dotagents_cli-0.3.0/docs/guide/private-sync.md +75 -0
- dotagents_cli-0.3.0/docs/index.md +60 -0
- dotagents_cli-0.3.0/install.py +92 -0
- dotagents_cli-0.3.0/mkdocs.yml +68 -0
- dotagents_cli-0.3.0/pyproject.toml +61 -0
- dotagents_cli-0.3.0/src/dotagents/AGENTS.md +125 -0
- dotagents_cli-0.3.0/src/dotagents/__init__.py +3 -0
- dotagents_cli-0.3.0/src/dotagents/__main__.py +6 -0
- dotagents_cli-0.3.0/src/dotagents/_agents.py +627 -0
- dotagents_cli-0.3.0/src/dotagents/_context.py +311 -0
- dotagents_cli-0.3.0/src/dotagents/_env.py +600 -0
- dotagents_cli-0.3.0/src/dotagents/_hooks.py +172 -0
- dotagents_cli-0.3.0/src/dotagents/_merge.py +122 -0
- dotagents_cli-0.3.0/src/dotagents/_overlay/AGENTS.md +31 -0
- dotagents_cli-0.3.0/src/dotagents/_overlay/CLAUDE.md +3 -0
- dotagents_cli-0.3.0/src/dotagents/_overlay/README.md +41 -0
- dotagents_cli-0.3.0/src/dotagents/_overlay/dotagents/DECISIONS.md +20 -0
- dotagents_cli-0.3.0/src/dotagents/_overlay/dotagents/cmds/README.md +46 -0
- dotagents_cli-0.3.0/src/dotagents/_overlays.py +412 -0
- dotagents_cli-0.3.0/src/dotagents/_resolve.py +81 -0
- dotagents_cli-0.3.0/src/dotagents/_scope.py +265 -0
- dotagents_cli-0.3.0/src/dotagents/_skills.py +236 -0
- dotagents_cli-0.3.0/src/dotagents/_sync.py +72 -0
- dotagents_cli-0.3.0/src/dotagents/_wrappers.py +112 -0
- dotagents_cli-0.3.0/src/dotagents/cli/__init__.py +354 -0
- dotagents_cli-0.3.0/src/dotagents/cli/_common.py +331 -0
- dotagents_cli-0.3.0/src/dotagents/cli/build_pyz.py +107 -0
- dotagents_cli-0.3.0/src/dotagents/cli/context.py +129 -0
- dotagents_cli-0.3.0/src/dotagents/cli/env.py +168 -0
- dotagents_cli-0.3.0/src/dotagents/cli/init.py +117 -0
- dotagents_cli-0.3.0/src/dotagents/cli/overlays.py +366 -0
- dotagents_cli-0.3.0/tests/test_agents.py +184 -0
- dotagents_cli-0.3.0/tests/test_audit_leak.py +71 -0
- dotagents_cli-0.3.0/tests/test_cmds_discovery.py +330 -0
- dotagents_cli-0.3.0/tests/test_context.py +198 -0
- dotagents_cli-0.3.0/tests/test_env.py +416 -0
- dotagents_cli-0.3.0/tests/test_env_format.py +230 -0
- dotagents_cli-0.3.0/tests/test_hooks.py +149 -0
- dotagents_cli-0.3.0/tests/test_overlays_command.py +615 -0
- dotagents_cli-0.3.0/tests/test_scope.py +126 -0
- dotagents_cli-0.3.0/tests/test_wire_hooks.py +439 -0
- dotagents_cli-0.3.0/tests/test_wrappers.py +127 -0
- dotagents_cli-0.3.0/tools/audit.py +261 -0
- dotagents_cli-0.3.0/tools/cloud-setup.sh +294 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Private agent config. No trailing slash: `dotagents link-project` makes .agents
|
|
2
|
+
# a symlink, which git treats as a file, so a directory-only `.agents/` would not
|
|
3
|
+
# match it. The design log lives INSIDE .agents/dotagents/ and is private like
|
|
4
|
+
# every other project's (D61, reversing D56); CHANGELOG.md cites no D-numbers.
|
|
5
|
+
.agents
|
|
6
|
+
*.local.md
|
|
7
|
+
CLAUDE.local.md
|
|
8
|
+
.claude/
|
|
9
|
+
|
|
10
|
+
# Python
|
|
11
|
+
__pycache__/
|
|
12
|
+
*.py[cod]
|
|
13
|
+
*.egg-info/
|
|
14
|
+
src/*.egg-info/
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.mypy_cache/
|
|
18
|
+
.tox/
|
|
19
|
+
.coverage
|
|
20
|
+
htmlcov/
|
|
21
|
+
|
|
22
|
+
# dotagents CLI package build artifacts (release artifacts, not committed)
|
|
23
|
+
dist/
|
|
24
|
+
*.pyz
|
|
25
|
+
.venv/
|
|
26
|
+
.venv-test/
|
|
27
|
+
|
|
28
|
+
# MkDocs build output
|
|
29
|
+
site/
|
|
30
|
+
|
|
31
|
+
# OS/editor
|
|
32
|
+
.DS_Store
|
|
33
|
+
Thumbs.db
|
|
34
|
+
.idea/
|
|
35
|
+
.vscode/
|
|
36
|
+
_staging/
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.3.0] - 2026-07-24
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **BREAKING** — `dotagents link` / `dotagents sync` are gone from the CLI. They are
|
|
15
|
+
the private-sync workflow's commands, not dotagents' core, so they moved — together
|
|
16
|
+
with the logic behind them (`src/dotagents/_link.py`) — into the opt-in
|
|
17
|
+
**`private-sync` overlay**, and were renamed to say what they act on:
|
|
18
|
+
|
|
19
|
+
dotagents link-project . # was: dotagents link .
|
|
20
|
+
dotagents sync-project -m "msg" # was: dotagents sync -m "msg"
|
|
21
|
+
|
|
22
|
+
Install the overlay to get them back: `dotagents overlays add private-sync --source
|
|
23
|
+
<overlays-checkout>`. A plain dotagents now ships no private-sync workflow at all;
|
|
24
|
+
its whole command surface is `init` / `build-pyz` / `context` / `env` / `overlays`,
|
|
25
|
+
and everything else is discovered from an overlay or from your own `cmds/` modules.
|
|
26
|
+
`tools/cloud-setup.sh` installs the overlay before linking, so the cloud bootstrap
|
|
27
|
+
is unaffected.
|
|
28
|
+
- The bundled `dotagents/cmds/` directory now ships no command module of its own, but
|
|
29
|
+
`init` still creates it: it is the documented drop-in point for your own commands
|
|
30
|
+
(a `README.md` beside it explains the shape and the precedence rules).
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- feat: `init` wires agent hooks and links the shared skills dir, for each active
|
|
35
|
+
agent with a published hook schema (today: Claude and Codex). `--no-hooks` skips it.
|
|
36
|
+
|
|
37
|
+
**Claude** (`~/.claude/settings.json`) gets `SessionStart`, which appends
|
|
38
|
+
`dotagents env --diff --format export` to `$CLAUDE_ENV_FILE` and then runs
|
|
39
|
+
`dotagents context` — Claude sources that file before each Bash command and injects
|
|
40
|
+
the hook's stdout into the session context, so both the env layers and the assembled
|
|
41
|
+
context reach the session automatically. It also gets `CwdChanged`, surfacing a
|
|
42
|
+
directory's `AGENTS.md`. The env redirect **appends** and is guarded against an
|
|
43
|
+
unset variable, both as the hooks docs require.
|
|
44
|
+
|
|
45
|
+
**Codex** (`~/.codex/hooks.json`, or `$CODEX_HOME`) gets `SessionStart` running
|
|
46
|
+
`dotagents context`; its hook JSON is structurally identical to Claude's. We write
|
|
47
|
+
`hooks.json` rather than touching your `config.toml` for hooks.
|
|
48
|
+
|
|
49
|
+
Codex's env arrives differently: it has no `$CLAUDE_ENV_FILE` equivalent, reads no
|
|
50
|
+
`.env` files, and has no event that fires before config load, so
|
|
51
|
+
`dotagents init --agents codex` writes a `# dotagents:begin/end` managed block
|
|
52
|
+
containing `[shell_environment_policy].set` into `config.toml`. **Only on an
|
|
53
|
+
explicit `--agents`** — this edits your main config with values that go stale, so
|
|
54
|
+
auto-detection never triggers it. The block is appended and refreshed in place
|
|
55
|
+
(everything outside the markers is untouched) and `set` merges rather than
|
|
56
|
+
replaces. **The values are a static snapshot: re-run `init` after changing your env
|
|
57
|
+
layers.** Identity vars describe the target agent, so initializing from Claude still
|
|
58
|
+
writes `AGENT = "codex"`; `PATH` is excluded, since `set` overrides per subprocess
|
|
59
|
+
and a baked-in `PATH` would replace the inherited one.
|
|
60
|
+
|
|
61
|
+
The merge is additive and idempotent: unrelated keys and hooks you wrote yourself
|
|
62
|
+
survive verbatim, malformed entries are dropped rather than raising, and re-running
|
|
63
|
+
writes nothing.
|
|
64
|
+
|
|
65
|
+
`init` also links `<scope>/skills/` into the agent's config dir, closing the last
|
|
66
|
+
mile for overlay-published skills — publishing only helps if the agent reads that
|
|
67
|
+
directory. Symlink where the OS permits, copy otherwise (a copy is a snapshot;
|
|
68
|
+
re-run `init` to refresh).
|
|
69
|
+
|
|
70
|
+
- feat: `tools/leak_check.py` now also scans commit messages (current branch history)
|
|
71
|
+
for agent-session trailers/URLs — a `Claude-Session:` trailer or `claude.ai/code/session`
|
|
72
|
+
link — and exits 1 on any hit. The trailer is auto-added by the agent harness and
|
|
73
|
+
exposes a session id in public history if it slips through; the pre-existing tracked-file
|
|
74
|
+
scan didn't cover commit messages. `flows/REPO.md` release discipline documents the check
|
|
75
|
+
and the `git filter-branch --msg-filter` remediation for one that already landed.
|
|
76
|
+
- feat: `tools/cloud-setup.sh` step 5 wires `hooks/settings.snippet.json` into the
|
|
77
|
+
user-level `~/.claude/settings.json` (idempotent JSON merge, preserves existing
|
|
78
|
+
settings/hooks). A fresh cloud container has no settings file and nothing else
|
|
79
|
+
created one, so the SessionStart pull/link and Stop sync-back hooks never ran —
|
|
80
|
+
the private repo went stale and session changes were silently never pushed back.
|
|
81
|
+
`kb/PRIVATE_SYNC.md` documents the auto-wiring (manual merge still applies on
|
|
82
|
+
local machines).
|
|
83
|
+
|
|
84
|
+
### Fixed
|
|
85
|
+
|
|
86
|
+
- fix: `dotagents link`/`sync` never adopt or copy back a `<project>/.agents` that is
|
|
87
|
+
itself a git checkout (`.git` present — dir, or file for worktrees). A hosted-runner
|
|
88
|
+
session that lists the agents repo as a *source* gets it cloned to
|
|
89
|
+
`<project>/.agents` by the harness; first-link adoption then moved that entire
|
|
90
|
+
checkout — `.git`, foreign proxy remote, session branch — into
|
|
91
|
+
`~/.agents/projects/<name>/`, nesting a repo inside the private repo, which a later
|
|
92
|
+
sync's `git add -A` would push as a bare gitlink (and `sync`'s copy-back had the same
|
|
93
|
+
swallow, with `overwrite=True`). Both paths now log a skip and leave the checkout in
|
|
94
|
+
place; `link --force` keeps an escape hatch that backs the checkout up to
|
|
95
|
+
`.agents.bak*` (git state intact) and links the store.
|
|
96
|
+
|
|
97
|
+
- fix: `dotagents sync` now authenticates the private repo directly against github.com
|
|
98
|
+
when `DOTAGENTS_AGENTS_TOKEN` is set — and on a hosted runner that rewrites github
|
|
99
|
+
traffic to a scoped in-session proxy, bypasses the rewrite — so a **standalone**
|
|
100
|
+
`dotagents sync` no longer 403s. Previously only the private-sync Stop hook worked
|
|
101
|
+
(it sources `_agents-git-auth.sh`); a direct CLI run had no bypass, so its pull failed
|
|
102
|
+
(`could not read Password`) and its push returned HTTP 403 through the proxy. The CLI
|
|
103
|
+
now ports that logic: a per-command `-c` credential helper in a normal environment, or
|
|
104
|
+
an isolated `GIT_CONFIG_GLOBAL` (identity + CA bundle preserved) that skips the rewrite
|
|
105
|
+
when one is active. The token is still read from the environment at auth time and never
|
|
106
|
+
written to `.git/config`.
|
|
107
|
+
- fix: `tools/cloud-setup.sh` no longer lets a single container-start clone failure
|
|
108
|
+
permanently disable the environment. The clone often loses a race with egress/proxy
|
|
109
|
+
readiness; previously it `exit 0`'d on the first failure, skipping the hook-wiring
|
|
110
|
+
step — so the SessionStart hook (which can itself re-clone) was never registered and
|
|
111
|
+
nothing ever recovered. Now the clone retries with backoff (5 attempts), and if it
|
|
112
|
+
still fails the script persists a copy of itself and wires a SessionStart **recovery
|
|
113
|
+
hook** that re-runs the bootstrap next session (egress is up by then); the first
|
|
114
|
+
successful run merges the private-sync hooks and removes the recovery hook.
|
|
115
|
+
- fix: `tools/cloud-setup.sh` also wires that recovery hook when
|
|
116
|
+
`DOTAGENTS_AGENTS_REMOTE` is **unset at setup time**, not only on clone failure.
|
|
117
|
+
Hosted runners often expose the remote/token secrets to session processes but not
|
|
118
|
+
to the setup-script phase, so the first bootstrap had no remote to clone and its
|
|
119
|
+
no-remote branch just `exit 0`'d, leaving nothing to retry — the environment stayed
|
|
120
|
+
dead every session (observed: setup ran the correct one-liner and emitted only the
|
|
121
|
+
banner + `skipping` line, ~154 bytes, no clone). The branch now persists the recovery
|
|
122
|
+
hook like the exhausted-clone path, so the next session — where the secret is present
|
|
123
|
+
— clones and self-removes the hook. A genuinely remote-less environment just re-skips
|
|
124
|
+
each session (idempotent; the hook never duplicates). (Durable fix is still
|
|
125
|
+
to expose the secrets to the Setup Script phase so the first container succeeds.)
|
|
126
|
+
- fix: `.gitignore` templates and `dotagents link` now use a slashless `.agents`
|
|
127
|
+
instead of `.agents/`. `link` creates `.agents` as a *symlink*, which git treats
|
|
128
|
+
as a file, so the directory-only `.agents/` pattern never actually ignored it —
|
|
129
|
+
the link showed up as untracked in every project. `_gitignore_excludes_agents`
|
|
130
|
+
is now symlink-aware (a bare `.agents/` no longer counts as excluding a symlinked
|
|
131
|
+
link, so the WARN fires), and the reference template, REPO.md guidance, and the
|
|
132
|
+
starter `_overlay/AGENTS.md` Leakage rule all recommend `.agents`.
|
|
133
|
+
- docs: recommend `curl … -o file && sh file` over `curl … | sh` for the setup-script
|
|
134
|
+
field (README, `kb/PRIVATE_SYNC.md`, `tools/cloud-setup.sh` header). With a pipe the
|
|
135
|
+
field's exit code is `sh`'s (0 on empty stdin), so a failed fetch at container start
|
|
136
|
+
is silently reported as success; `&&` propagates the fetch failure to the setup log.
|
|
137
|
+
|
|
138
|
+
### Changed
|
|
139
|
+
|
|
140
|
+
- refactor: move the cloud bootstrap from `overlays/private-sync/hooks/cloud-setup.sh` to
|
|
141
|
+
top-level `tools/cloud-setup.sh` (public, required tooling) so a fresh cloud container
|
|
142
|
+
can fetch-and-run it from the public repo instead of pasting its contents — the web
|
|
143
|
+
environment setup-script field becomes a one-liner
|
|
144
|
+
(`curl -fsSL …/tools/cloud-setup.sh | sh`) that stays current on every container start.
|
|
145
|
+
Docs (README, `kb/PRIVATE_SYNC.md`) updated to the download bootstrap.
|
|
146
|
+
- fix: `tools/cloud-setup.sh` prints `starting`/`done` banners (so a setup-script log
|
|
147
|
+
proves whether it executed — a blank log means the field never invoked it, a config
|
|
148
|
+
issue) and `mkdir -p "$HOME"` before `git config --global` (which fails if HOME isn't
|
|
149
|
+
created yet in some setup contexts).
|
|
150
|
+
|
|
151
|
+
## [0.2.0] - 2026-07-19
|
|
152
|
+
|
|
153
|
+
### Changed
|
|
154
|
+
|
|
155
|
+
- chore: migrate the CLI to `duho>=0.3.3` (was `>=0.1.1`). duho's Plan-13 `Args`/`Cmd`
|
|
156
|
+
split means commands are now `class X(LoggingArgs, Cmd)` with a `__call__` entrypoint
|
|
157
|
+
(was a bare `LoggingArgs` with `__run__`) and the umbrella root is
|
|
158
|
+
`class Dotagents(LoggingArgs, Cli)`. Field declarations (annotation + help string +
|
|
159
|
+
flags tuple) are unchanged. Bumped the `build-pyz` vendored `duho` default to 0.3.3.
|
|
160
|
+
- fix: restore full flag/help fidelity in the built `dotagents.pyz` under duho 0.3.3.
|
|
161
|
+
duho discovers each field's flags + help by AST-parsing its module source, and inside
|
|
162
|
+
a zipapp the zip-internal `__file__` isn't readable — degrading `--from` to `--from-`,
|
|
163
|
+
the `link` positional to `--path`, and dropping help text. `cli.main` now repoints the
|
|
164
|
+
affected module sources (`dotagents.cli`, `duho.presets`) to extracted temp files
|
|
165
|
+
before dispatch; a no-op for a plain install.
|
|
166
|
+
|
|
167
|
+
### Added
|
|
168
|
+
|
|
169
|
+
- feat: private-agents git sync — `dotagents link` symlinks a project's `.agents` to a
|
|
170
|
+
per-project store under the global `~/.agents/projects/<name>` (basename-keyed, so a
|
|
171
|
+
local and a cloud checkout converge on the same store), adopting an existing real
|
|
172
|
+
`.agents/` into an empty store on the first link; `--copy` mirrors it as a real dir
|
|
173
|
+
for no-symlink environments (with automatic fallback), `--force` handles conflicts.
|
|
174
|
+
`dotagents sync` runs `git pull --rebase`/commit/push on the private repo, copies a
|
|
175
|
+
copy-mode project's `.agents` back into its store first (`--project`), and bootstraps
|
|
176
|
+
a fresh repo in one command (`--remote`). Logic in `src/dotagents/_link.py`; the model
|
|
177
|
+
keeps per-user config and every project's private `.agents` in one private repo while
|
|
178
|
+
the public project repos track none of it (the Leakage rule already `.gitignore`s
|
|
179
|
+
`.agents/`).
|
|
180
|
+
- feat: `overlays/private-sync/` overlay — `kb/PRIVATE_SYNC.md` (the model, commands,
|
|
181
|
+
first-time + cloud setup, auth, gotchas) plus `hooks/private-sync-{start,stop}.sh`
|
|
182
|
+
(SessionStart clone/pull + link, Stop sync-back) and a `settings.snippet.json` for
|
|
183
|
+
`~/.claude/settings.json`, so cloud sessions link and sync automatically. Cloud auth is
|
|
184
|
+
a fine-grained PAT via `DOTAGENTS_AGENTS_TOKEN`, wired through a git credential helper
|
|
185
|
+
that reads it from the environment (never persisted to `.git/config`);
|
|
186
|
+
`hooks/_agents-git-auth.sh` auto-detects a hosted-runner `github.com`→in-session-proxy
|
|
187
|
+
`insteadOf` rewrite and bypasses it (isolated git config) so token auth reaches the
|
|
188
|
+
real github.com for a private repo outside the session's scope. `hooks/cloud-setup.sh`
|
|
189
|
+
is a self-contained container-start bootstrap (inlines auth + bypass, so it runs before
|
|
190
|
+
`~/.agents` exists) that clones/pulls the repo, installs the CLI, and links the project
|
|
191
|
+
— for the web environment's setup-script field, solving the first-clone chicken-and-egg
|
|
192
|
+
the SessionStart hook can't.
|
|
193
|
+
- feat: installable `dotagents` CLI package (`src/dotagents/`, built on `duho` for
|
|
194
|
+
the argument surface and `pathlib_next` for copy/URI handling) exposing `init`
|
|
195
|
+
(lay down the neutral base overlay), `install` (base plus opt-in overlays via
|
|
196
|
+
repeatable `--overlays <path>`, copied additively), `audit` (wraps
|
|
197
|
+
`tools/audit_config.py`), and `build-pyz` (vendors pinned `duho`/`pathlib_next`
|
|
198
|
+
via `pip install --target` + `zipapp` into a self-contained, downloadable
|
|
199
|
+
`dotagents.pyz`). `init`'s `AGENTS.md`/`CLAUDE.md` are merged as a
|
|
200
|
+
marker-delimited managed block so re-running never clobbers customizations
|
|
201
|
+
outside the block. `install --bin-dir` writes `dotagents`/`dotagents.cmd`
|
|
202
|
+
wrappers. `install.py` is a thin shim over `dotagents.cli.main()`.
|
|
203
|
+
- Config content is a **neutral base overlay** (`src/dotagents/_overlay/` — the
|
|
204
|
+
`AGENTS.md` scaffolding + design-log convention `init` writes) plus **opt-in
|
|
205
|
+
overlays** (`overlays/<name>/`): `flows` (PLAN/EXEC/REVIEW/REPO + MODELS),
|
|
206
|
+
`recovery`, `references`, `python`/`node`/`rust`, `agents`, `tools`. Each carries
|
|
207
|
+
an `overlay.toml` manifest for a future `dotagents overlays` subcommand.
|
|
208
|
+
- Repo layout: the CLI in `src/dotagents/`, config overlays in `overlays/`, required
|
|
209
|
+
tooling in top-level `tools/` (`audit_config.py`, `leak_check.py`); repo root holds
|
|
210
|
+
the installer, CI, repo-development directives, and the tracked, sanitized
|
|
211
|
+
`.agents/` design log (index + per-decision files) + plans. `audit_config.py` has
|
|
212
|
+
`--repo-hygiene` (scans tracked files for personal/machine-specific leftovers).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jose A.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dotagents-cli
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: The dotagents CLI: install and manage a portable ~/.agents config for AI coding agents
|
|
5
|
+
Project-URL: Homepage, https://github.com/jose-pr/dotagents/
|
|
6
|
+
Project-URL: Repository, https://github.com/jose-pr/dotagents
|
|
7
|
+
Project-URL: Documentation, https://jose-pr.github.io/dotagents/
|
|
8
|
+
Author: Jose A.
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026 Jose A.
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Classifier: Development Status :: 4 - Beta
|
|
32
|
+
Classifier: Environment :: Console
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Operating System :: OS Independent
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
43
|
+
Requires-Python: >=3.9
|
|
44
|
+
Requires-Dist: duho>=0.4.0
|
|
45
|
+
Requires-Dist: pathlib-next>=0.8.0
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
48
|
+
Provides-Extra: docs
|
|
49
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
50
|
+
Requires-Dist: mkdocs>=1.5; extra == 'docs'
|
|
51
|
+
Requires-Dist: mkdocstrings[python]>=0.24; extra == 'docs'
|
|
52
|
+
Provides-Extra: http
|
|
53
|
+
Requires-Dist: pathlib-next[http]; extra == 'http'
|
|
54
|
+
Provides-Extra: s3
|
|
55
|
+
Requires-Dist: pathlib-next[s3]; extra == 's3'
|
|
56
|
+
Provides-Extra: sftp
|
|
57
|
+
Requires-Dist: pathlib-next[sftp]; extra == 'sftp'
|
|
58
|
+
Provides-Extra: uri
|
|
59
|
+
Requires-Dist: pathlib-next[uri]; extra == 'uri'
|
|
60
|
+
Description-Content-Type: text/markdown
|
|
61
|
+
|
|
62
|
+
# dotagents
|
|
63
|
+
|
|
64
|
+
[](https://github.com/jose-pr/dotagents/actions/workflows/test.yml)
|
|
65
|
+
[](https://jose-pr.github.io/dotagents/)
|
|
66
|
+
[](https://github.com/jose-pr/dotagents/blob/main/LICENSE)
|
|
67
|
+
|
|
68
|
+
Like dotfiles, but for AI coding agents: a portable, token-budgeted `~/.agents`
|
|
69
|
+
configuration that works across agent runners (Claude Code, Antigravity, Copilot,
|
|
70
|
+
Codex, ...). dotagents is the **mechanism** — install a neutral base, then layer in
|
|
71
|
+
opt-in **overlays** that carry your standards (repo structure, CI/release discipline,
|
|
72
|
+
whatever workflows you want) — so you record them once instead of restating them every
|
|
73
|
+
session.
|
|
74
|
+
|
|
75
|
+
## Design
|
|
76
|
+
|
|
77
|
+
- **Core + load-on-demand routing.** `AGENTS.md` is the only always-loaded file: a
|
|
78
|
+
handful of always-on rules plus a routing table. Task-specific detail lives in
|
|
79
|
+
`flows/` and `kb/` files that an agent reads only when the task matches. You pay for
|
|
80
|
+
what you use.
|
|
81
|
+
- **A neutral base + opt-in overlays.** `init` lays down a minimal, opinion-free
|
|
82
|
+
**base overlay** (just the `AGENTS.md` scaffolding + design-log convention).
|
|
83
|
+
Everything opinionated — workflows, language `kb/` files, repo templates, tools —
|
|
84
|
+
lives in composable **overlays** you layer in explicitly
|
|
85
|
+
(`dotagents overlays add <name>`), each contributing its own routing lines, rules,
|
|
86
|
+
skills, and commands. Additive-only: overlays never overwrite something you've
|
|
87
|
+
already customized.
|
|
88
|
+
- **Overlays carry the opinions, not the tool.** dotagents is the mechanism
|
|
89
|
+
(install, compose, discover); *what* your agents should do is an overlay concern.
|
|
90
|
+
The example overlays in this repo are a starting point — a planning/execution/review
|
|
91
|
+
workflow set, language conventions, a release helper — but they're payloads riding on
|
|
92
|
+
dotagents, swappable for your own. See the [docs](https://jose-pr.github.io/dotagents/)
|
|
93
|
+
for what each ships.
|
|
94
|
+
|
|
95
|
+
## Layout
|
|
96
|
+
|
|
97
|
+
The config is a **base overlay** plus opt-in **overlays**; the `dotagents` CLI applies
|
|
98
|
+
them. Everything else is repo infrastructure.
|
|
99
|
+
|
|
100
|
+
| Path | What |
|
|
101
|
+
| --- | --- |
|
|
102
|
+
| `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 |
|
|
103
|
+
| `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 |
|
|
104
|
+
| `tools/` | Required tooling (not an overlay): `cloud-setup.sh` (`leak-check` moved to the opt-in `leak-check` overlay, D84) |
|
|
105
|
+
| `install.py` | Thin shim over `dotagents.cli.main()`, kept at this filename for muscle memory |
|
|
106
|
+
|
|
107
|
+
The **example overlays** — the `flows` workflow set, per-language `kb/` + templates,
|
|
108
|
+
`references`, `release`, `private-sync`, `net`, `recovery`, `tools` — live on a separate
|
|
109
|
+
[`overlays` branch](https://github.com/jose-pr/dotagents/tree/overlays), not in `main`'s
|
|
110
|
+
tree: they are swappable payloads, not part of the tool. `dotagents overlays add <name>`
|
|
111
|
+
resolves them from there (or from any `--source`). See the
|
|
112
|
+
[docs](https://jose-pr.github.io/dotagents/) for what each ships.
|
|
113
|
+
|
|
114
|
+
Named-agent directives aren't a shipped overlay — a named agent (Claude, Antigravity,
|
|
115
|
+
…) just reads its own `~/.agents/<agent>.md` on top of the shared `AGENTS.md`, which the
|
|
116
|
+
base overlay's routing already states. This config's own design log lives **privately**
|
|
117
|
+
under its untracked `.agents/dotagents/` (`DECISIONS.md` + one file per decision) — like
|
|
118
|
+
every project, `.agents/` is never tracked or pushed.
|
|
119
|
+
|
|
120
|
+
Each overlay's `<name>/overlay.toml` carries a `name`/`description`/`requires`/`routing`
|
|
121
|
+
manifest read by the `dotagents overlays` subcommand, which manages overlays by name
|
|
122
|
+
(`add`/`remove`/`list`/`sync`) — see [Managing overlays](#managing-overlays) below.
|
|
123
|
+
|
|
124
|
+
## Install
|
|
125
|
+
|
|
126
|
+
**`dotagents init`** lays down the neutral base config; a self-contained downloadable
|
|
127
|
+
`.pyz` needs no `pip install` at all.
|
|
128
|
+
|
|
129
|
+
`init` writes the `.agents/` scaffolding — the `AGENTS.md` managed block, the per-agent
|
|
130
|
+
`<CLAUDE|ANTIGRAVITY|...>.md → @AGENTS.md` pattern, the design-log convention — and
|
|
131
|
+
wires each supporting agent's hooks so `dotagents context` reaches it automatically at
|
|
132
|
+
session start (`--no-hooks` opts out) — but
|
|
133
|
+
imposes no opinions (those come from `overlays add`). Its `AGENTS.md`/`CLAUDE.md` are a
|
|
134
|
+
marker-delimited managed block, so re-running `init` never clobbers what you've added
|
|
135
|
+
around it. **Scope**: project by default (`<cwd>/.agents`), or the user store with
|
|
136
|
+
`-g`/`--global` (`~/.agents`).
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
dotagents init # project: <cwd>/.agents
|
|
140
|
+
dotagents init -g # user store: ~/.agents
|
|
141
|
+
dotagents init --bin-dir ~/.local/bin # also write a `dotagents` command on PATH
|
|
142
|
+
dotagents init --dry-run # show what would happen
|
|
143
|
+
dotagents init --force # replace AGENTS.md/CLAUDE.md wholesale (backed up)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`--from <path-or-uri>` selects the *base* source for a `pip install`-only environment
|
|
147
|
+
(a git checkout dir, `file:`, `http(s):`, `zip:`, `sftp:`, or `s3:` URI via
|
|
148
|
+
`pip install "dotagents-cli[uri]"`); `init`'s base ships inside the package, so it needs no
|
|
149
|
+
`--from`.
|
|
150
|
+
|
|
151
|
+
Overlays beyond the base are managed by name with `dotagents overlays add <name>` — it
|
|
152
|
+
installs into `<scope>/.agents/overlays/<name>/` (discoverable) and publishes the
|
|
153
|
+
overlay's skills into the shared skills dir. See below.
|
|
154
|
+
|
|
155
|
+
### Managing overlays
|
|
156
|
+
|
|
157
|
+
`dotagents overlays` manages opt-in overlays **by name**, resolving each name against a
|
|
158
|
+
source directory of overlays — point `--source <dir>` (or `$AGENTS_OVERLAYS_SRC`) at
|
|
159
|
+
one, e.g. a checkout of the [`overlays` branch](https://github.com/jose-pr/dotagents/tree/overlays)
|
|
160
|
+
where the example overlays live. Installed overlays are *discovered* by their presence
|
|
161
|
+
under `<scope>/.agents/overlays/` — there is no registry file.
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
dotagents overlays add python flows # install into the scope, publish skills, merge D59 rules/routing
|
|
165
|
+
dotagents overlays list # installed (discovered) + available (from source)
|
|
166
|
+
dotagents overlays sync 'py*' # refresh installed overlays matching a glob, resync their skills
|
|
167
|
+
dotagents overlays remove python # delete the overlay dir + unpublish its skills
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Scope is **project** by default (`<project>/.agents/`, when run inside one) or **user**
|
|
171
|
+
with `-g`/`--global` (`~/.agents/`, the configurable store). Each overlay installs as a
|
|
172
|
+
directory (kept, discoverable), its `routing`/`rules` merge additively into `AGENTS.md`'s
|
|
173
|
+
managed block, and its `skills/<name>/` are symlinked (or `--copy`'d, for Windows /
|
|
174
|
+
no-symlink) into the shared `<scope>/.agents/skills/` so every agent sees the same skills.
|
|
175
|
+
`add`/`sync` are additive and never clobber a file you hand-edited inside an installed
|
|
176
|
+
overlay. Removing an overlay deletes only its dir and unpublishes only the skills **it**
|
|
177
|
+
published; its lines in `AGENTS.md`'s managed block are not auto-pruned (a warning points
|
|
178
|
+
at the manual edit, or re-run `install`).
|
|
179
|
+
|
|
180
|
+
**Overlay setup scripts.** An overlay may ship an **idempotent** `setup.py` at its root
|
|
181
|
+
(the recommended form: it runs under the same Python that runs dotagents, so it works on
|
|
182
|
+
every OS — the bundled `net` overlay is the model). An extensionless `setup` (a POSIX
|
|
183
|
+
shell script) is still honored as a legacy fallback, but it is discouraged: a shell
|
|
184
|
+
script isn't portable to Windows without a shell. After `add`/`sync` copies the overlay
|
|
185
|
+
in, dotagents runs the script automatically — so anything a human would otherwise
|
|
186
|
+
hand-follow (PATH/lib wiring, self-registration) is one script the tool runs, not a doc.
|
|
187
|
+
When both are present, `setup.py` wins. Presence of a script is the opt-in; skip it with
|
|
188
|
+
`--no-setup`. The contract for authors:
|
|
189
|
+
|
|
190
|
+
- **Idempotent** — safe to run on every `add`/`sync`; check-then-act, never blindly append.
|
|
191
|
+
- **cwd** is the installed overlay dir (`<scope>/.agents/overlays/<name>/`), so reference
|
|
192
|
+
your own files by relative path.
|
|
193
|
+
- **Env** carries `AGENTS_HOME` (the resolved store path — never hardcode
|
|
194
|
+
`~/.agents`) and `AGENTS_OVERLAY_DIR` (your own installed dir).
|
|
195
|
+
- A **non-zero exit fails the install** with a clear error (not a silent skip). For any
|
|
196
|
+
outward or irreversible action the *script* must confirm first — the runner invokes a
|
|
197
|
+
script you chose to install; it does not second-guess it.
|
|
198
|
+
|
|
199
|
+
**Downloadable `dotagents.pyz`** — a self-contained zipapp with `duho`/
|
|
200
|
+
`pathlib_next` and the required `tools/` bundled in, so it needs no `pip install`:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
python -m dotagents build-pyz --out dist/dotagents.pyz # build it (needs this repo checkout)
|
|
204
|
+
python dist/dotagents.pyz init --bin-dir ~/.local/bin # lay down the base + a `dotagents` command, offline
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Then wire your runner to it — e.g. Claude Code: put `@AGENTS.md` in
|
|
208
|
+
`~/.claude/CLAUDE.md`... which is exactly what the installed `CLAUDE.md` contains.
|
|
209
|
+
|
|
210
|
+
**Or let your agent do it:** point it at this repo and say —
|
|
211
|
+
> Read README.md, run `python install.py init && python install.py overlays add flows -g`,
|
|
212
|
+
> and confirm `~/.agents/overlays/flows/flows/PLAN.md` exists.
|
|
213
|
+
|
|
214
|
+
## Private sync (per-user + per-project, one private repo)
|
|
215
|
+
|
|
216
|
+
Keep your global config **and** every project's private `.agents` (plans, kb, findings)
|
|
217
|
+
in a single private git repo — synced across machines and cloud sessions — without ever
|
|
218
|
+
committing any of it into the (often public) project repos.
|
|
219
|
+
|
|
220
|
+
The idea: your global `~/.agents` **is** a private git repo. Its root is the per-user
|
|
221
|
+
config; a `projects/<name>/` tree holds each project's private `.agents` payload. For a
|
|
222
|
+
checked-out project, `<project>/.agents` is a **symlink** to `~/.agents/projects/<name>`
|
|
223
|
+
(the project's `.gitignore` already excludes `.agents/` per the Leakage rule, so the
|
|
224
|
+
link never lands in the public repo). `<name>` defaults to the project's basename, so a
|
|
225
|
+
local `~/code/app` and a cloud `/home/user/app` resolve to the same store.
|
|
226
|
+
|
|
227
|
+
The `link-project` / `sync-project` commands are **supplied by the `private-sync`
|
|
228
|
+
overlay**, not by dotagents itself — installing the overlay is what makes them exist
|
|
229
|
+
(it ships the commands, their logic, the `kb/` walkthrough and the cloud hooks
|
|
230
|
+
together). So `overlays add private-sync` comes first:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
dotagents init # base
|
|
234
|
+
dotagents overlays add private-sync --source <overlays-checkout> # commands + kb + hooks
|
|
235
|
+
dotagents link-project . # symlink this project's .agents into the private repo
|
|
236
|
+
# (an existing .agents/ is adopted in on the first link;
|
|
237
|
+
# --copy mirrors it as a real dir for no-symlink systems)
|
|
238
|
+
dotagents sync-project -m msg # git pull --rebase / commit / push the private repo
|
|
239
|
+
dotagents sync-project --remote git@github.com:<you>/.agents.git -m init # one-command bootstrap
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
In cloud sessions, the installed `~/.agents/hooks/private-sync-{start,stop}.sh` clone/pull
|
|
243
|
+
the private repo and link/sync the project per session — register them in
|
|
244
|
+
`~/.claude/settings.json` (see `~/.agents/hooks/settings.snippet.json`). For a **fresh
|
|
245
|
+
container** (no `~/.agents` yet), point the web environment's **setup-script** field at the
|
|
246
|
+
self-contained bootstrap in this repo — it fetches the latest each start, so there's
|
|
247
|
+
nothing to re-paste:
|
|
248
|
+
|
|
249
|
+
```bash
|
|
250
|
+
curl -fsSL https://raw.githubusercontent.com/<you>/dotagents/main/tools/cloud-setup.sh -o /tmp/dg-cloud-setup.sh && sh /tmp/dg-cloud-setup.sh
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Use `curl … -o file && sh file`, not `curl … | sh`: with a pipe the setup field's exit
|
|
254
|
+
code is `sh`'s (0 on empty stdin), so a failed fetch is silently logged as success; `&&`
|
|
255
|
+
propagates the curl failure instead.
|
|
256
|
+
|
|
257
|
+
It authenticates (bypassing a hosted-runner `github.com`→proxy git rewrite), clones/pulls
|
|
258
|
+
`~/.agents`, installs the CLI, and links the project — driven by `AGENTS_REMOTE`
|
|
259
|
+
/ `DOTAGENTS_AGENTS_TOKEN` / `DOTAGENTS_CLI_INSTALL` env vars (token never committed). Full
|
|
260
|
+
walkthrough: `~/.agents/kb/PRIVATE_SYNC.md`.
|
|
261
|
+
|
|
262
|
+
## Validate
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
python tools/audit.py --root . # validate THIS REPO's layout (CI tooling)
|
|
266
|
+
python tools/audit.py --check-templates --root . # + template checks (needs 3.11+)
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Customize
|
|
270
|
+
|
|
271
|
+
Fork it — that's the point. Keep the base `AGENTS.md` small (the audit warns past
|
|
272
|
+
~2.5KB); put opinionated content in overlays. Your `~/.agents/dotagents/DECISIONS.md`
|
|
273
|
+
is *your* private, per-install design log (index + `decisions/` files) — installed
|
|
274
|
+
empty, edited directly, never distributed. This repo follows the same rule: its own
|
|
275
|
+
design log and all working material live in an **untracked** `.agents/dotagents/`, never
|
|
276
|
+
committed — so what's public here is only the CLI, the base overlay, and the opt-in
|
|
277
|
+
overlays. If you fork, keep the tracked surface free of personal paths and private
|
|
278
|
+
project names. `dotagents audit` validates config *structure* only; personal-leak
|
|
279
|
+
scanning (machine paths, private plan names, session trailers) is a separate,
|
|
280
|
+
personal `leak-check` tool you run locally before a push — it lives in your private
|
|
281
|
+
`.agents/`, not shipped in this repo.
|
|
282
|
+
|
|
283
|
+
## Documentation
|
|
284
|
+
|
|
285
|
+
Full docs — install modes, the overlay model, the CLI command surface, private sync,
|
|
286
|
+
authoring your own overlays and commands, and the API reference — are at
|
|
287
|
+
[jose-pr.github.io/dotagents](https://jose-pr.github.io/dotagents/).
|
|
288
|
+
|
|
289
|
+
## License
|
|
290
|
+
|
|
291
|
+
MIT — see [LICENSE](LICENSE).
|