agentharnesses-cli 0.1.2__tar.gz → 0.1.4__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.
- {agentharnesses_cli-0.1.2 → agentharnesses_cli-0.1.4}/PKG-INFO +31 -1
- {agentharnesses_cli-0.1.2 → agentharnesses_cli-0.1.4}/README.md +29 -0
- {agentharnesses_cli-0.1.2 → agentharnesses_cli-0.1.4}/pyproject.toml +6 -0
- {agentharnesses_cli-0.1.2 → agentharnesses_cli-0.1.4}/src/ahar/commands/init.py +14 -36
- agentharnesses_cli-0.1.4/tests/__init__.py +0 -0
- agentharnesses_cli-0.1.4/tests/test_init.py +137 -0
- {agentharnesses_cli-0.1.2 → agentharnesses_cli-0.1.4}/.github/workflows/pypi.yaml +0 -0
- {agentharnesses_cli-0.1.2 → agentharnesses_cli-0.1.4}/.gitignore +0 -0
- {agentharnesses_cli-0.1.2 → agentharnesses_cli-0.1.4}/src/ahar/__init__.py +0 -0
- {agentharnesses_cli-0.1.2 → agentharnesses_cli-0.1.4}/src/ahar/commands/__init__.py +0 -0
- {agentharnesses_cli-0.1.2 → agentharnesses_cli-0.1.4}/src/ahar/main.py +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentharnesses-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: CLI tools for agentharnesses.io
|
|
5
5
|
Project-URL: Homepage, https://agentharnesses.io
|
|
6
6
|
Project-URL: Repository, https://github.com/agentharnesses/cli
|
|
7
7
|
License-Expression: Apache-2.0
|
|
8
8
|
Requires-Python: >=3.10
|
|
9
9
|
Requires-Dist: click>=8.1
|
|
10
|
+
Requires-Dist: harnesses-ref>=0.1.0
|
|
10
11
|
Description-Content-Type: text/markdown
|
|
11
12
|
|
|
12
13
|
# agentharnesses-cli
|
|
@@ -64,6 +65,35 @@ When using the `claude` preset (default), also installs:
|
|
|
64
65
|
└── SKILL.md
|
|
65
66
|
```
|
|
66
67
|
|
|
68
|
+
The metaskill is cloned fresh from [agentharnesses/metaskill](https://github.com/agentharnesses/metaskill) at init time, so you always get the latest version.
|
|
69
|
+
|
|
70
|
+
### `ahar validate`
|
|
71
|
+
|
|
72
|
+
Validate a harness directory structure:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
ahar validate ./my-harness
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `ahar read`
|
|
79
|
+
|
|
80
|
+
Read a property from a harness's `HARNESS.md` frontmatter:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
ahar read ./my-harness name
|
|
84
|
+
ahar read ./my-harness description
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### `ahar prompt`
|
|
88
|
+
|
|
89
|
+
Render a harness as prompt XML for agent injection:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
ahar prompt ./my-harness
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
These commands are backed by [harnesses-ref](https://pypi.org/project/harnesses-ref/), the reference implementation for the Agent Harnesses standard.
|
|
96
|
+
|
|
67
97
|
## Publishing
|
|
68
98
|
|
|
69
99
|
Releases are published to PyPI automatically when a version tag is pushed:
|
|
@@ -53,6 +53,35 @@ When using the `claude` preset (default), also installs:
|
|
|
53
53
|
└── SKILL.md
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
The metaskill is cloned fresh from [agentharnesses/metaskill](https://github.com/agentharnesses/metaskill) at init time, so you always get the latest version.
|
|
57
|
+
|
|
58
|
+
### `ahar validate`
|
|
59
|
+
|
|
60
|
+
Validate a harness directory structure:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
ahar validate ./my-harness
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `ahar read`
|
|
67
|
+
|
|
68
|
+
Read a property from a harness's `HARNESS.md` frontmatter:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
ahar read ./my-harness name
|
|
72
|
+
ahar read ./my-harness description
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### `ahar prompt`
|
|
76
|
+
|
|
77
|
+
Render a harness as prompt XML for agent injection:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
ahar prompt ./my-harness
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
These commands are backed by [harnesses-ref](https://pypi.org/project/harnesses-ref/), the reference implementation for the Agent Harnesses standard.
|
|
84
|
+
|
|
56
85
|
## Publishing
|
|
57
86
|
|
|
58
87
|
Releases are published to PyPI automatically when a version tag is pushed:
|
|
@@ -7,6 +7,7 @@ readme = "README.md"
|
|
|
7
7
|
requires-python = ">=3.10"
|
|
8
8
|
dependencies = [
|
|
9
9
|
"click>=8.1",
|
|
10
|
+
"harnesses-ref>=0.1.0",
|
|
10
11
|
]
|
|
11
12
|
|
|
12
13
|
[project.urls]
|
|
@@ -25,3 +26,8 @@ source = "vcs"
|
|
|
25
26
|
|
|
26
27
|
[tool.hatch.build.targets.wheel]
|
|
27
28
|
packages = ["src/ahar"]
|
|
29
|
+
|
|
30
|
+
[dependency-groups]
|
|
31
|
+
dev = [
|
|
32
|
+
"pytest>=7.0",
|
|
33
|
+
]
|
|
@@ -14,39 +14,25 @@ _MAINTAIN_SKILLS_INDEX_DEST = "skills/maintenance/SKILLS.md"
|
|
|
14
14
|
_MAINTAIN_SKILL = """\
|
|
15
15
|
---
|
|
16
16
|
name: modify-harness
|
|
17
|
-
description:
|
|
17
|
+
description: Update harness structure files — HARNESS.md, SKILLS.md indexes, REFERENCES.md — to keep routing and descriptions accurate as the harness evolves.
|
|
18
18
|
---
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## Role
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Keep the harness self-consistent when skills or references are added, renamed, or removed.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
## What to do
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
1. Use reverse progressive disclosure (via the `agent-harnesses` skill) to find which index files reference the target path
|
|
27
|
+
2. Read the current state of each affected file
|
|
28
|
+
3. Apply the change: add, update, or remove the relevant entry
|
|
29
|
+
4. Ensure descriptions remain accurate and routing summaries reflect actual contents
|
|
29
30
|
|
|
30
|
-
|
|
31
|
+
## Conventions
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
- Update the `description` frontmatter field when the harness scope changes
|
|
36
|
-
|
|
37
|
-
### Adding a skill bucket
|
|
38
|
-
1. Create `skills/<bucket-name>/<skill-name>/SKILL.md` with a frontmatter `name` and `description`
|
|
39
|
-
2. Add an entry to `skills/<bucket-name>/SKILLS.md` summarizing when to use the skill
|
|
40
|
-
3. Ensure `skills/SKILLS.md` references the bucket
|
|
41
|
-
4. Add a bullet to the `## Skills` section in `HARNESS.md`
|
|
42
|
-
|
|
43
|
-
### Adding a reference document
|
|
44
|
-
1. Add the document to `references/`
|
|
45
|
-
2. Add an entry to `references/REFERENCES.md` describing the document's purpose
|
|
46
|
-
3. Add a bullet to the `## References` section in `HARNESS.md`
|
|
47
|
-
|
|
48
|
-
### General conventions
|
|
49
|
-
- Keep skill descriptions actionable: "Use when..." not "This skill..."
|
|
33
|
+
- Keep `HARNESS.md` `## Skills` and `## References` sections in sync with `skills/SKILLS.md` and `references/REFERENCES.md`
|
|
34
|
+
- Update the `description` frontmatter in `HARNESS.md` when the harness scope changes
|
|
35
|
+
- Skill descriptions should be actionable: "Use when..." not "This skill..."
|
|
50
36
|
- Reference documents should be stable facts; skill buckets contain executable guidance
|
|
51
37
|
- Prefer updating existing skill buckets over creating new ones when scope overlaps
|
|
52
38
|
"""
|
|
@@ -81,17 +67,9 @@ TODO: write the entry message Claude should internalize when this harness loads.
|
|
|
81
67
|
|
|
82
68
|
## How to Find Information for Claude
|
|
83
69
|
|
|
84
|
-
Use the `agent-harnesses` skill to explore the harness
|
|
85
|
-
|
|
86
|
-
Do not load skills or references speculatively. Use `disclose.py` to find resources when necessary. Any time you need to find anything in the harness, and you don't already know where it exists, use `disclose.py`.
|
|
87
|
-
|
|
88
|
-
When **maintaining the harness** (adding, moving, or renaming files), use `reverse_disclose.py` to find every `.md` file above the target that links to it — so you can update all references that would break:
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
python3 .claude/skills/agent-harnesses/scripts/reverse_disclose.py <target_path>
|
|
92
|
-
```
|
|
70
|
+
Use the `agent-harnesses` skill to explore the harness just in time, based on prompts from the user. Select only what is relevant and repeat until the session is complete, then read the returned resources.
|
|
93
71
|
|
|
94
|
-
|
|
72
|
+
When **maintaining the harness** (adding, moving, or renaming files), consult the `agent-harnesses` skill for reverse progressive disclosure to keep routing files in sync.
|
|
95
73
|
|
|
96
74
|
## Skills
|
|
97
75
|
|
|
File without changes
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
from click.testing import CliRunner
|
|
6
|
+
|
|
7
|
+
from ahar.commands.init import scaffold
|
|
8
|
+
from ahar.main import cli
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# --- scaffold unit tests ---
|
|
12
|
+
|
|
13
|
+
def test_scaffold_creates_required_files(tmp_path):
|
|
14
|
+
scaffold(str(tmp_path), "my-harness")
|
|
15
|
+
assert (tmp_path / "HARNESS.md").exists()
|
|
16
|
+
assert (tmp_path / "README.md").exists()
|
|
17
|
+
assert (tmp_path / ".gitignore").exists()
|
|
18
|
+
assert (tmp_path / ".claude" / "settings.json").exists()
|
|
19
|
+
assert (tmp_path / "skills" / "SKILLS.md").exists()
|
|
20
|
+
assert (tmp_path / "references" / "REFERENCES.md").exists()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_scaffold_harness_md_contains_name(tmp_path):
|
|
24
|
+
scaffold(str(tmp_path), "my-harness")
|
|
25
|
+
content = (tmp_path / "HARNESS.md").read_text()
|
|
26
|
+
assert "name: my-harness" in content
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_scaffold_settings_registers_marketplace(tmp_path):
|
|
30
|
+
scaffold(str(tmp_path), "my-harness")
|
|
31
|
+
settings = json.loads((tmp_path / ".claude" / "settings.json").read_text())
|
|
32
|
+
assert "my-harness" in settings["extraKnownMarketplaces"]
|
|
33
|
+
assert settings["extraKnownMarketplaces"]["my-harness"]["source"]["source"] == "directory"
|
|
34
|
+
assert settings["extraKnownMarketplaces"]["my-harness"]["source"]["path"] == str(tmp_path)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_scaffold_settings_enables_plugin(tmp_path):
|
|
38
|
+
scaffold(str(tmp_path), "my-harness")
|
|
39
|
+
settings = json.loads((tmp_path / ".claude" / "settings.json").read_text())
|
|
40
|
+
assert "my-harness@my-harness" in settings["enabledPlugins"]
|
|
41
|
+
assert settings["enabledPlugins"]["my-harness@my-harness"] is True
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_scaffold_gitignore_excludes_settings(tmp_path):
|
|
45
|
+
scaffold(str(tmp_path), "my-harness")
|
|
46
|
+
content = (tmp_path / ".gitignore").read_text()
|
|
47
|
+
assert ".claude/settings.json" in content
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# --- init CLI tests ---
|
|
51
|
+
|
|
52
|
+
def test_init_empty_preset_creates_scaffold(tmp_path):
|
|
53
|
+
runner = CliRunner()
|
|
54
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
55
|
+
result = runner.invoke(cli, ["init", "test-harness"], input="empty\n")
|
|
56
|
+
assert result.exit_code == 0
|
|
57
|
+
assert Path("HARNESS.md").exists()
|
|
58
|
+
assert Path("skills/SKILLS.md").exists()
|
|
59
|
+
assert Path("references/REFERENCES.md").exists()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_init_uses_directory_name_as_default(tmp_path):
|
|
63
|
+
runner = CliRunner()
|
|
64
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
65
|
+
result = runner.invoke(cli, ["init"], input="empty\n")
|
|
66
|
+
assert result.exit_code == 0
|
|
67
|
+
content = Path("HARNESS.md").read_text()
|
|
68
|
+
assert f"name: {Path.cwd().name}" in content
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_init_fails_if_already_initialized(tmp_path):
|
|
72
|
+
runner = CliRunner()
|
|
73
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
74
|
+
Path("HARNESS.md").write_text("---\nname: existing\ndescription: x\n---\n")
|
|
75
|
+
result = runner.invoke(cli, ["init"], input="empty\n")
|
|
76
|
+
assert result.exit_code == 1
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_init_empty_preset_skips_metaskill(tmp_path):
|
|
80
|
+
runner = CliRunner()
|
|
81
|
+
with runner.isolated_filesystem(temp_dir=tmp_path):
|
|
82
|
+
result = runner.invoke(cli, ["init", "test-harness"], input="empty\n")
|
|
83
|
+
assert result.exit_code == 0
|
|
84
|
+
assert not Path(".claude/skills/agent-harnesses").exists()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# --- validate CLI tests ---
|
|
88
|
+
|
|
89
|
+
def _make_valid_harness(path: Path) -> Path:
|
|
90
|
+
(path / "HARNESS.md").write_text(
|
|
91
|
+
"---\nname: Test Harness\ndescription: A test harness.\n---\nBody.\n"
|
|
92
|
+
)
|
|
93
|
+
return path
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_validate_valid_harness(tmp_path):
|
|
97
|
+
_make_valid_harness(tmp_path)
|
|
98
|
+
runner = CliRunner()
|
|
99
|
+
result = runner.invoke(cli, ["validate", str(tmp_path)])
|
|
100
|
+
assert result.exit_code == 0
|
|
101
|
+
assert "valid" in result.output
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_validate_invalid_harness(tmp_path):
|
|
105
|
+
(tmp_path / "HARNESS.md").write_text("no frontmatter")
|
|
106
|
+
runner = CliRunner()
|
|
107
|
+
result = runner.invoke(cli, ["validate", str(tmp_path)])
|
|
108
|
+
assert result.exit_code == 1
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# --- read CLI tests ---
|
|
112
|
+
|
|
113
|
+
def test_read_name(tmp_path):
|
|
114
|
+
_make_valid_harness(tmp_path)
|
|
115
|
+
runner = CliRunner()
|
|
116
|
+
result = runner.invoke(cli, ["read", str(tmp_path), "name"])
|
|
117
|
+
assert result.exit_code == 0
|
|
118
|
+
assert result.output.strip() == "Test Harness"
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_read_description(tmp_path):
|
|
122
|
+
_make_valid_harness(tmp_path)
|
|
123
|
+
runner = CliRunner()
|
|
124
|
+
result = runner.invoke(cli, ["read", str(tmp_path), "description"])
|
|
125
|
+
assert result.exit_code == 0
|
|
126
|
+
assert result.output.strip() == "A test harness."
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# --- prompt CLI tests ---
|
|
130
|
+
|
|
131
|
+
def test_prompt_renders_xml(tmp_path):
|
|
132
|
+
_make_valid_harness(tmp_path)
|
|
133
|
+
runner = CliRunner()
|
|
134
|
+
result = runner.invoke(cli, ["prompt", str(tmp_path)])
|
|
135
|
+
assert result.exit_code == 0
|
|
136
|
+
assert "<harness" in result.output
|
|
137
|
+
assert "Test Harness" in result.output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|