pipper-cli 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.
- pipper_cli-0.1.0/LICENSE +21 -0
- pipper_cli-0.1.0/PKG-INFO +95 -0
- pipper_cli-0.1.0/README.md +77 -0
- pipper_cli-0.1.0/pipper.py +159 -0
- pipper_cli-0.1.0/pipper_cli.egg-info/PKG-INFO +95 -0
- pipper_cli-0.1.0/pipper_cli.egg-info/SOURCES.txt +9 -0
- pipper_cli-0.1.0/pipper_cli.egg-info/dependency_links.txt +1 -0
- pipper_cli-0.1.0/pipper_cli.egg-info/entry_points.txt +2 -0
- pipper_cli-0.1.0/pipper_cli.egg-info/top_level.txt +1 -0
- pipper_cli-0.1.0/pyproject.toml +30 -0
- pipper_cli-0.1.0/setup.cfg +4 -0
pipper_cli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sanjay (sanjoxtech)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pipper-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Drive Claude Code headlessly in a tmux session โ script and automate the claude CLI.
|
|
5
|
+
Author-email: "Sanjay (sanjoxtech)" <sanjox.tech@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://sanjox.tech
|
|
8
|
+
Project-URL: Repository, https://github.com/sanjoxtech/pipper
|
|
9
|
+
Keywords: claude,claude-code,tmux,automation,headless,cli,agents
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: POSIX
|
|
13
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# ๐ช pipper
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<img src="docs/assets/readme-banner.png" alt="pipper โ drive Claude Code, headlessly" width="100%">
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
**Drive Claude Code headlessly in a tmux session โ script and automate the `claude` CLI from shell, cron, or code.**
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+

