hire-ai 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

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.
hire/cli.py CHANGED
@@ -4,10 +4,10 @@ import argparse
4
4
  import sys
5
5
 
6
6
  from . import __version__
7
- from .commands import run_ask, run_delete, run_sessions, run_show
7
+ from .commands import run_ask, run_delete, run_doctor, run_sessions, run_show
8
8
 
9
9
 
10
- SUBCOMMANDS = {"sessions", "show", "delete", "help", "--help", "-h", "--version"}
10
+ SUBCOMMANDS = {"sessions", "show", "delete", "doctor", "help", "--help", "-h", "--version"}
11
11
 
12
12
 
13
13
  def main() -> int:
@@ -68,6 +68,9 @@ def main() -> int:
68
68
  help="Delete without confirmation",
69
69
  )
70
70
 
71
+ # doctor command
72
+ subparsers.add_parser("doctor", help="Check environment and agent availability")
73
+
71
74
  args = parser.parse_args()
72
75
 
73
76
  if args.command is None:
@@ -81,6 +84,8 @@ def main() -> int:
81
84
  return run_show(args)
82
85
  elif args.command == "delete":
83
86
  return run_delete(args)
87
+ elif args.command == "doctor":
88
+ return run_doctor(args)
84
89
  else:
85
90
  print_usage()
86
91
  return 1
@@ -126,6 +131,16 @@ def run_default() -> int:
126
131
  action="store_true",
127
132
  help="Output in JSON format",
128
133
  )
134
+ parser.add_argument(
135
+ "--clip",
136
+ action="store_true",
137
+ help="Copy output to clipboard",
138
+ )
139
+ parser.add_argument(
140
+ "-o", "--out",
141
+ metavar="FILE",
142
+ help="Write output to file",
143
+ )
129
144
 
130
145
  args = parser.parse_args()
131
146
  return run_ask(args)
@@ -141,6 +156,7 @@ Usage:
141
156
  hire sessions [target] List sessions
142
157
  hire show <name-or-id> Show session details
143
158
  hire delete <name-or-id> Delete a session
159
+ hire doctor Check environment
144
160
 
145
161
  Targets:
146
162
  claude, codex, gemini
@@ -151,6 +167,8 @@ Options:
151
167
  -n, --name NAME Name the session
152
168
  -m, --model MODEL Specify model
153
169
  --json Output in JSON format
170
+ --clip Copy output to clipboard
171
+ -o, --out FILE Write output to file
154
172
 
155
173
  Examples:
156
174
  hire codex "Design a REST API"
hire/clipboard.py ADDED
@@ -0,0 +1,40 @@
1
+ """Clipboard utilities."""
2
+
3
+ import platform
4
+ import subprocess
5
+
6
+
7
+ def copy_to_clipboard(text: str) -> bool:
8
+ """Copy text to system clipboard. Returns True on success."""
9
+ system = platform.system()
10
+ try:
11
+ if system == "Darwin": # macOS
12
+ # Don't override env - let system locale handle it
13
+ subprocess.run(
14
+ ["pbcopy"],
15
+ input=text,
16
+ text=True,
17
+ encoding="utf-8",
18
+ check=True,
19
+ )
20
+ elif system == "Linux":
21
+ # Try xclip first, fall back to xsel
22
+ try:
23
+ subprocess.run(
24
+ ["xclip", "-selection", "clipboard"],
25
+ input=text.encode("utf-8"),
26
+ check=True,
27
+ )
28
+ except FileNotFoundError:
29
+ subprocess.run(
30
+ ["xsel", "--clipboard", "--input"],
31
+ input=text.encode("utf-8"),
32
+ check=True,
33
+ )
34
+ elif system == "Windows":
35
+ subprocess.run(["clip"], input=text.encode("utf-8"), check=True)
36
+ else:
37
+ return False
38
+ return True
39
+ except (subprocess.CalledProcessError, FileNotFoundError):
40
+ return False
hire/commands/__init__.py CHANGED
@@ -4,5 +4,6 @@ from .ask import run_ask
4
4
  from .sessions import run_sessions
5
5
  from .show import run_show
6
6
  from .delete import run_delete
7
+ from .doctor import run_doctor
7
8
 
8
- __all__ = ["run_ask", "run_sessions", "run_show", "run_delete"]
9
+ __all__ = ["run_ask", "run_sessions", "run_show", "run_delete", "run_doctor"]
hire/commands/ask.py CHANGED
@@ -5,6 +5,7 @@ import sys
5
5
  from argparse import Namespace
6
6
 
7
7
  from ..adapters import get_adapter
