duet-cli 0.2.1__tar.gz → 0.2.2__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.
- {duet_cli-0.2.1/duet_cli.egg-info → duet_cli-0.2.2}/PKG-INFO +48 -6
- {duet_cli-0.2.1 → duet_cli-0.2.2}/README.md +47 -5
- {duet_cli-0.2.1 → duet_cli-0.2.2/duet_cli.egg-info}/PKG-INFO +48 -6
- {duet_cli-0.2.1 → duet_cli-0.2.2}/duet_cli.egg-info/SOURCES.txt +1 -0
- {duet_cli-0.2.1 → duet_cli-0.2.2}/pyproject.toml +1 -1
- duet_cli-0.2.2/tests/test_bump_release_version.py +106 -0
- {duet_cli-0.2.1 → duet_cli-0.2.2}/LICENSE +0 -0
- {duet_cli-0.2.1 → duet_cli-0.2.2}/duet.py +0 -0
- {duet_cli-0.2.1 → duet_cli-0.2.2}/duet_cli.egg-info/dependency_links.txt +0 -0
- {duet_cli-0.2.1 → duet_cli-0.2.2}/duet_cli.egg-info/entry_points.txt +0 -0
- {duet_cli-0.2.1 → duet_cli-0.2.2}/duet_cli.egg-info/requires.txt +0 -0
- {duet_cli-0.2.1 → duet_cli-0.2.2}/duet_cli.egg-info/top_level.txt +0 -0
- {duet_cli-0.2.1 → duet_cli-0.2.2}/setup.cfg +0 -0
- {duet_cli-0.2.1 → duet_cli-0.2.2}/tests/test_duet.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: duet-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Two CLI agents in conversation. One Python file. Stdlib only.
|
|
5
5
|
Author: Volkan Altan
|
|
6
6
|
License-Expression: MIT
|
|
@@ -47,6 +47,38 @@ a worktree until they converge. Pass any upstream command as the kickoff:
|
|
|
47
47
|
install that first (see below) and make sure `command -v duet` passes in Claude
|
|
48
48
|
Code's shell. Full guide:
|
|
49
49
|
[docs/CLAUDE_CODE_PLUGIN.md](https://github.com/volkan/duet/blob/main/docs/CLAUDE_CODE_PLUGIN.md).
|
|
50
|
+
If Claude Code disambiguates plugin commands with namespaces, `/duet:duet` is
|
|
51
|
+
the same command.
|
|
52
|
+
|
|
53
|
+
Autonomous handoff example:
|
|
54
|
+
|
|
55
|
+

|
|
56
|
+
|
|
57
|
+
While the handoff runs, Claude Code shows the shell in auto mode and exposes
|
|
58
|
+
the exact `duet` command under shell details:
|
|
59
|
+
|
|
60
|
+

|
|
61
|
+
|
|
62
|
+

|
|
63
|
+
|
|
64
|
+
Copy-ready version:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
/loop /goal Create a temporary todo.md from the plan above and the remaining
|
|
68
|
+
tasks in todo_codex.md.
|
|
69
|
+
|
|
70
|
+
1. Use /duet:duet with max reasoning to confirm the plan.
|
|
71
|
+
2. After the plan is confirmed, implement it.
|
|
72
|
+
3. Once the first implementation is done, use /duet:duet with max reasoning
|
|
73
|
+
for code review.
|
|
74
|
+
4. Use /duet:duet with max reasoning to review the second plan, then implement
|
|
75
|
+
it.
|
|
76
|
+
5. When the process is complete and all checks are green, merge the approved
|
|
77
|
+
changes.
|
|
78
|
+
|
|
79
|
+
P.S. I will not be around, so handle decisions without me. If you need another
|
|
80
|
+
opinion, use /duet:duet to discuss it with Codex.
|
|
81
|
+
```
|
|
50
82
|
|
|
51
83
|
### 2. Inside Codex — `$duet`
|
|
52
84
|
|
|
@@ -64,16 +96,26 @@ Codex's shell. Full guide:
|
|
|
64
96
|
### 3. From the terminal — `duet`
|
|
65
97
|
|
|
66
98
|
```bash
|
|
67
|
-
pipx install duet-cli # the command it installs is `duet`
|
|
99
|
+
pipx install duet-cli # recommended; the command it installs is `duet`
|
|
68
100
|
duet --task "Fix the failing test" --cwd ~/code/myrepo
|
|
69
101
|
```
|
|
70
102
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
103
|
+
`pipx` is the recommended install. Two other persistent options put `duet` on
|
|
104
|
+
PATH the same way:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
uv tool install duet-cli
|
|
108
|
+
python3 -m pip install --user duet-cli
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The PyPI package is `duet-cli` (bare `duet` on PyPI is Google's async library).
|
|
112
|
+
Add the `[yaml]` extra for `--config foo.yaml` support — `pipx install
|
|
113
|
+
'duet-cli[yaml]'`, `uv tool install 'duet-cli[yaml]'`, or `python3 -m pip
|
|
114
|
+
install --user 'duet-cli[yaml]'`. One-shot, no install:
|
|
74
115
|
`uvx --from duet-cli duet --task "..."` — note this is ephemeral and does not put
|
|
75
116
|
`duet` on PATH, so the `/duet` and `$duet` plugins need a persistent install
|
|
76
|
-
(`pipx install duet-cli`
|
|
117
|
+
(`pipx install duet-cli`, `uv tool install duet-cli`,
|
|
118
|
+
`python3 -m pip install --user duet-cli`, or `make install`) instead.
|
|
77
119
|
|
|
78
120
|
## Examples
|
|
79
121
|
|
|
@@ -26,6 +26,38 @@ a worktree until they converge. Pass any upstream command as the kickoff:
|
|
|
26
26
|
install that first (see below) and make sure `command -v duet` passes in Claude
|
|
27
27
|
Code's shell. Full guide:
|
|
28
28
|
[docs/CLAUDE_CODE_PLUGIN.md](https://github.com/volkan/duet/blob/main/docs/CLAUDE_CODE_PLUGIN.md).
|
|
29
|
+
If Claude Code disambiguates plugin commands with namespaces, `/duet:duet` is
|
|
30
|
+
the same command.
|
|
31
|
+
|
|
32
|
+
Autonomous handoff example:
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
While the handoff runs, Claude Code shows the shell in auto mode and exposes
|
|
37
|
+
the exact `duet` command under shell details:
|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+

|
|
42
|
+
|
|
43
|
+
Copy-ready version:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
/loop /goal Create a temporary todo.md from the plan above and the remaining
|
|
47
|
+
tasks in todo_codex.md.
|
|
48
|
+
|
|
49
|
+
1. Use /duet:duet with max reasoning to confirm the plan.
|
|
50
|
+
2. After the plan is confirmed, implement it.
|
|
51
|
+
3. Once the first implementation is done, use /duet:duet with max reasoning
|
|
52
|
+
for code review.
|
|
53
|
+
4. Use /duet:duet with max reasoning to review the second plan, then implement
|
|
54
|
+
it.
|
|
55
|
+
5. When the process is complete and all checks are green, merge the approved
|
|
56
|
+
changes.
|
|
57
|
+
|
|
58
|
+
P.S. I will not be around, so handle decisions without me. If you need another
|
|
59
|
+
opinion, use /duet:duet to discuss it with Codex.
|
|
60
|
+
```
|
|
29
61
|
|
|
30
62
|
### 2. Inside Codex — `$duet`
|
|
31
63
|
|
|
@@ -43,16 +75,26 @@ Codex's shell. Full guide:
|
|
|
43
75
|
### 3. From the terminal — `duet`
|
|
44
76
|
|
|
45
77
|
```bash
|
|
46
|
-
pipx install duet-cli # the command it installs is `duet`
|
|
78
|
+
pipx install duet-cli # recommended; the command it installs is `duet`
|
|
47
79
|
duet --task "Fix the failing test" --cwd ~/code/myrepo
|
|
48
80
|
```
|
|
49
81
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
82
|
+
`pipx` is the recommended install. Two other persistent options put `duet` on
|
|
83
|
+
PATH the same way:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
uv tool install duet-cli
|
|
87
|
+
python3 -m pip install --user duet-cli
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The PyPI package is `duet-cli` (bare `duet` on PyPI is Google's async library).
|
|
91
|
+
Add the `[yaml]` extra for `--config foo.yaml` support — `pipx install
|
|
92
|
+
'duet-cli[yaml]'`, `uv tool install 'duet-cli[yaml]'`, or `python3 -m pip
|
|
93
|
+
install --user 'duet-cli[yaml]'`. One-shot, no install:
|
|
53
94
|
`uvx --from duet-cli duet --task "..."` — note this is ephemeral and does not put
|
|
54
95
|
`duet` on PATH, so the `/duet` and `$duet` plugins need a persistent install
|
|
55
|
-
(`pipx install duet-cli`
|
|
96
|
+
(`pipx install duet-cli`, `uv tool install duet-cli`,
|
|
97
|
+
`python3 -m pip install --user duet-cli`, or `make install`) instead.
|
|
56
98
|
|
|
57
99
|
## Examples
|
|
58
100
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: duet-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Two CLI agents in conversation. One Python file. Stdlib only.
|
|
5
5
|
Author: Volkan Altan
|
|
6
6
|
License-Expression: MIT
|
|
@@ -47,6 +47,38 @@ a worktree until they converge. Pass any upstream command as the kickoff:
|
|
|
47
47
|
install that first (see below) and make sure `command -v duet` passes in Claude
|
|
48
48
|
Code's shell. Full guide:
|
|
49
49
|
[docs/CLAUDE_CODE_PLUGIN.md](https://github.com/volkan/duet/blob/main/docs/CLAUDE_CODE_PLUGIN.md).
|
|
50
|
+
If Claude Code disambiguates plugin commands with namespaces, `/duet:duet` is
|
|
51
|
+
the same command.
|
|
52
|
+
|
|
53
|
+
Autonomous handoff example:
|
|
54
|
+
|
|
55
|
+

|
|
56
|
+
|
|
57
|
+
While the handoff runs, Claude Code shows the shell in auto mode and exposes
|
|
58
|
+
the exact `duet` command under shell details:
|
|
59
|
+
|
|
60
|
+

|
|
61
|
+
|
|
62
|
+

|
|
63
|
+
|
|
64
|
+
Copy-ready version:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
/loop /goal Create a temporary todo.md from the plan above and the remaining
|
|
68
|
+
tasks in todo_codex.md.
|
|
69
|
+
|
|
70
|
+
1. Use /duet:duet with max reasoning to confirm the plan.
|
|
71
|
+
2. After the plan is confirmed, implement it.
|
|
72
|
+
3. Once the first implementation is done, use /duet:duet with max reasoning
|
|
73
|
+
for code review.
|
|
74
|
+
4. Use /duet:duet with max reasoning to review the second plan, then implement
|
|
75
|
+
it.
|
|
76
|
+
5. When the process is complete and all checks are green, merge the approved
|
|
77
|
+
changes.
|
|
78
|
+
|
|
79
|
+
P.S. I will not be around, so handle decisions without me. If you need another
|
|
80
|
+
opinion, use /duet:duet to discuss it with Codex.
|
|
81
|
+
```
|
|
50
82
|
|
|
51
83
|
### 2. Inside Codex — `$duet`
|
|
52
84
|
|
|
@@ -64,16 +96,26 @@ Codex's shell. Full guide:
|
|
|
64
96
|
### 3. From the terminal — `duet`
|
|
65
97
|
|
|
66
98
|
```bash
|
|
67
|
-
pipx install duet-cli # the command it installs is `duet`
|
|
99
|
+
pipx install duet-cli # recommended; the command it installs is `duet`
|
|
68
100
|
duet --task "Fix the failing test" --cwd ~/code/myrepo
|
|
69
101
|
```
|
|
70
102
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
103
|
+
`pipx` is the recommended install. Two other persistent options put `duet` on
|
|
104
|
+
PATH the same way:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
uv tool install duet-cli
|
|
108
|
+
python3 -m pip install --user duet-cli
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The PyPI package is `duet-cli` (bare `duet` on PyPI is Google's async library).
|
|
112
|
+
Add the `[yaml]` extra for `--config foo.yaml` support — `pipx install
|
|
113
|
+
'duet-cli[yaml]'`, `uv tool install 'duet-cli[yaml]'`, or `python3 -m pip
|
|
114
|
+
install --user 'duet-cli[yaml]'`. One-shot, no install:
|
|
74
115
|
`uvx --from duet-cli duet --task "..."` — note this is ephemeral and does not put
|
|
75
116
|
`duet` on PATH, so the `/duet` and `$duet` plugins need a persistent install
|
|
76
|
-
(`pipx install duet-cli`
|
|
117
|
+
(`pipx install duet-cli`, `uv tool install duet-cli`,
|
|
118
|
+
`python3 -m pip install --user duet-cli`, or `make install`) instead.
|
|
77
119
|
|
|
78
120
|
## Examples
|
|
79
121
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Unit tests for scripts/bump_release_version.py (stdlib unittest, no subprocess)."""
|
|
2
|
+
import json
|
|
3
|
+
import sys
|
|
4
|
+
import tempfile
|
|
5
|
+
import unittest
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
_REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
9
|
+
sys.path.insert(0, str(_REPO_ROOT / "scripts"))
|
|
10
|
+
|
|
11
|
+
import bump_release_version as brv # noqa: E402
|
|
12
|
+
|
|
13
|
+
PYPROJECT = """\
|
|
14
|
+
[build-system]
|
|
15
|
+
requires = ["setuptools>=77"]
|
|
16
|
+
build-backend = "setuptools.build_meta"
|
|
17
|
+
|
|
18
|
+
[project]
|
|
19
|
+
name = "duet-cli"
|
|
20
|
+
version = "0.2.1"
|
|
21
|
+
description = "x"
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
CLAUDE_PLUGIN = {"name": "duet", "version": "0.2.1", "author": {"name": "Volkan Altan"}}
|
|
25
|
+
CODEX_PLUGIN = {"name": "duet", "version": "0.2.1", "skills": "./skills/"}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _make_repo(tmp: Path) -> None:
|
|
29
|
+
(tmp / "pyproject.toml").write_text(PYPROJECT, encoding="utf-8")
|
|
30
|
+
claude = tmp / "plugins" / "duet-claude" / ".claude-plugin"
|
|
31
|
+
codex = tmp / "plugins" / "duet" / ".codex-plugin"
|
|
32
|
+
claude.mkdir(parents=True)
|
|
33
|
+
codex.mkdir(parents=True)
|
|
34
|
+
(claude / "plugin.json").write_text(json.dumps(CLAUDE_PLUGIN, indent=2) + "\n", encoding="utf-8")
|
|
35
|
+
(codex / "plugin.json").write_text(json.dumps(CODEX_PLUGIN, indent=2) + "\n", encoding="utf-8")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class TestParseStrictSemver(unittest.TestCase):
|
|
39
|
+
def test_valid(self):
|
|
40
|
+
self.assertEqual(brv.parse_strict_semver("1.2.3"), (1, 2, 3))
|
|
41
|
+
self.assertEqual(brv.parse_strict_semver("0.2.10"), (0, 2, 10))
|
|
42
|
+
|
|
43
|
+
def test_rejects(self):
|
|
44
|
+
for bad in ("v1.2.3", "0.3.0-rc1", "0.3.0+build", "1.2", "1.2.3.4", "1.2.x", ""):
|
|
45
|
+
with self.assertRaises(brv.BumpError, msg=bad):
|
|
46
|
+
brv.parse_strict_semver(bad)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestBump(unittest.TestCase):
|
|
50
|
+
def setUp(self):
|
|
51
|
+
self._tmp = tempfile.TemporaryDirectory()
|
|
52
|
+
self.root = Path(self._tmp.name)
|
|
53
|
+
_make_repo(self.root)
|
|
54
|
+
|
|
55
|
+
def tearDown(self):
|
|
56
|
+
self._tmp.cleanup()
|
|
57
|
+
|
|
58
|
+
def _versions(self):
|
|
59
|
+
py = brv.read_current_version((self.root / "pyproject.toml").read_text())
|
|
60
|
+
cl = json.loads((self.root / "plugins/duet-claude/.claude-plugin/plugin.json").read_text())
|
|
61
|
+
cx = json.loads((self.root / "plugins/duet/.codex-plugin/plugin.json").read_text())
|
|
62
|
+
return py, cl["version"], cx["version"]
|
|
63
|
+
|
|
64
|
+
def test_read_current_version(self):
|
|
65
|
+
self.assertEqual(brv.read_current_version(PYPROJECT), "0.2.1")
|
|
66
|
+
|
|
67
|
+
def test_bump_updates_all_three(self):
|
|
68
|
+
prev = brv.bump(self.root, "0.3.0")
|
|
69
|
+
self.assertEqual(prev, "0.2.1")
|
|
70
|
+
self.assertEqual(self._versions(), ("0.3.0", "0.3.0", "0.3.0"))
|
|
71
|
+
|
|
72
|
+
def test_bump_preserves_json_structure(self):
|
|
73
|
+
brv.bump(self.root, "0.2.2")
|
|
74
|
+
cl = json.loads((self.root / "plugins/duet-claude/.claude-plugin/plugin.json").read_text())
|
|
75
|
+
self.assertEqual(cl["name"], "duet")
|
|
76
|
+
self.assertEqual(cl["author"], {"name": "Volkan Altan"})
|
|
77
|
+
# only the build-system version-like string and [project] survive; the bump
|
|
78
|
+
# must not touch the [build-system] requires line.
|
|
79
|
+
py = (self.root / "pyproject.toml").read_text()
|
|
80
|
+
self.assertIn('requires = ["setuptools>=77"]', py)
|
|
81
|
+
self.assertIn('version = "0.2.2"', py)
|
|
82
|
+
|
|
83
|
+
def test_reject_downgrade(self):
|
|
84
|
+
with self.assertRaises(brv.BumpError):
|
|
85
|
+
brv.bump(self.root, "0.2.0")
|
|
86
|
+
self.assertEqual(self._versions(), ("0.2.1", "0.2.1", "0.2.1")) # unchanged
|
|
87
|
+
|
|
88
|
+
def test_reject_same_version(self):
|
|
89
|
+
with self.assertRaises(brv.BumpError):
|
|
90
|
+
brv.bump(self.root, "0.2.1")
|
|
91
|
+
|
|
92
|
+
def test_reject_invalid_before_write(self):
|
|
93
|
+
with self.assertRaises(brv.BumpError):
|
|
94
|
+
brv.bump(self.root, "v0.3.0")
|
|
95
|
+
self.assertEqual(self._versions(), ("0.2.1", "0.2.1", "0.2.1")) # no partial write
|
|
96
|
+
|
|
97
|
+
def test_missing_project_version_raises(self):
|
|
98
|
+
(self.root / "pyproject.toml").write_text(
|
|
99
|
+
"[build-system]\nrequires = []\n", encoding="utf-8"
|
|
100
|
+
)
|
|
101
|
+
with self.assertRaises(brv.BumpError):
|
|
102
|
+
brv.bump(self.root, "0.3.0")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == "__main__":
|
|
106
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|