|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
`pipper` is a tiny harness that runs the interactive `claude` CLI inside **tmux**, auto-accepts its permission dialogs, sends a prompt, and returns Claude's answer. It lets you call Claude Code from scripts, cron jobs, pipelines, or another program โ without sitting at the terminal.
|
|
34
|
+
|
|
35
|
+
It drives your normal, already-logged-in `claude` client, so it inherits whatever plan and limits that login has. **Use it within [Anthropic's terms of service](https://www.anthropic.com/legal/aup).**
|
|
36
|
+
|
|
37
|
+
## Why
|
|
38
|
+
|
|
39
|
+
- **Automate Claude Code** in CI, cron, or batch jobs
|
|
40
|
+
- **Glue it into other tools** โ get an answer back as text or JSON
|
|
41
|
+
- **TUI-version-proof** โ instead of scraping the terminal, pipper asks Claude to write its answer to a temp file, then reads it back
|
|
42
|
+
|
|
43
|
+
## Requires
|
|
44
|
+
|
|
45
|
+
- [`tmux`](https://github.com/tmux/tmux)
|
|
46
|
+
- the [`claude` CLI](https://docs.claude.com/en/docs/claude-code) โ installed **and already authenticated** (`claude` runs without a login prompt)
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install pipper-cli # once published; command is `pipper`
|
|
52
|
+
# or from source:
|
|
53
|
+
git clone https://github.com/sanjoxtech/pipper && cd pipper && pip install -e .
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Zero dependencies โ Python stdlib only.
|
|
57
|
+
|
|
58
|
+
## Use
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pipper "summarize this repo's README in 3 bullets"
|
|
62
|
+
echo "list the riskiest lines in auth.py" | pipper --cwd ./myproject
|
|
63
|
+
pipper -f task.txt --timeout 900 --json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Options: `--cwd <dir>`, `--session <name>`, `--timeout <s>`, `--boot-timeout <s>`, `--json`.
|
|
67
|
+
|
|
68
|
+
From Python:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import pipper
|
|
72
|
+
answer = pipper.run("explain what this project does", cwd="./repo")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## How it works
|
|
76
|
+
|
|
77
|
+
1. Spawns `claude --permission-mode bypassPermissions` in a fresh tmux session
|
|
78
|
+
2. Auto-accepts the startup/permission dialogs (handles different orderings + plain y/n)
|
|
79
|
+
3. Pastes your prompt via an isolated tmux buffer (so parallel runs don't clash)
|
|
80
|
+
4. Asks Claude to **write its final answer to a temp file**, then polls that file
|
|
81
|
+
5. Returns the answer; tears the session down; bails early on repeated API errors
|
|
82
|
+
|
|
83
|
+
## Limitations / honest notes
|
|
84
|
+
|
|
85
|
+
- Relies on `tmux` + the interactive `claude` TUI. If Claude Code changes its startup dialogs a lot, the auto-accept heuristics may need a tweak.
|
|
86
|
+
- `bypassPermissions` means Claude can run tools without asking โ only point it at directories you trust.
|
|
87
|
+
- It automates your normal `claude` login; respect Anthropic's usage terms and rate limits.
|
|
88
|
+
|
|
89
|
+
## Author
|
|
90
|
+
|
|
91
|
+
Built by **Sanjay** ([sanjoxtech](https://github.com/sanjoxtech)) โ [sanjox.tech](https://sanjox.tech) ยท sanjox.tech@gmail.com
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT โ see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# ๐ช pipper
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="docs/assets/readme-banner.png" alt="pipper โ drive Claude Code, headlessly" width="100%">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
**Drive Claude Code headlessly in a tmux session โ script and automate the `claude` CLI from shell, cron, or code.**
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+

|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
`pipper` is a tiny harness that runs the interactive `claude` CLI inside **tmux**, auto-accepts its permission dialogs, sends a prompt, and returns Claude's answer. It lets you call Claude Code from scripts, cron jobs, pipelines, or another program โ without sitting at the terminal.
|
|
16
|
+
|
|
17
|
+
It drives your normal, already-logged-in `claude` client, so it inherits whatever plan and limits that login has. **Use it within [Anthropic's terms of service](https://www.anthropic.com/legal/aup).**
|
|
18
|
+
|
|
19
|
+
## Why
|
|
20
|
+
|
|
21
|
+
- **Automate Claude Code** in CI, cron, or batch jobs
|
|
22
|
+
- **Glue it into other tools** โ get an answer back as text or JSON
|
|
23
|
+
- **TUI-version-proof** โ instead of scraping the terminal, pipper asks Claude to write its answer to a temp file, then reads it back
|
|
24
|
+
|
|
25
|
+
## Requires
|
|
26
|
+
|
|
27
|
+
- [`tmux`](https://github.com/tmux/tmux)
|
|
28
|
+
- the [`claude` CLI](https://docs.claude.com/en/docs/claude-code) โ installed **and already authenticated** (`claude` runs without a login prompt)
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install pipper-cli # once published; command is `pipper`
|
|
34
|
+
# or from source:
|
|
35
|
+
git clone https://github.com/sanjoxtech/pipper && cd pipper && pip install -e .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Zero dependencies โ Python stdlib only.
|
|
39
|
+
|
|
40
|
+
## Use
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pipper "summarize this repo's README in 3 bullets"
|
|
44
|
+
echo "list the riskiest lines in auth.py" | pipper --cwd ./myproject
|
|
45
|
+
pipper -f task.txt --timeout 900 --json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Options: `--cwd <dir>`, `--session <name>`, `--timeout <s>`, `--boot-timeout <s>`, `--json`.
|
|
49
|
+
|
|
50
|
+
From Python:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import pipper
|
|
54
|
+
answer = pipper.run("explain what this project does", cwd="./repo")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## How it works
|
|
58
|
+
|
|
59
|
+
1. Spawns `claude --permission-mode bypassPermissions` in a fresh tmux session
|
|
60
|
+
2. Auto-accepts the startup/permission dialogs (handles different orderings + plain y/n)
|
|
61
|
+
3. Pastes your prompt via an isolated tmux buffer (so parallel runs don't clash)
|
|
62
|
+
4. Asks Claude to **write its final answer to a temp file**, then polls that file
|
|
63
|
+
5. Returns the answer; tears the session down; bails early on repeated API errors
|
|
64
|
+
|
|
65
|
+
## Limitations / honest notes
|
|
66
|
+
|
|
67
|
+
- Relies on `tmux` + the interactive `claude` TUI. If Claude Code changes its startup dialogs a lot, the auto-accept heuristics may need a tweak.
|
|
68
|
+
- `bypassPermissions` means Claude can run tools without asking โ only point it at directories you trust.
|
|
69
|
+
- It automates your normal `claude` login; respect Anthropic's usage terms and rate limits.
|
|
70
|
+
|
|
71
|
+
## Author
|
|
72
|
+
|
|
73
|
+
Built by **Sanjay** ([sanjoxtech](https://github.com/sanjoxtech)) โ [sanjox.tech](https://sanjox.tech) ยท sanjox.tech@gmail.com
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
MIT โ see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
pipper โ drive Claude Code headlessly in a tmux session.
|
|
4
|
+
|
|
5
|
+
A tiny harness that runs the interactive `claude` CLI inside tmux, auto-accepts its
|
|
6
|
+
permission dialogs, sends a prompt, and returns Claude's answer โ so you can script
|
|
7
|
+
and automate Claude Code from a shell, cron job, or another program.
|
|
8
|
+
|
|
9
|
+
It drives your normal, already-logged-in `claude` client (so it inherits whatever
|
|
10
|
+
plan and limits that login has). Use it within Anthropic's terms of service.
|
|
11
|
+
|
|
12
|
+
Requires: `tmux` and the `claude` CLI installed and already authenticated.
|
|
13
|
+
|
|
14
|
+
pipper "summarize this repo's README in 3 bullets"
|
|
15
|
+
echo "list the riskiest lines in auth.py" | pipper --cwd ./myproject
|
|
16
|
+
pipper -f task.txt --timeout 900 --json
|
|
17
|
+
|
|
18
|
+
How it works: pipper asks Claude to write its final answer to a temp file (instead of
|
|
19
|
+
scraping the terminal UI), then polls that file โ robust and TUI-version-proof.
|
|
20
|
+
|
|
21
|
+
Zero dependencies. Python stdlib only.
|
|
22
|
+
"""
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
import argparse, json, os, re, subprocess, sys, tempfile, time
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
__version__ = "0.1.0"
|
|
28
|
+
|
|
29
|
+
_OPT_RE = re.compile(r"^\s*(?:โฏ\s*)?(\d+)\.\s*(.+)$")
|
|
30
|
+
_DIALOG_HINTS = ("enter to confirm", "do you want", "do you trust", "i accept",
|
|
31
|
+
"proceed?", "(y/n)", "[y/n]", "trust the files")
|
|
32
|
+
_YES_WORDS = ("yes", "accept", "proceed", "trust", "continue", "allow")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _tmux(*args) -> subprocess.CompletedProcess:
|
|
36
|
+
return subprocess.run(["tmux", *args], capture_output=True, text=True)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _require_tools():
|
|
40
|
+
for tool in ("tmux", "claude"):
|
|
41
|
+
if subprocess.run(["which", tool], capture_output=True).returncode != 0:
|
|
42
|
+
sys.exit(f"pipper: `{tool}` not found on PATH. Install it and make sure it works first.")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def auto_accept(session: str) -> bool:
|
|
46
|
+
"""If a yes/no confirmation dialog is showing, press the affirmative option.
|
|
47
|
+
Handles different option orderings (Yes can be 1 or 2) and plain y/n prompts."""
|
|
48
|
+
pane = _tmux("capture-pane", "-t", session, "-p").stdout
|
|
49
|
+
low = pane.lower()
|
|
50
|
+
if not any(h in low for h in _DIALOG_HINTS):
|
|
51
|
+
return False
|
|
52
|
+
for line in pane.splitlines():
|
|
53
|
+
m = _OPT_RE.match(line.strip())
|
|
54
|
+
if m and any(w in m.group(2).lower() for w in _YES_WORDS):
|
|
55
|
+
_tmux("send-keys", "-t", session, m.group(1)); time.sleep(0.3)
|
|
56
|
+
_tmux("send-keys", "-t", session, "Enter"); time.sleep(1.0)
|
|
57
|
+
return True
|
|
58
|
+
if "(y/n)" in low or "[y/n]" in low:
|
|
59
|
+
_tmux("send-keys", "-t", session, "y")
|
|
60
|
+
_tmux("send-keys", "-t", session, "Enter"); time.sleep(0.8)
|
|
61
|
+
return True
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def run(prompt: str, cwd: str = ".", session: str | None = None,
|
|
66
|
+
boot_timeout: int = 30, work_timeout: int = 600) -> str:
|
|
67
|
+
"""Run `prompt` through interactive claude in tmux; return Claude's answer text."""
|
|
68
|
+
_require_tools()
|
|
69
|
+
session = session or f"pipper_{os.getpid()}"
|
|
70
|
+
cwd = str(Path(cwd).resolve())
|
|
71
|
+
tmp = Path(tempfile.mkdtemp(prefix="pipper_"))
|
|
72
|
+
outfile = tmp / "answer.txt"
|
|
73
|
+
promptfile = tmp / "prompt.txt"
|
|
74
|
+
|
|
75
|
+
wrapped = (
|
|
76
|
+
"Work autonomously. Do NOT ask me any questions. Do NOT print the final answer "
|
|
77
|
+
f"to chat. When done, use your Write tool to save your FINAL ANSWER to:\n{outfile}\n\n"
|
|
78
|
+
"--- TASK ---\n" + prompt
|
|
79
|
+
)
|
|
80
|
+
promptfile.write_text(wrapped, encoding="utf-8")
|
|
81
|
+
|
|
82
|
+
_tmux("kill-session", "-t", session)
|
|
83
|
+
_tmux("new-session", "-d", "-s", session, "-x", "220", "-y", "50")
|
|
84
|
+
_tmux("send-keys", "-t", session,
|
|
85
|
+
f"cd {cwd} && claude --permission-mode bypassPermissions", "Enter")
|
|
86
|
+
|
|
87
|
+
# wait for the input box, accepting any startup dialogs
|
|
88
|
+
t0 = time.time(); ready = False
|
|
89
|
+
while time.time() - t0 < boot_timeout:
|
|
90
|
+
pane = _tmux("capture-pane", "-t", session, "-p").stdout
|
|
91
|
+
low = pane.lower()
|
|
92
|
+
if ("shortcuts" in pane or "Welcome to Claude" in pane) and not any(
|
|
93
|
+
h in low for h in ("i accept", "do you trust", "trust the files", "enter to confirm")):
|
|
94
|
+
ready = True; break
|
|
95
|
+
auto_accept(session); time.sleep(1)
|
|
96
|
+
if not ready:
|
|
97
|
+
time.sleep(4)
|
|
98
|
+
|
|
99
|
+
# paste the prompt via an isolated named buffer, then submit
|
|
100
|
+
buf = f"b_{session}"
|
|
101
|
+
_tmux("load-buffer", "-b", buf, str(promptfile))
|
|
102
|
+
_tmux("paste-buffer", "-b", buf, "-t", session, "-d")
|
|
103
|
+
time.sleep(1.5)
|
|
104
|
+
_tmux("send-keys", "-t", session, "Enter")
|
|
105
|
+
|
|
106
|
+
# poll for the answer file; bail early on repeated API errors
|
|
107
|
+
t0 = time.time()
|
|
108
|
+
while time.time() - t0 < work_timeout:
|
|
109
|
+
if outfile.exists():
|
|
110
|
+
text = outfile.read_text(encoding="utf-8").strip()
|
|
111
|
+
if text:
|
|
112
|
+
_tmux("kill-session", "-t", session)
|
|
113
|
+
return text
|
|
114
|
+
pane = _tmux("capture-pane", "-t", session, "-p").stdout
|
|
115
|
+
auto_accept(session)
|
|
116
|
+
m = re.search(r"(API error.*?attempt\s+(\d+)/10|overloaded|rate.?limit|Connection error)", pane, re.I)
|
|
117
|
+
if m and (int(m.group(2)) if m.group(2) else 99) >= 4:
|
|
118
|
+
line = next((l.strip() for l in pane.splitlines() if "error" in l.lower()), m.group(0))
|
|
119
|
+
_tmux("kill-session", "-t", session)
|
|
120
|
+
raise RuntimeError(f"claude reported a repeated error: {line}")
|
|
121
|
+
time.sleep(3)
|
|
122
|
+
|
|
123
|
+
tail = _tmux("capture-pane", "-t", session, "-p").stdout[-1200:]
|
|
124
|
+
_tmux("kill-session", "-t", session)
|
|
125
|
+
raise TimeoutError(f"pipper timed out after {work_timeout}s. claude pane tail:\n{tail}")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def main(argv=None) -> int:
|
|
129
|
+
ap = argparse.ArgumentParser(prog="pipper", description="Drive Claude Code headlessly via tmux.")
|
|
130
|
+
ap.add_argument("prompt", nargs="?", help="the prompt (or use -f / stdin)")
|
|
131
|
+
ap.add_argument("-f", "--file", help="read the prompt from a file")
|
|
132
|
+
ap.add_argument("--cwd", default=".", help="working directory to run claude in")
|
|
133
|
+
ap.add_argument("--session", default=None, help="tmux session name (default: pipper_<pid>)")
|
|
134
|
+
ap.add_argument("--timeout", type=int, default=600, help="max seconds to wait for the answer")
|
|
135
|
+
ap.add_argument("--boot-timeout", type=int, default=30, help="max seconds to wait for claude to start")
|
|
136
|
+
ap.add_argument("--json", action="store_true", help="print {\"answer\": ...} as JSON")
|
|
137
|
+
ap.add_argument("--version", action="version", version=f"pipper {__version__}")
|
|
138
|
+
args = ap.parse_args(argv)
|
|
139
|
+
|
|
140
|
+
if args.file:
|
|
141
|
+
prompt = Path(args.file).read_text(encoding="utf-8")
|
|
142
|
+
elif args.prompt:
|
|
143
|
+
prompt = args.prompt
|
|
144
|
+
elif not sys.stdin.isatty():
|
|
145
|
+
prompt = sys.stdin.read()
|
|
146
|
+
else:
|
|
147
|
+
ap.error("no prompt given (pass it as an argument, with -f, or via stdin)")
|
|
148
|
+
|
|
149
|
+
if not prompt.strip():
|
|
150
|
+
ap.error("empty prompt")
|
|
151
|
+
|
|
152
|
+
answer = run(prompt, cwd=args.cwd, session=args.session,
|
|
153
|
+
boot_timeout=args.boot_timeout, work_timeout=args.timeout)
|
|
154
|
+
print(json.dumps({"answer": answer}) if args.json else answer)
|
|
155
|
+
return 0
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
if __name__ == "__main__":
|
|
159
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pipper-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Drive Claude Code headlessly in a tmux session โ script and automate the claude CLI.
|
|
5
|
+
Author-email: "Sanjay (sanjoxtech)" <sanjox.tech@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://sanjox.tech
|
|
8
|
+
Project-URL: Repository, https://github.com/sanjoxtech/pipper
|
|
9
|
+
Keywords: claude,claude-code,tmux,automation,headless,cli,agents
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: POSIX
|
|
13
|
+
Classifier: Topic :: Software Development :: Build Tools
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# ๐ช pipper
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<img src="docs/assets/readme-banner.png" alt="pipper โ drive Claude Code, headlessly" width="100%">
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
**Drive Claude Code headlessly in a tmux session โ script and automate the `claude` CLI from shell, cron, or code.**
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+

|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
`pipper` is a tiny harness that runs the interactive `claude` CLI inside **tmux**, auto-accepts its permission dialogs, sends a prompt, and returns Claude's answer. It lets you call Claude Code from scripts, cron jobs, pipelines, or another program โ without sitting at the terminal.
|
|
34
|
+
|
|
35
|
+
It drives your normal, already-logged-in `claude` client, so it inherits whatever plan and limits that login has. **Use it within [Anthropic's terms of service](https://www.anthropic.com/legal/aup).**
|
|
36
|
+
|
|
37
|
+
## Why
|
|
38
|
+
|
|
39
|
+
- **Automate Claude Code** in CI, cron, or batch jobs
|
|
40
|
+
- **Glue it into other tools** โ get an answer back as text or JSON
|
|
41
|
+
- **TUI-version-proof** โ instead of scraping the terminal, pipper asks Claude to write its answer to a temp file, then reads it back
|
|
42
|
+
|
|
43
|
+
## Requires
|
|
44
|
+
|
|
45
|
+
- [`tmux`](https://github.com/tmux/tmux)
|
|
46
|
+
- the [`claude` CLI](https://docs.claude.com/en/docs/claude-code) โ installed **and already authenticated** (`claude` runs without a login prompt)
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install pipper-cli # once published; command is `pipper`
|
|
52
|
+
# or from source:
|
|
53
|
+
git clone https://github.com/sanjoxtech/pipper && cd pipper && pip install -e .
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Zero dependencies โ Python stdlib only.
|
|
57
|
+
|
|
58
|
+
## Use
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pipper "summarize this repo's README in 3 bullets"
|
|
62
|
+
echo "list the riskiest lines in auth.py" | pipper --cwd ./myproject
|
|
63
|
+
pipper -f task.txt --timeout 900 --json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Options: `--cwd <dir>`, `--session <name>`, `--timeout <s>`, `--boot-timeout <s>`, `--json`.
|
|
67
|
+
|
|
68
|
+
From Python:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import pipper
|
|
72
|
+
answer = pipper.run("explain what this project does", cwd="./repo")
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## How it works
|
|
76
|
+
|
|
77
|
+
1. Spawns `claude --permission-mode bypassPermissions` in a fresh tmux session
|
|
78
|
+
2. Auto-accepts the startup/permission dialogs (handles different orderings + plain y/n)
|
|
79
|
+
3. Pastes your prompt via an isolated tmux buffer (so parallel runs don't clash)
|
|
80
|
+
4. Asks Claude to **write its final answer to a temp file**, then polls that file
|
|
81
|
+
5. Returns the answer; tears the session down; bails early on repeated API errors
|
|
82
|
+
|
|
83
|
+
## Limitations / honest notes
|
|
84
|
+
|
|
85
|
+
- Relies on `tmux` + the interactive `claude` TUI. If Claude Code changes its startup dialogs a lot, the auto-accept heuristics may need a tweak.
|
|
86
|
+
- `bypassPermissions` means Claude can run tools without asking โ only point it at directories you trust.
|
|
87
|
+
- It automates your normal `claude` login; respect Anthropic's usage terms and rate limits.
|
|
88
|
+
|
|
89
|
+
## Author
|
|
90
|
+
|
|
91
|
+
Built by **Sanjay** ([sanjoxtech](https://github.com/sanjoxtech)) โ [sanjox.tech](https://sanjox.tech) ยท sanjox.tech@gmail.com
|
|
92
|
+
|
|
93
|
+
## License
|
|
94
|
+
|
|
95
|
+
MIT โ see [LICENSE](LICENSE).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pipper
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pipper-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Drive Claude Code headlessly in a tmux session โ script and automate the claude CLI."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Sanjay (sanjoxtech)", email = "sanjox.tech@gmail.com" }]
|
|
13
|
+
keywords = ["claude", "claude-code", "tmux", "automation", "headless", "cli", "agents"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Operating System :: POSIX",
|
|
18
|
+
"Topic :: Software Development :: Build Tools",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [] # zero dependencies, on purpose
|
|
21
|
+
|
|
22
|
+
[project.urls]
|
|
23
|
+
Homepage = "https://sanjox.tech"
|
|
24
|
+
Repository = "https://github.com/sanjoxtech/pipper"
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
pipper = "pipper:main"
|
|
28
|
+
|
|
29
|
+
[tool.setuptools]
|
|
30
|
+
py-modules = ["pipper"]
|