8
+ from ..clipboard import copy_to_clipboard
8
9
  from ..session import (
9
10
  create_session,
10
11
  find_session,
@@ -14,25 +15,49 @@ from ..session import (
14
15
  )
15
16
 
16
17
 
18
+ def read_stdin() -> str | None:
19
+ """Read from stdin if available (pipe/redirect)."""
20
+ if sys.stdin.isatty():
21
+ return None
22
+ content = sys.stdin.read()
23
+ return content.strip() if content else None
24
+
25
+
26
+ def build_message(message: str | None, stdin: str | None) -> str | None:
27
+ """Build the final message from args and stdin."""
28
+ if message and stdin:
29
+ return f"{message}\n\n--- stdin ---\n{stdin}"
30
+ elif stdin:
31
+ return stdin
32
+ else:
33
+ return message
34
+
35
+
17
36
  VALID_TARGETS = {"claude", "codex", "gemini"}
18
37
 
19
38
 
20
39
  def run_ask(args: Namespace) -> int:
21
40
  """Run the ask command."""
22
41
  target = args.target
23
- message = args.message
42
+ arg_message = args.message
43
+ stdin_content = read_stdin()
24
44
  continue_session = getattr(args, "continue_session", False)
25
45
  session_id = args.session
26
46
  name = args.name
27
47
  model = args.model
28
48
  output_json = args.json
49
+ copy_clip = getattr(args, "clip", False)
50
+ out_file = getattr(args, "out", None)
29
51
 
30
52
  # Handle case where target is actually the message (when target is omitted)
31
- # e.g., "delegate 'message'" -> target='message', message=None
32
- if target and target not in VALID_TARGETS and message is None:
33
- message = target
53
+ # e.g., "hire 'message'" -> target='message', message=None
54
+ if target and target not in VALID_TARGETS and arg_message is None:
55
+ arg_message = target
34
56
  target = None
35
57
 
58
+ # Build final message from args and stdin
59
+ message = build_message(arg_message, stdin_content)
60
+
36
61
  # Load config for defaults
37
62
  from ..config import load_config
38
63
  config = load_config()
@@ -136,8 +161,26 @@ def run_ask(args: Namespace) -> int:
136
161
  "agent": target,
137
162
  "name": session.get("name"),
138
163
  }
139
- print(json.dumps(output, indent=2, ensure_ascii=False))
164
+ output_text = json.dumps(output, indent=2, ensure_ascii=False)
140
165
  else:
141
- print(result.get("response", ""))
166
+ output_text = result.get("response", "")
167
+
168
+ print(output_text)
169
+
170
+ # Copy to clipboard if requested
171
+ if copy_clip:
172
+ if copy_to_clipboard(output_text):
173
+ print("\n(Copied to clipboard)", file=sys.stderr)
174
+ else:
175
+ print("\n(Failed to copy to clipboard)", file=sys.stderr)
176
+
177
+ # Write to file if requested
178
+ if out_file:
179
+ try:
180
+ with open(out_file, "w", encoding="utf-8") as f:
181
+ f.write(output_text)
182
+ print(f"\n(Written to {out_file})", file=sys.stderr)
183
+ except OSError as e:
184
+ print(f"\n(Failed to write to {out_file}: {e})", file=sys.stderr)
142
185
 
143
186
  return 0
@@ -0,0 +1,60 @@
1
+ """Doctor command implementation."""
2
+
3
+ import shutil
4
+ import sys
5
+ from argparse import Namespace
6
+
7
+ from .. import __version__
8
+ from ..paths import get_config_path, get_sessions_dir
9
+
10
+
11
+ AGENTS = {
12
+ "claude": "claude",
13
+ "codex": "codex",
14
+ "gemini": "gemini",
15
+ }
16
+
17
+
18
+ def run_doctor(args: Namespace) -> int:
19
+ """Run the doctor command to check environment."""
20
+ print(f"hire-ai v{__version__}")
21
+ print(f"Python {sys.version.split()[0]}")
22
+ print()
23
+
24
+ # Check agents
25
+ print("Checking agents...")
26
+ found = 0
27
+ missing = 0
28
+ for name, cmd in AGENTS.items():
29
+ path = shutil.which(cmd)
30
+ if path:
31
+ print(f" \u2713 {name} - {path}")
32
+ found += 1
33
+ else:
34
+ print(f" \u2717 {name} - not found")
35
+ missing += 1
36
+ print()
37
+
38
+ # Check config
39
+ print("Checking config...")
40
+ config_path = get_config_path()
41
+ if config_path.exists():
42
+ print(f" \u2713 Config: {config_path}")
43
+ else:
44
+ print(f" - Config: {config_path} (not created yet, using defaults)")
45
+
46
+ sessions_dir = get_sessions_dir()
47
+ session_count = sum(1 for _ in sessions_dir.rglob("*.json"))
48
+ print(f" \u2713 Sessions: {sessions_dir} ({session_count} sessions)")
49
+ print()
50
+
51
+ # Summary
52
+ if missing == 0:
53
+ print("All good!")
54
+ elif found == 0:
55
+ print("No agents found. Install at least one of: claude, codex, gemini")
56
+ return 1
57
+ else:
58
+ print(f"Ready! ({missing} agent(s) not installed)")
59
+
60
+ return 0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hire-ai
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: CLI to orchestrate AI agents (Claude, Codex, Gemini)
5
- Project-URL: Homepage, https://github.com/nichiki/hire
6
- Project-URL: Repository, https://github.com/nichiki/hire
7
- Project-URL: Issues, https://github.com/nichiki/hire/issues
5
+ Project-URL: Homepage, https://github.com/nichiki/hire-ai
6
+ Project-URL: Repository, https://github.com/nichiki/hire-ai
7
+ Project-URL: Issues, https://github.com/nichiki/hire-ai/issues
8
8
  Author: nichiki
