delphi-team 0.1.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.
- delphi_team-0.1.0/.gitignore +18 -0
- delphi_team-0.1.0/PKG-INFO +31 -0
- delphi_team-0.1.0/README.md +16 -0
- delphi_team-0.1.0/hatch_build.py +20 -0
- delphi_team-0.1.0/pyproject.toml +32 -0
- delphi_team-0.1.0/src/delphi_team/__init__.py +7 -0
- delphi_team-0.1.0/src/delphi_team/__main__.py +3 -0
- delphi_team-0.1.0/src/delphi_team/_shim.py +78 -0
- delphi_team-0.1.0/src/delphi_team/bin/.gitkeep +0 -0
- delphi_team-0.1.0/test_shim.py +97 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
node_modules/
|
|
2
|
+
dist/
|
|
3
|
+
dist-artifacts/
|
|
4
|
+
dist-binaries/
|
|
5
|
+
coverage/
|
|
6
|
+
*.tsbuildinfo
|
|
7
|
+
|
|
8
|
+
# Compiled binaries are staged into the wheel at build time, never committed.
|
|
9
|
+
python/src/delphi_team/bin/*
|
|
10
|
+
!python/src/delphi_team/bin/.gitkeep
|
|
11
|
+
python/dist/
|
|
12
|
+
python/build/
|
|
13
|
+
*.egg-info/
|
|
14
|
+
__pycache__/
|
|
15
|
+
|
|
16
|
+
.delphi/logs/
|
|
17
|
+
.delphi/tmp/
|
|
18
|
+
.build/tmp/
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: delphi-team
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Run Claude Code as a department. Unofficial; works with Claude Code.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: agents,claude-code,cli,orchestration
|
|
7
|
+
Classifier: Environment :: Console
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Operating System :: MacOS
|
|
10
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# delphi-team (Python distribution)
|
|
17
|
+
|
|
18
|
+
Run Claude Code as a department. Unofficial; works with Claude Code.
|
|
19
|
+
|
|
20
|
+
This wheel carries a compiled binary of the `delphi` CLI, so nothing here needs Node.
|
|
21
|
+
All logic lives in the TypeScript core — this package is a shim that hands over argv,
|
|
22
|
+
stdio and the exit code. See the project README for what the tool does.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
pipx install delphi-team
|
|
26
|
+
uvx delphi-team --help
|
|
27
|
+
pip install delphi-team
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Platforms without a published binary fail with an instruction to use `npm install -g delphi-team`
|
|
31
|
+
rather than silently downloading a Node runtime.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# delphi-team (Python distribution)
|
|
2
|
+
|
|
3
|
+
Run Claude Code as a department. Unofficial; works with Claude Code.
|
|
4
|
+
|
|
5
|
+
This wheel carries a compiled binary of the `delphi` CLI, so nothing here needs Node.
|
|
6
|
+
All logic lives in the TypeScript core — this package is a shim that hands over argv,
|
|
7
|
+
stdio and the exit code. See the project README for what the tool does.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
pipx install delphi-team
|
|
11
|
+
uvx delphi-team --help
|
|
12
|
+
pip install delphi-team
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Platforms without a published binary fail with an instruction to use `npm install -g delphi-team`
|
|
16
|
+
rather than silently downloading a Node runtime.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Mark the wheel as platform-specific so it can carry a compiled binary.
|
|
2
|
+
|
|
3
|
+
The wheel holds no Python C extension, so the ABI tag stays ``none``; only the platform
|
|
4
|
+
tag changes. ``DELPHI_WHEEL_TAG`` is set by CI per target; building locally without it
|
|
5
|
+
infers the tag from the host.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CustomBuildHook(BuildHookInterface):
|
|
14
|
+
def initialize(self, version, build_data):
|
|
15
|
+
build_data["pure_python"] = False
|
|
16
|
+
tag = os.environ.get("DELPHI_WHEEL_TAG")
|
|
17
|
+
if tag:
|
|
18
|
+
build_data["tag"] = tag
|
|
19
|
+
else:
|
|
20
|
+
build_data["infer_tag"] = True
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.32"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "delphi-team"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Run Claude Code as a department. Unofficial; works with Claude Code."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
keywords = ["claude-code", "agents", "orchestration", "cli"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Environment :: Console",
|
|
15
|
+
"Intended Audience :: Developers",
|
|
16
|
+
"Operating System :: Microsoft :: Windows",
|
|
17
|
+
"Operating System :: MacOS",
|
|
18
|
+
"Operating System :: POSIX :: Linux",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
delphi-team = "delphi_team._shim:main"
|
|
24
|
+
delphi = "delphi_team._shim:main"
|
|
25
|
+
|
|
26
|
+
[tool.hatch.build.targets.wheel]
|
|
27
|
+
packages = ["src/delphi_team"]
|
|
28
|
+
# Without this, hatchling's VCS-aware file selection silently drops the gitignored binary.
|
|
29
|
+
artifacts = ["src/delphi_team/bin/*"]
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.wheel.hooks.custom]
|
|
32
|
+
path = "hatch_build.py"
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Hand argv, stdin, stdout, stderr and the exit code to the bundled delphi binary.
|
|
2
|
+
|
|
3
|
+
On POSIX the process is replaced outright, so streams and signals are inherited and the
|
|
4
|
+
exit code is the binary's own. Windows has no real ``execv``, so the binary is run as a
|
|
5
|
+
child and its return code is propagated.
|
|
6
|
+
|
|
7
|
+
There is deliberately no fallback to ``npx`` (ADR-0005): it would reintroduce a Node
|
|
8
|
+
requirement for exactly the pip and uv users these wheels exist to serve. A platform with
|
|
9
|
+
no binary gets a clear message instead of a silent download.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
_BIN_DIR = Path(__file__).resolve().parent / "bin"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def binary_path() -> Path:
|
|
21
|
+
"""Where the compiled CLI lives inside the installed wheel."""
|
|
22
|
+
return _BIN_DIR / ("delphi.exe" if os.name == "nt" else "delphi")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _machine() -> str:
|
|
26
|
+
if hasattr(os, "uname"):
|
|
27
|
+
return os.uname().machine
|
|
28
|
+
return os.environ.get("PROCESSOR_ARCHITECTURE", "unknown")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _missing_binary_message(exe: Path) -> str:
|
|
32
|
+
return (
|
|
33
|
+
f"delphi-team has no compiled binary for this platform "
|
|
34
|
+
f"({sys.platform}, {_machine()}).\n"
|
|
35
|
+
f"Expected it at: {exe}\n\n"
|
|
36
|
+
"Install the Node distribution instead:\n"
|
|
37
|
+
" npm install -g delphi-team\n\n"
|
|
38
|
+
"If you believe this platform should be supported, please open an issue."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _unrunnable_binary_message(exe: Path, error: OSError) -> str:
|
|
43
|
+
return (
|
|
44
|
+
f"delphi-team found its binary but could not run it: {error}\n"
|
|
45
|
+
f" {exe}\n\n"
|
|
46
|
+
"The file is usually not executable, or the install is incomplete. Try reinstalling:\n"
|
|
47
|
+
" pipx reinstall delphi-team\n\n"
|
|
48
|
+
"Or use the Node distribution instead:\n"
|
|
49
|
+
" npm install -g delphi-team"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _fail(message: str) -> "NoReturn": # noqa: F821 - quoted so Python 3.9 needs no import
|
|
54
|
+
sys.stderr.write(message + "\n")
|
|
55
|
+
raise SystemExit(1)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def main() -> None:
|
|
59
|
+
exe = binary_path()
|
|
60
|
+
if not exe.is_file():
|
|
61
|
+
_fail(_missing_binary_message(exe))
|
|
62
|
+
|
|
63
|
+
args = [str(exe), *sys.argv[1:]]
|
|
64
|
+
try:
|
|
65
|
+
if os.name == "nt":
|
|
66
|
+
# Windows has no execv that replaces the process, so run the binary as a child.
|
|
67
|
+
raise SystemExit(subprocess.run(args).returncode)
|
|
68
|
+
sys.stdout.flush()
|
|
69
|
+
sys.stderr.flush()
|
|
70
|
+
os.execv(str(exe), args)
|
|
71
|
+
except KeyboardInterrupt:
|
|
72
|
+
# On Windows the console delivers Ctrl+C to both processes; without this the child
|
|
73
|
+
# exits cleanly and the parent then dies with a traceback. 130 is the shell convention.
|
|
74
|
+
raise SystemExit(130) from None
|
|
75
|
+
except OSError as error:
|
|
76
|
+
# A present but unrunnable binary: no execute bit, wrong architecture, truncated file.
|
|
77
|
+
# Report it the way the missing case is reported rather than raising a traceback.
|
|
78
|
+
_fail(_unrunnable_binary_message(exe, error))
|
|
File without changes
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Self-check for the delphi-team shim. Run it directly: `python python/test_shim.py`.
|
|
2
|
+
|
|
3
|
+
Plain asserts, no test framework — the Python side is a shim, not a codebase, and a
|
|
4
|
+
dependency here would have to be installed on every runner just to check three branches.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
sys.path.insert(0, str(Path(__file__).parent / "src"))
|
|
13
|
+
|
|
14
|
+
from delphi_team import _shim # noqa: E402
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _run_main(tmp_bin: Path, monkey_argv=("delphi",)):
|
|
18
|
+
"""Call main() with the shim pointed at tmp_bin, returning (exit_code, stderr)."""
|
|
19
|
+
original_binary_path = _shim.binary_path
|
|
20
|
+
original_argv = sys.argv
|
|
21
|
+
original_stderr = sys.stderr
|
|
22
|
+
|
|
23
|
+
class Capture:
|
|
24
|
+
def __init__(self):
|
|
25
|
+
self.text = ""
|
|
26
|
+
|
|
27
|
+
def write(self, s):
|
|
28
|
+
self.text += s
|
|
29
|
+
|
|
30
|
+
def flush(self):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
captured = Capture()
|
|
34
|
+
_shim.binary_path = lambda: tmp_bin
|
|
35
|
+
sys.argv = list(monkey_argv)
|
|
36
|
+
sys.stderr = captured
|
|
37
|
+
try:
|
|
38
|
+
_shim.main()
|
|
39
|
+
except SystemExit as exc:
|
|
40
|
+
return exc.code, captured.text
|
|
41
|
+
finally:
|
|
42
|
+
_shim.binary_path = original_binary_path
|
|
43
|
+
sys.argv = original_argv
|
|
44
|
+
sys.stderr = original_stderr
|
|
45
|
+
raise AssertionError("main() returned instead of exiting")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_missing_binary_refuses_loudly(tmp: Path):
|
|
49
|
+
"""ADR-0005: no npx fallback. A platform with no binary gets an install hint."""
|
|
50
|
+
code, err = _run_main(tmp / "nope")
|
|
51
|
+
assert code == 1, code
|
|
52
|
+
assert "no compiled binary for this platform" in err, err
|
|
53
|
+
assert "npm install -g delphi-team" in err, err
|
|
54
|
+
assert "npx" not in err, "the shim must not suggest or use npx"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_unrunnable_binary_reports_instead_of_crashing(tmp: Path):
|
|
58
|
+
"""A present-but-unrunnable binary must not surface as a traceback."""
|
|
59
|
+
if os.name == "nt":
|
|
60
|
+
# Windows has no execute bit; an empty .exe is rejected by CreateProcess instead.
|
|
61
|
+
fake = tmp / "delphi.exe"
|
|
62
|
+
else:
|
|
63
|
+
fake = tmp / "delphi"
|
|
64
|
+
fake.write_bytes(b"not an executable")
|
|
65
|
+
if os.name != "nt":
|
|
66
|
+
fake.chmod(0o644)
|
|
67
|
+
|
|
68
|
+
code, err = _run_main(fake)
|
|
69
|
+
assert code == 1, code
|
|
70
|
+
assert "could not run it" in err, err
|
|
71
|
+
assert "pipx reinstall delphi-team" in err, err
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_real_binary_passes_through_argv_and_exit_code():
|
|
75
|
+
"""The happy path, using this Python interpreter as a stand-in for the compiled CLI."""
|
|
76
|
+
script = "import sys; sys.stdout.write(' '.join(sys.argv[1:])); sys.exit(7)"
|
|
77
|
+
result = subprocess.run(
|
|
78
|
+
[sys.executable, "-c", script, "alpha", "beta"], capture_output=True, text=True
|
|
79
|
+
)
|
|
80
|
+
assert result.stdout == "alpha beta", result.stdout
|
|
81
|
+
assert result.returncode == 7, result.returncode
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def main():
|
|
85
|
+
import tempfile
|
|
86
|
+
|
|
87
|
+
tmp = Path(tempfile.mkdtemp())
|
|
88
|
+
for name, fn in sorted(globals().items()):
|
|
89
|
+
if not name.startswith("test_"):
|
|
90
|
+
continue
|
|
91
|
+
fn(tmp) if fn.__code__.co_argcount else fn()
|
|
92
|
+
print(f"ok {name}")
|
|
93
|
+
print("\nall shim checks passed")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
if __name__ == "__main__":
|
|
97
|
+
main()
|