agentharnesses-cli 0.1.3__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.
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentharnesses-cli
3
- Version: 0.1.3
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
+ ]
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