9
9
  License-Expression: MIT
10
10
  License-File: LICENSE
@@ -15,12 +15,13 @@ Classifier: Intended Audience :: Developers
15
15
  Classifier: License :: OSI Approved :: MIT License
16
16
  Classifier: Operating System :: OS Independent
17
17
  Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
18
19
  Classifier: Programming Language :: Python :: 3.11
19
20
  Classifier: Programming Language :: Python :: 3.12
20
21
  Classifier: Programming Language :: Python :: 3.13
21
22
  Classifier: Topic :: Software Development
22
23
  Classifier: Topic :: Utilities
23
- Requires-Python: >=3.11
24
+ Requires-Python: >=3.10
24
25
  Provides-Extra: dev
25
26
  Requires-Dist: build>=1.0; extra == 'dev'
26
27
  Requires-Dist: mypy>=1.10; extra == 'dev'
@@ -29,29 +30,37 @@ Requires-Dist: ruff>=0.4; extra == 'dev'
29
30
  Requires-Dist: twine>=5.0; extra == 'dev'
30
31
  Description-Content-Type: text/markdown
31
32
 
32
- # hire
33
+ # hire-ai
33
34
 
34
35
  CLI to orchestrate AI agents (Claude, Codex, Gemini).
35
36
 
37
+ > **⚠️ Warning**: By default, all agents run in **auto-approve mode**:
38
+ > - Claude Code: `--dangerously-skip-permissions`
39
+ > - Codex: `--full-auto`
40
+ > - Gemini CLI: `-y`
41
+ >
42
+ > This means agents can execute commands and modify files without confirmation.
43
+ > You can customize this in `~/.config/hire/config.json`.
44
+
36
45
  ## Installation
37
46
 
38
47
  ```bash
39
48
  # Using pipx (recommended)
40
- pipx install hire
49
+ pipx install hire-ai
41
50
 
42
51
  # Using pip
43
- pip install hire
52
+ pip install hire-ai
44
53
 
45
- # Using Homebrew
46
- brew install nichiki/tap/hire
54
+ # Using Homebrew (macOS)
55
+ brew install nichiki/tap/hire-ai
47
56
  ```
48
57
 
49
58
  ## Prerequisites
50
59
 
51
60
  You need at least one of the following CLI tools installed:
52
61
 
