imap-agent-cli 0.1.6__tar.gz → 0.2.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.
- imap_agent_cli-0.2.0/CHANGELOG.md +10 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/PKG-INFO +45 -9
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/README.md +41 -7
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/imap_agent_cli.py +2 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/pyproject.toml +3 -1
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/spec.md +20 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/src/imap_agent_cli/__init__.py +1 -1
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/src/imap_agent_cli/cli.py +60 -13
- imap_agent_cli-0.2.0/src/imap_agent_cli/runtime.py +41 -0
- imap_agent_cli-0.2.0/src/imap_agent_cli/skill.py +476 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/tests/_bootstrap.py +3 -2
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/tests/test_cli.py +5 -0
- imap_agent_cli-0.2.0/tests/test_runtime.py +61 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/tests/test_skill.py +7 -1
- imap_agent_cli-0.2.0/tests/test_skill_sync.py +470 -0
- imap_agent_cli-0.2.0/tests/test_wheel_smoke.py +101 -0
- imap_agent_cli-0.1.6/src/imap_agent_cli/skill.py +0 -219
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/.github/workflows/publish.yml +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/.gitignore +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/AGENTS.md +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/LICENSE +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/src/imap_agent_cli/config.py +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/src/imap_agent_cli/errors.py +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/src/imap_agent_cli/imap_client.py +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/src/imap_agent_cli/mime.py +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/src/imap_agent_cli/models.py +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/src/imap_agent_cli/render.py +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/src/imap_agent_cli/search.py +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/tests/__init__.py +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/tests/pymap_server_runner.py +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/tests/test_config.py +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/tests/test_imap_client.py +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/tests/test_live_no_seen.py +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/tests/test_mime.py +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/tests/test_pymap_integration.py +0 -0
- {imap_agent_cli-0.1.6 → imap_agent_cli-0.2.0}/tests/test_search.py +0 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0 - 2026-09-03
|
|
4
|
+
|
|
5
|
+
- Automatically synchronize existing managed `imap` skills in the standard directory to the running CLI's newer version. No package lookup, uv refresh, or CLI update is performed.
|
|
6
|
+
- Store management identity, exact CLI version, and normalized SHA-256 content integrity in `SKILL.md` YAML metadata. Migrate legacy markers and recover missing or malformed versions.
|
|
7
|
+
- Preserve modified or unverifiable content with valid versions. Add `uvx imap-agent-cli skill install --force` for explicit replacement of managed content.
|
|
8
|
+
- Add read-only `skill status` with JSON and plain output. Keep `install-skill`, `remove-skill`, custom directories, and removal-time `--force`.
|
|
9
|
+
- Skip automatic synchronization for local source and editable builds. Custom skills require explicit updates. Updates affect future agent skill loading.
|
|
10
|
+
- Replace skills atomically and preserve unrelated files during removal.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: imap-agent-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Agent-first IMAP CLI for safe email search, read, attachment download, and draft creation
|
|
5
5
|
Project-URL: Homepage, https://github.com/pseudosavant/imap-agent-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/pseudosavant/imap-agent-cli
|
|
@@ -26,6 +26,8 @@ Requires-Dist: beautifulsoup4>=4.12.0
|
|
|
26
26
|
Requires-Dist: bleach>=6.2.0
|
|
27
27
|
Requires-Dist: imapclient>=3.0.0
|
|
28
28
|
Requires-Dist: markdownify>=0.14.0
|
|
29
|
+
Requires-Dist: packaging>=23.2
|
|
30
|
+
Requires-Dist: pyyaml>=6.0
|
|
29
31
|
Provides-Extra: test
|
|
30
32
|
Requires-Dist: pymap>=0.36.0; extra == 'test'
|
|
31
33
|
Requires-Dist: pytest>=8.0.0; extra == 'test'
|
|
@@ -44,7 +46,7 @@ It can inspect mailboxes and append messages to Drafts. It cannot send email, de
|
|
|
44
46
|
Install or update the `imap` agent skill:
|
|
45
47
|
|
|
46
48
|
```text
|
|
47
|
-
uvx imap-agent-cli install
|
|
49
|
+
uvx imap-agent-cli skill install
|
|
48
50
|
```
|
|
49
51
|
|
|
50
52
|
Configure the default IMAP account with environment variables:
|
|
@@ -86,7 +88,7 @@ python -m pip install imap-agent-cli
|
|
|
86
88
|
imap-agent-cli --help
|
|
87
89
|
```
|
|
88
90
|
|
|
89
|
-
|
|
91
|
+
Command payloads are JSON on stdout by default. Skill status also offers `--format plain`. Diagnostics and errors go to stderr.
|
|
90
92
|
|
|
91
93
|
Use `imap-agent-cli --about` for project URL and license attribution. Use `imap-agent-cli --version` to print only the version number.
|
|
92
94
|
|
|
@@ -130,12 +132,12 @@ imap-agent-cli config check
|
|
|
130
132
|
|
|
131
133
|
### Agent Skill
|
|
132
134
|
|
|
133
|
-
The installed skill teaches an agentic tool how to use `imap-agent-cli` safely
|
|
135
|
+
The installed skill teaches an agentic tool how to use `imap-agent-cli` safely through `uvx`.
|
|
134
136
|
|
|
135
137
|
Install or update the user-scoped `imap` skill:
|
|
136
138
|
|
|
137
139
|
```text
|
|
138
|
-
uvx imap-agent-cli install
|
|
140
|
+
uvx imap-agent-cli skill install
|
|
139
141
|
```
|
|
140
142
|
|
|
141
143
|
This writes:
|
|
@@ -144,18 +146,44 @@ This writes:
|
|
|
144
146
|
~/.agents/skills/imap/SKILL.md
|
|
145
147
|
```
|
|
146
148
|
|
|
149
|
+
Normally installed CLI builds check this location on every ordinary invocation, including help, version, and about output. An already-installed managed skill from an older CLI version is replaced only if its recorded content hash still matches. Missing skills are never installed automatically. Unmanaged skills, equal versions, and newer versions are left alone.
|
|
150
|
+
|
|
151
|
+
The running CLI version is the authority. Synchronization is local. It does not query PyPI, refresh uv's cache, or update uv or the CLI. Updating those tools remains a separate action. Skill-management commands skip automatic synchronization.
|
|
152
|
+
|
|
153
|
+
Inspect the installed version, content integrity, and synchronization eligibility without changing the skill:
|
|
154
|
+
|
|
155
|
+
```text
|
|
156
|
+
uvx imap-agent-cli skill status
|
|
157
|
+
uvx imap-agent-cli skill status --format plain
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Modified skills and skills with valid versions but missing or invalid hashes are preserved. To replace managed content explicitly:
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
uvx imap-agent-cli skill install --force
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Install-time `--force` still refuses unmanaged content and never downgrades a newer skill. Legacy skills with the old HTML managed marker migrate once as version 0. Missing or malformed managed versions receive a fresh replacement without a hash check. Ownership, version, and the SHA-256 content hash are stored in `SKILL.md` front matter under `metadata`. The hash detects edits. It is not a signature.
|
|
167
|
+
|
|
147
168
|
Remove the managed skill:
|
|
148
169
|
|
|
149
170
|
```text
|
|
150
|
-
uvx imap-agent-cli remove
|
|
171
|
+
uvx imap-agent-cli skill remove
|
|
151
172
|
```
|
|
152
173
|
|
|
153
|
-
|
|
174
|
+
Removal deletes only `SKILL.md` and removes its directory if empty. Unrelated files are kept. Removal retains `--force` for explicitly removing an unmanaged `SKILL.md`. The original `install-skill` and `remove-skill` aliases remain supported.
|
|
175
|
+
|
|
176
|
+
Use `--skills-dir PATH` on any skill command to select a custom skills root. Custom locations require explicit updates and are never discovered by ordinary invocations:
|
|
154
177
|
|
|
155
178
|
```text
|
|
156
|
-
uvx imap-agent-cli
|
|
179
|
+
uvx imap-agent-cli skill install --skills-dir ./my-skills
|
|
180
|
+
uvx imap-agent-cli skill status --skills-dir ./my-skills
|
|
157
181
|
```
|
|
158
182
|
|
|
183
|
+
Local checkouts, local source installs, and editable builds do not synchronize automatically. Unverifiable installation origins are also skipped. Explicit installation still works from development builds, including `uvx --from . imap-agent-cli skill install`. Installed wheels remain eligible, including wheels installed from a local file.
|
|
184
|
+
|
|
185
|
+
An update affects future agent skill loading. An agent session may retain instructions it has already loaded.
|
|
186
|
+
|
|
159
187
|
### Safety Boundary
|
|
160
188
|
|
|
161
189
|
Allowed:
|
|
@@ -219,6 +247,14 @@ Run no-network unit tests:
|
|
|
219
247
|
python -m unittest discover -v
|
|
220
248
|
```
|
|
221
249
|
|
|
250
|
+
To validate an installed wheel and local-source exclusions, build the package and set `IMAP_AGENT_CLI_WHEEL_TEST` to the wheel's path. Then run:
|
|
251
|
+
|
|
252
|
+
```text
|
|
253
|
+
python -m unittest tests.test_wheel_smoke -v
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
This opt-in check uses uv to install into a temporary environment with a temporary home directory. The regular tests also isolate skill paths from your home directory.
|
|
257
|
+
|
|
222
258
|
Run the opt-in local IMAP integration test:
|
|
223
259
|
|
|
224
260
|
Set `IMAP_AGENT_CLI_TEST_PYMAP=1` in your shell, then run:
|
|
@@ -11,7 +11,7 @@ It can inspect mailboxes and append messages to Drafts. It cannot send email, de
|
|
|
11
11
|
Install or update the `imap` agent skill:
|
|
12
12
|
|
|
13
13
|
```text
|
|
14
|
-
uvx imap-agent-cli install
|
|
14
|
+
uvx imap-agent-cli skill install
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Configure the default IMAP account with environment variables:
|
|
@@ -53,7 +53,7 @@ python -m pip install imap-agent-cli
|
|
|
53
53
|
imap-agent-cli --help
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
Command payloads are JSON on stdout by default. Skill status also offers `--format plain`. Diagnostics and errors go to stderr.
|
|
57
57
|
|
|
58
58
|
Use `imap-agent-cli --about` for project URL and license attribution. Use `imap-agent-cli --version` to print only the version number.
|
|
59
59
|
|
|
@@ -97,12 +97,12 @@ imap-agent-cli config check
|
|
|
97
97
|
|
|
98
98
|
### Agent Skill
|
|
99
99
|
|
|
100
|
-
The installed skill teaches an agentic tool how to use `imap-agent-cli` safely
|
|
100
|
+
The installed skill teaches an agentic tool how to use `imap-agent-cli` safely through `uvx`.
|
|
101
101
|
|
|
102
102
|
Install or update the user-scoped `imap` skill:
|
|
103
103
|
|
|
104
104
|
```text
|
|
105
|
-
uvx imap-agent-cli install
|
|
105
|
+
uvx imap-agent-cli skill install
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
This writes:
|
|
@@ -111,18 +111,44 @@ This writes:
|
|
|
111
111
|
~/.agents/skills/imap/SKILL.md
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
Normally installed CLI builds check this location on every ordinary invocation, including help, version, and about output. An already-installed managed skill from an older CLI version is replaced only if its recorded content hash still matches. Missing skills are never installed automatically. Unmanaged skills, equal versions, and newer versions are left alone.
|
|
115
|
+
|
|
116
|
+
The running CLI version is the authority. Synchronization is local. It does not query PyPI, refresh uv's cache, or update uv or the CLI. Updating those tools remains a separate action. Skill-management commands skip automatic synchronization.
|
|
117
|
+
|
|
118
|
+
Inspect the installed version, content integrity, and synchronization eligibility without changing the skill:
|
|
119
|
+
|
|
120
|
+
```text
|
|
121
|
+
uvx imap-agent-cli skill status
|
|
122
|
+
uvx imap-agent-cli skill status --format plain
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Modified skills and skills with valid versions but missing or invalid hashes are preserved. To replace managed content explicitly:
|
|
126
|
+
|
|
127
|
+
```text
|
|
128
|
+
uvx imap-agent-cli skill install --force
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Install-time `--force` still refuses unmanaged content and never downgrades a newer skill. Legacy skills with the old HTML managed marker migrate once as version 0. Missing or malformed managed versions receive a fresh replacement without a hash check. Ownership, version, and the SHA-256 content hash are stored in `SKILL.md` front matter under `metadata`. The hash detects edits. It is not a signature.
|
|
132
|
+
|
|
114
133
|
Remove the managed skill:
|
|
115
134
|
|
|
116
135
|
```text
|
|
117
|
-
uvx imap-agent-cli remove
|
|
136
|
+
uvx imap-agent-cli skill remove
|
|
118
137
|
```
|
|
119
138
|
|
|
120
|
-
|
|
139
|
+
Removal deletes only `SKILL.md` and removes its directory if empty. Unrelated files are kept. Removal retains `--force` for explicitly removing an unmanaged `SKILL.md`. The original `install-skill` and `remove-skill` aliases remain supported.
|
|
140
|
+
|
|
141
|
+
Use `--skills-dir PATH` on any skill command to select a custom skills root. Custom locations require explicit updates and are never discovered by ordinary invocations:
|
|
121
142
|
|
|
122
143
|
```text
|
|
123
|
-
uvx imap-agent-cli
|
|
144
|
+
uvx imap-agent-cli skill install --skills-dir ./my-skills
|
|
145
|
+
uvx imap-agent-cli skill status --skills-dir ./my-skills
|
|
124
146
|
```
|
|
125
147
|
|
|
148
|
+
Local checkouts, local source installs, and editable builds do not synchronize automatically. Unverifiable installation origins are also skipped. Explicit installation still works from development builds, including `uvx --from . imap-agent-cli skill install`. Installed wheels remain eligible, including wheels installed from a local file.
|
|
149
|
+
|
|
150
|
+
An update affects future agent skill loading. An agent session may retain instructions it has already loaded.
|
|
151
|
+
|
|
126
152
|
### Safety Boundary
|
|
127
153
|
|
|
128
154
|
Allowed:
|
|
@@ -186,6 +212,14 @@ Run no-network unit tests:
|
|
|
186
212
|
python -m unittest discover -v
|
|
187
213
|
```
|
|
188
214
|
|
|
215
|
+
To validate an installed wheel and local-source exclusions, build the package and set `IMAP_AGENT_CLI_WHEEL_TEST` to the wheel's path. Then run:
|
|
216
|
+
|
|
217
|
+
```text
|
|
218
|
+
python -m unittest tests.test_wheel_smoke -v
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
This opt-in check uses uv to install into a temporary environment with a temporary home directory. The regular tests also isolate skill paths from your home directory.
|
|
222
|
+
|
|
189
223
|
Run the opt-in local IMAP integration test:
|
|
190
224
|
|
|
191
225
|
Set `IMAP_AGENT_CLI_TEST_PYMAP=1` in your shell, then run:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "imap-agent-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.2.0"
|
|
8
8
|
description = "Agent-first IMAP CLI for safe email search, read, attachment download, and draft creation"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -32,6 +32,8 @@ dependencies = [
|
|
|
32
32
|
"beautifulsoup4>=4.12.0",
|
|
33
33
|
"bleach>=6.2.0",
|
|
34
34
|
"markdownify>=0.14.0",
|
|
35
|
+
"packaging>=23.2",
|
|
36
|
+
"PyYAML>=6.0",
|
|
35
37
|
]
|
|
36
38
|
|
|
37
39
|
[project.optional-dependencies]
|
|
@@ -319,6 +319,26 @@ imap-agent-cli config add-profile work --preset generic-ssl-993 --host colo-mail
|
|
|
319
319
|
|
|
320
320
|
All commands output JSON to stdout by default. Diagnostics and errors go to stderr.
|
|
321
321
|
|
|
322
|
+
## Managed Agent Skill
|
|
323
|
+
|
|
324
|
+
The distribution and command are `imap-agent-cli`. The Python import package is `imap_agent_cli`. The skill is `imap`, installed at `~/.agents/skills/imap/SKILL.md` by `uvx imap-agent-cli skill install`. The original `install-skill` and `remove-skill` commands remain aliases.
|
|
325
|
+
|
|
326
|
+
`imap_agent_cli.__version__` is the runtime authority and must match `--version`. Canonical skill generation writes `metadata.managed-by: imap-agent-cli`, a quoted `metadata.managed-version`, and a quoted `metadata.managed-content-sha256` with the `sha256:` prefix and 64 lowercase hexadecimal characters. There is no top-level version or sidecar file. The bundled instructions continue to prefer `uvx`.
|
|
327
|
+
|
|
328
|
+
The content hash covers the entire UTF-8 file after normalizing CRLF and CR to LF and replacing only the hash field's value with `""`. Verification uses the installed text without reserializing YAML. Generated files have LF endings, a trailing newline, and no byte order mark. This detects modifications and is not a signature.
|
|
329
|
+
|
|
330
|
+
Ordinary invocations, including help, version, about, and no arguments, check only an already-installed skill in the standard location. Skill-management commands skip this check. Compare versions with PEP 440. Replace pristine older managed content. Preserve equal or newer versions and unmanaged content. Preserve older content with a missing, malformed, or mismatched hash and recommend `uvx imap-agent-cli skill install --force`.
|
|
331
|
+
|
|
332
|
+
The legacy HTML marker remains recognized unless front matter assigns ownership to another tool. Legacy content without a version migrates as version 0. Missing or malformed managed versions intentionally recover through replacement without integrity verification. Newly generated metadata becomes authoritative.
|
|
333
|
+
|
|
334
|
+
`skill install` creates missing skills and updates pristine older managed skills. `--force` permits replacement of altered managed content, including at the same version. It never overwrites unmanaged content or downgrades a newer version. `skill remove` manages only `SKILL.md`, keeps unrelated files, and retains its explicit force override for unmanaged files. Unexpected directories and linked skill paths are refused.
|
|
335
|
+
|
|
336
|
+
`skill status` is read-only. JSON is the default, with `--format plain` available. Report path, installation and ownership, running and installed versions, version relationship, integrity, automatic eligibility, local-development exclusion, and any force recommendation. All skill commands accept `--skills-dir PATH`. Custom locations require explicit updates.
|
|
337
|
+
|
|
338
|
+
Automatic synchronization excludes local checkouts, local source installations, editable builds, and unverified installation origins. Use installed-distribution file records and PEP 610 metadata. Built wheel installations remain eligible. Explicit skill commands work from development builds.
|
|
339
|
+
|
|
340
|
+
Synchronization performs no package-index lookup, uv cache refresh, or CLI update. Replacement uses a flushed and closed temporary file in the target directory and an atomic replace after rechecking the installed bytes. Concurrent changes abort that attempt. Failures do not change the primary command's exit status. Notices go only to stderr. Updates apply to future skill loading and may not change instructions already loaded in an agent session.
|
|
341
|
+
|
|
322
342
|
## Folder Commands
|
|
323
343
|
|
|
324
344
|
List folders:
|
|
@@ -12,7 +12,7 @@ from .errors import AppError, ConfigError
|
|
|
12
12
|
from .imap_client import ImapSession
|
|
13
13
|
from .mime import create_draft_message, header_value, parse_message
|
|
14
14
|
from .render import write_error, write_json
|
|
15
|
-
from .skill import install_skill, remove_skill
|
|
15
|
+
from .skill import install_skill, remove_skill, skill_status, sync_skill
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
PROJECT_URL = "https://github.com/pseudosavant/imap-agent-cli"
|
|
@@ -52,8 +52,9 @@ Output:
|
|
|
52
52
|
Commands:
|
|
53
53
|
config initialize and manage profile configuration
|
|
54
54
|
profiles list configured profile names
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
skill install, inspect status, or remove the imap agent skill
|
|
56
|
+
install-skill alias for skill install
|
|
57
|
+
remove-skill alias for skill remove
|
|
57
58
|
folders list folders
|
|
58
59
|
search search messages
|
|
59
60
|
read read a message by folder and UID
|
|
@@ -61,6 +62,14 @@ Commands:
|
|
|
61
62
|
attachments list or download attachments
|
|
62
63
|
draft create new or reply drafts
|
|
63
64
|
|
|
65
|
+
Agent skill:
|
|
66
|
+
uvx imap-agent-cli skill install
|
|
67
|
+
uvx imap-agent-cli skill status
|
|
68
|
+
uvx imap-agent-cli skill install --force
|
|
69
|
+
Installed managed skills in ~/.agents/skills/imap synchronize to newer CLI versions.
|
|
70
|
+
Modified skills are preserved. Local development builds skip synchronization.
|
|
71
|
+
Custom locations require explicit updates with --skills-dir PATH.
|
|
72
|
+
|
|
64
73
|
More help:
|
|
65
74
|
imap-agent-cli <command> --help
|
|
66
75
|
imap-agent-cli --about
|
|
@@ -271,15 +280,46 @@ def cmd_profiles(args: argparse.Namespace) -> int:
|
|
|
271
280
|
|
|
272
281
|
|
|
273
282
|
def cmd_install_skill(args: argparse.Namespace) -> int:
|
|
274
|
-
write_json(
|
|
283
|
+
write_json(_skill_result(args, install_skill, force=args.force))
|
|
275
284
|
return 0
|
|
276
285
|
|
|
277
286
|
|
|
278
287
|
def cmd_remove_skill(args: argparse.Namespace) -> int:
|
|
279
|
-
write_json(
|
|
288
|
+
write_json(_skill_result(args, remove_skill, force=args.force))
|
|
289
|
+
return 0
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _skill_result(args: argparse.Namespace, operation: Any, **kwargs: Any) -> dict[str, Any]:
|
|
293
|
+
try:
|
|
294
|
+
return operation(Path(args.skills_dir).expanduser() if args.skills_dir else None, **kwargs)
|
|
295
|
+
except (OSError, UnicodeError) as exc:
|
|
296
|
+
raise AppError("invalid_request", "skill operation failed. Check the skill path, UTF-8 encoding, and file permissions.") from exc
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def cmd_skill_status(args: argparse.Namespace) -> int:
|
|
300
|
+
result = _skill_result(args, skill_status)
|
|
301
|
+
if args.format == "plain":
|
|
302
|
+
for key, value in result.items():
|
|
303
|
+
rendered = json.dumps(value) if value is None or isinstance(value, bool) else str(value)
|
|
304
|
+
sys.stdout.write(f"{key.replace('_', ' ')}: {rendered}\n")
|
|
305
|
+
else:
|
|
306
|
+
write_json(result)
|
|
280
307
|
return 0
|
|
281
308
|
|
|
282
309
|
|
|
310
|
+
def _skill_parser(parser: argparse.ArgumentParser, action: str) -> None:
|
|
311
|
+
parser.add_argument("--skills-dir", metavar="PATH", help="skills root directory (default: ~/.agents/skills). Custom locations require explicit updates")
|
|
312
|
+
if action == "install":
|
|
313
|
+
parser.add_argument("--force", action="store_true", help="replace altered managed content. Never overwrite unmanaged skills or downgrade a newer version")
|
|
314
|
+
parser.set_defaults(func=cmd_install_skill)
|
|
315
|
+
elif action == "remove":
|
|
316
|
+
parser.add_argument("--force", action="store_true", help="allow removal of an unmanaged SKILL.md. Keep unrelated files")
|
|
317
|
+
parser.set_defaults(func=cmd_remove_skill)
|
|
318
|
+
else:
|
|
319
|
+
parser.add_argument("--format", choices=["json", "plain"], default="json", help="status output format (default: json). This command never modifies the skill")
|
|
320
|
+
parser.set_defaults(func=cmd_skill_status)
|
|
321
|
+
|
|
322
|
+
|
|
283
323
|
def cmd_folders(args: argparse.Namespace) -> int:
|
|
284
324
|
with _session(args) as session:
|
|
285
325
|
write_json(session.folders())
|
|
@@ -501,14 +541,16 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
501
541
|
profiles = sub.add_parser("profiles", help="list configured profile names")
|
|
502
542
|
profiles.set_defaults(func=cmd_profiles)
|
|
503
543
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
544
|
+
skill = sub.add_parser("skill", help="manage the imap agent skill", description="Manage SKILL.md explicitly. These commands skip automatic synchronization.")
|
|
545
|
+
skill_sub = skill.add_subparsers(dest="skill_command", required=True)
|
|
546
|
+
for action, description in (
|
|
547
|
+
("install", "install a missing skill or update pristine older managed content"),
|
|
548
|
+
("remove", "remove SKILL.md and keep unrelated files"),
|
|
549
|
+
("status", "inspect version, integrity, and automatic synchronization eligibility without writing"),
|
|
550
|
+
):
|
|
551
|
+
_skill_parser(skill_sub.add_parser(action, help=description, description=description), action)
|
|
552
|
+
_skill_parser(sub.add_parser("install-skill", help="alias for skill install"), "install")
|
|
553
|
+
_skill_parser(sub.add_parser("remove-skill", help="alias for skill remove"), "remove")
|
|
512
554
|
|
|
513
555
|
folders = sub.add_parser("folders", help="list folders and folder metadata")
|
|
514
556
|
_common_profile_args(folders)
|
|
@@ -616,6 +658,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
616
658
|
def main(argv: list[str] | None = None) -> int:
|
|
617
659
|
if argv is None:
|
|
618
660
|
argv = sys.argv[1:]
|
|
661
|
+
# Global options take no values. Recognize the command before argparse can
|
|
662
|
+
# exit for help or version, including help for skill-management commands.
|
|
663
|
+
command = next((arg for arg in argv if not arg.startswith("-")), None)
|
|
664
|
+
if command not in {"skill", "install-skill", "remove-skill"}:
|
|
665
|
+
sync_skill()
|
|
619
666
|
if not argv or argv in (["--help"], ["-h"]):
|
|
620
667
|
sys.stdout.write(TOP_LEVEL_HELP)
|
|
621
668
|
return 0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Identify installed code without depending on its launcher."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from importlib import metadata
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from urllib.parse import urlsplit
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
DISTRIBUTION_NAME = "imap-agent-cli"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def is_local_development() -> bool:
|
|
15
|
+
"""Skip local sources and code whose installed origin cannot be verified.
|
|
16
|
+
|
|
17
|
+
A local wheel is a built distribution, unlike a local source archive or
|
|
18
|
+
directory. Its PEP 610 archive metadata therefore remains eligible.
|
|
19
|
+
"""
|
|
20
|
+
try:
|
|
21
|
+
distribution = metadata.distribution(DISTRIBUTION_NAME)
|
|
22
|
+
module = Path(__file__).resolve()
|
|
23
|
+
files = distribution.files or ()
|
|
24
|
+
if not any(
|
|
25
|
+
str(item).replace("\\", "/") == "imap_agent_cli/runtime.py"
|
|
26
|
+
and Path(distribution.locate_file(item)).resolve() == module
|
|
27
|
+
for item in files
|
|
28
|
+
):
|
|
29
|
+
return True
|
|
30
|
+
direct_url = distribution.read_text("direct_url.json")
|
|
31
|
+
if direct_url is None:
|
|
32
|
+
return False
|
|
33
|
+
source = json.loads(direct_url)
|
|
34
|
+
url = urlsplit(source["url"])
|
|
35
|
+
if "dir_info" in source:
|
|
36
|
+
return True
|
|
37
|
+
if url.scheme == "file":
|
|
38
|
+
return not (url.path.lower().endswith(".whl") and isinstance(source.get("archive_info"), dict))
|
|
39
|
+
return not bool(url.scheme)
|
|
40
|
+
except (metadata.PackageNotFoundError, OSError, ValueError, KeyError, TypeError, AttributeError):
|
|
41
|
+
return True
|