53
- - [Claude CLI](https://docs.anthropic.com/claude-code)
54
- - [Codex CLI](https://github.com/openai/codex)
62
+ - [Claude Code](https://claude.ai/claude-code)
63
+ - [Codex](https://github.com/openai/codex)
55
64
  - [Gemini CLI](https://github.com/google-gemini/gemini-cli)
56
65
 
57
66
  ## Usage
@@ -70,9 +79,24 @@ hire -s SESSION_ID "Follow up question"
70
79
  hire -n my-project codex "Start designing the architecture"
71
80
  hire -s my-project "What about the database schema?"
72
81
 
82
+ # Pipe input
83
+ cat main.py | hire codex "Review this code"
84
+ git diff | hire claude "Explain these changes"
85
+ echo "What is 2+2?" | hire gemini
86
+
87
+ # Attach files (using @filepath - agent feature)
88
+ hire claude "Review @src/main.py for security issues"
89
+ hire codex "Explain @package.json and @tsconfig.json"
90
+
73
91
  # Output as JSON
74
92
  hire gemini "Summarize this" --json
75
93
 
94
+ # Copy to clipboard
95
+ hire codex "Write a function" --clip
96
+
97
+ # Write to file
98
+ hire claude "Generate a README" -o README.md
99
+
76
100
  # Session management
77
101
  hire sessions # List all sessions
78
102
  hire sessions codex # List Codex sessions only
@@ -89,6 +113,8 @@ hire delete SESSION_ID # Delete a session
89
113
  | `-n, --name NAME` | Name the session |
90
114
  | `-m, --model MODEL` | Specify model to use |
91
115
  | `--json` | Output in JSON format |
116
+ | `--clip` | Copy output to clipboard |
117
+ | `-o, --out FILE` | Write output to file |
92
118
 
93
119
  ## Configuration
94
120
 
@@ -1,5 +1,6 @@
1
1
  hire/__init__.py,sha256=-V9lKavG7egEg5jYKkWGmwMTdpZzvGX_Tkl5sM9U6l0,90
2
- hire/cli.py,sha256=jGCt6rAKTy_Ydk4sorBAzJ6uSfuuJDeAEVgxyZ14pqg,4244
2
+ hire/cli.py,sha256=qpjV_vUdd_p7q9BYwRIlCOpM3F2pwT_G4hsszA8IE-Y,4811
3
+ hire/clipboard.py,sha256=rDhB2lYNT3pPeHmTHXVMs6R_yLy1xm9ErInxjBM9MHo,1266
3
4
  hire/config.py,sha256=s058dSnojmIdT1KRULWXchdMv6WM9aZ8CKgCqMGdVvA,1164
4
5
  hire/paths.py,sha256=PMLwEil1qRmW-9fJ-nTnvWZOUuJvMc-N54xrPp2NHfA,1372
5
6
  hire/session.py,sha256=Y-A2zCqiVshEOkBTNTAgaYu_FtCy1JBp4R91NMs5ffY,4874
@@ -8,13 +9,14 @@ hire/adapters/base.py,sha256=rubjoIXu7S8nIhBhksejR_Pb5YG4kwTZUj3dAMff_l4,1105
8
9
  hire/adapters/claude.py,sha256=tM7G25jGqinjK_7QjtF_N0E089V9p1dzNReXJnq0VO4,1929
9
10
  hire/adapters/codex.py,sha256=_4MXGTz-0YujuutDVCliaj9P94bm-Z1yNEElRtN3wzA,2895
10
11
  hire/adapters/gemini.py,sha256=Qm_e1zzhxu2ooXANfFvVCwzxteVatlbDnBZeuf-jHNw,2613
11
- hire/commands/__init__.py,sha256=sp3kdLPcKmqqgh_PyiOyDxJeHhnCHw0GIaoHWccgSCk,204
12
- hire/commands/ask.py,sha256=tsM3VK3i06zTQgb0iEAXX97_13b9bHVyX8Ju7dV369g,4490
12
+ hire/commands/__init__.py,sha256=aX9gG9U6_ygeSCy577EoX31SzY-VL9Db1DRl7hT_bGg,249
13
+ hire/commands/ask.py,sha256=EPLigSMQlKmiEfMc8elRqelaC1urpNCr9YjttHbeorM,5876
13
14
  hire/commands/delete.py,sha256=1fLZ64xmyIzR5s7i7lsNe-WornsD_qsOAe1cCdOodDU,991
15
+ hire/commands/doctor.py,sha256=QCPc1Eb_S8cNfs_xn0bw6UKj1OJTna_h1qTkueCN1Ms,1504
14
16
  hire/commands/sessions.py,sha256=aIsh8oC4N3KtMeUdIrCvU3hONMxAfV_Z4qnprBiP62k,1073
15
17
  hire/commands/show.py,sha256=gztFedJ553fvru0dPbHyperOMXDew-Yri3E3APu9d3c,860
16
- hire_ai-0.1.0.dist-info/METADATA,sha256=FCFDGQIV4XBQ7ZhLCHq9hlJ6HRP2nahzJw3ievV3y9s,3151
17
- hire_ai-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
18
- hire_ai-0.1.0.dist-info/entry_points.txt,sha256=oq7wq8Ju9C2Lw79ljxSkQIaeKOHA_aIN9JKQAaDbz_Q,39
19
- hire_ai-0.1.0.dist-info/licenses/LICENSE,sha256=Hj8NYmaOGk4xCcCYqD_5U91CVJwcJXEFSMWS965dUI0,1064
20
- hire_ai-0.1.0.dist-info/RECORD,,
18
+ hire_ai-0.1.2.dist-info/METADATA,sha256=St-0dAw-kuzhRygEaaJRlZ0RtU_sHqqJL39NV3-0mXc,4032
19
+ hire_ai-0.1.2.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
20
+ hire_ai-0.1.2.dist-info/entry_points.txt,sha256=oq7wq8Ju9C2Lw79ljxSkQIaeKOHA_aIN9JKQAaDbz_Q,39
21
+ hire_ai-0.1.2.dist-info/licenses/LICENSE,sha256=Hj8NYmaOGk4xCcCYqD_5U91CVJwcJXEFSMWS965dUI0,1064
22
+ hire_ai-0.1.2.dist-info/RECORD,,