agentproc 0.1.1__tar.gz → 0.3.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.
- {agentproc-0.1.1 → agentproc-0.3.0}/PKG-INFO +3 -3
- {agentproc-0.1.1 → agentproc-0.3.0}/pyproject.toml +6 -3
- {agentproc-0.1.1 → agentproc-0.3.0}/src/agentproc/__init__.py +27 -0
- agentproc-0.3.0/src/agentproc/cli.py +504 -0
- agentproc-0.3.0/src/agentproc/hub.py +294 -0
- agentproc-0.3.0/src/agentproc/runner.py +381 -0
- {agentproc-0.1.1 → agentproc-0.3.0}/src/agentproc.egg-info/PKG-INFO +3 -3
- {agentproc-0.1.1 → agentproc-0.3.0}/src/agentproc.egg-info/SOURCES.txt +7 -1
- agentproc-0.3.0/src/agentproc.egg-info/entry_points.txt +2 -0
- agentproc-0.3.0/tests/test_hub.py +299 -0
- agentproc-0.3.0/tests/test_runner.py +374 -0
- {agentproc-0.1.1 → agentproc-0.3.0}/setup.cfg +0 -0
- {agentproc-0.1.1 → agentproc-0.3.0}/src/agentproc.egg-info/dependency_links.txt +0 -0
- {agentproc-0.1.1 → agentproc-0.3.0}/src/agentproc.egg-info/top_level.txt +0 -0
- {agentproc-0.1.1 → agentproc-0.3.0}/tests/test_agentproc.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentproc
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: AgentProc SDK — connect any Agent CLI to a messaging platform
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: AgentProc Protocol SDK + CLI — connect any Agent CLI to a messaging platform
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/jeffkit/agentproc
|
|
7
|
-
Project-URL: Documentation, https://
|
|
7
|
+
Project-URL: Documentation, https://agentproc.dev/
|
|
8
8
|
Project-URL: Repository, https://github.com/jeffkit/agentproc
|
|
9
9
|
Project-URL: Issues, https://github.com/jeffkit/agentproc/issues
|
|
10
10
|
Requires-Python: >=3.9
|
|
@@ -4,16 +4,19 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "agentproc"
|
|
7
|
-
version = "0.
|
|
8
|
-
description = "AgentProc SDK — connect any Agent CLI to a messaging platform
|
|
7
|
+
version = "0.3.0"
|
|
8
|
+
description = "AgentProc Protocol SDK + CLI — connect any Agent CLI to a messaging platform"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
11
11
|
requires-python = ">=3.9"
|
|
12
12
|
dependencies = []
|
|
13
13
|
|
|
14
|
+
[project.scripts]
|
|
15
|
+
agentproc = "agentproc.cli:main"
|
|
16
|
+
|
|
14
17
|
[project.urls]
|
|
15
18
|
Homepage = "https://github.com/jeffkit/agentproc"
|
|
16
|
-
Documentation = "https://
|
|
19
|
+
Documentation = "https://agentproc.dev/"
|
|
17
20
|
Repository = "https://github.com/jeffkit/agentproc"
|
|
18
21
|
Issues = "https://github.com/jeffkit/agentproc/issues"
|
|
19
22
|
|
|
@@ -47,8 +47,35 @@ __all__ = [
|
|
|
47
47
|
"load_history",
|
|
48
48
|
"append_history",
|
|
49
49
|
"session_file_path",
|
|
50
|
+
"__version__",
|
|
50
51
|
]
|
|
51
52
|
|
|
53
|
+
|
|
54
|
+
def _read_version() -> str:
|
|
55
|
+
try:
|
|
56
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
57
|
+
try:
|
|
58
|
+
return version("agentproc")
|
|
59
|
+
except PackageNotFoundError:
|
|
60
|
+
pass
|
|
61
|
+
except ImportError:
|
|
62
|
+
pass
|
|
63
|
+
try:
|
|
64
|
+
from pathlib import Path
|
|
65
|
+
toml_path = Path(__file__).resolve().parents[2] / "pyproject.toml"
|
|
66
|
+
if toml_path.exists():
|
|
67
|
+
import re
|
|
68
|
+
text = toml_path.read_text(encoding="utf-8")
|
|
69
|
+
m = re.search(r'^version\s*=\s*"([^"]+)"', text, re.MULTILINE)
|
|
70
|
+
if m:
|
|
71
|
+
return m.group(1)
|
|
72
|
+
except Exception:
|
|
73
|
+
pass
|
|
74
|
+
return "0.0.0+unknown"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
__version__ = _read_version()
|
|
78
|
+
|
|
52
79
|
PROTOCOL_VERSION = "0.1"
|
|
53
80
|
|
|
54
81
|
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
"""agentproc CLI — run any AgentProc profile against a message.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
agentproc --profile <path.yaml> --prompt "hello" [options]
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
16
|
+
|
|
17
|
+
from .runner import (
|
|
18
|
+
PROTOCOL_VERSION,
|
|
19
|
+
EXIT_ERROR,
|
|
20
|
+
EXIT_SUCCESS,
|
|
21
|
+
EXIT_TIMEOUT,
|
|
22
|
+
RunOptions,
|
|
23
|
+
run,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _read_pkg_version() -> str:
|
|
28
|
+
"""Read installed package version.
|
|
29
|
+
|
|
30
|
+
Primary: importlib.metadata (works after pip/pipx install).
|
|
31
|
+
Fallback: parse pyproject.toml (works in source checkout, dev mode).
|
|
32
|
+
"""
|
|
33
|
+
try:
|
|
34
|
+
from importlib.metadata import version, PackageNotFoundError
|
|
35
|
+
try:
|
|
36
|
+
return version("agentproc")
|
|
37
|
+
except PackageNotFoundError:
|
|
38
|
+
pass
|
|
39
|
+
except ImportError:
|
|
40
|
+
pass
|
|
41
|
+
# Fallback for source checkout (development, not installed).
|
|
42
|
+
try:
|
|
43
|
+
toml_path = Path(__file__).resolve().parents[2] / "pyproject.toml"
|
|
44
|
+
if toml_path.exists():
|
|
45
|
+
text = toml_path.read_text(encoding="utf-8")
|
|
46
|
+
m = re.search(r'^version\s*=\s*"([^"]+)"', text, re.MULTILINE)
|
|
47
|
+
if m:
|
|
48
|
+
return m.group(1)
|
|
49
|
+
except Exception:
|
|
50
|
+
pass
|
|
51
|
+
return "0.0.0+unknown"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
PKG_VERSION = _read_pkg_version()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# ---------------------------------------------------------------------------
|
|
58
|
+
# Minimal YAML parser (zero-dep; covers hub profile subset)
|
|
59
|
+
# ---------------------------------------------------------------------------
|
|
60
|
+
|
|
61
|
+
def parse_yaml(text: str) -> Dict[str, Any]:
|
|
62
|
+
"""Parse a YAML subset into a Python dict.
|
|
63
|
+
|
|
64
|
+
Supports: nested maps, block scalars (|), inline flow sequences ([a, b]),
|
|
65
|
+
block sequences (- item), quoted strings, booleans, null, ints, floats.
|
|
66
|
+
"""
|
|
67
|
+
text_stripped = text.strip()
|
|
68
|
+
if text_stripped.startswith("{") or text_stripped.startswith("["):
|
|
69
|
+
try:
|
|
70
|
+
return json.loads(text)
|
|
71
|
+
except Exception:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
lines = text.splitlines()
|
|
75
|
+
root: Dict[str, Any] = {}
|
|
76
|
+
stack: List[Tuple[int, Any]] = [(-1, root)]
|
|
77
|
+
|
|
78
|
+
def current(min_indent: int) -> Tuple[int, Any]:
|
|
79
|
+
while len(stack) > 1 and stack[-1][0] >= min_indent:
|
|
80
|
+
stack.pop()
|
|
81
|
+
return stack[-1]
|
|
82
|
+
|
|
83
|
+
i = 0
|
|
84
|
+
while i < len(lines):
|
|
85
|
+
raw = lines[i]
|
|
86
|
+
if raw.strip() == "" or raw.strip().startswith("#"):
|
|
87
|
+
i += 1
|
|
88
|
+
continue
|
|
89
|
+
indent = _leading_spaces(raw)
|
|
90
|
+
content = raw[indent:].rstrip("\r")
|
|
91
|
+
_, container = current(indent)
|
|
92
|
+
|
|
93
|
+
if content == "-" or content.startswith("- "):
|
|
94
|
+
if isinstance(container, list):
|
|
95
|
+
rest = "" if content == "-" else content[2:]
|
|
96
|
+
if rest.strip():
|
|
97
|
+
container.append(_strip_scalar(rest))
|
|
98
|
+
i += 1
|
|
99
|
+
continue
|
|
100
|
+
|
|
101
|
+
m = re.match(r"^([A-Za-z_][A-Za-z0-9_-]*)\s*:\s*(.*)$", content)
|
|
102
|
+
if not m:
|
|
103
|
+
i += 1
|
|
104
|
+
continue
|
|
105
|
+
key, val = m.group(1), m.group(2)
|
|
106
|
+
|
|
107
|
+
if val == "":
|
|
108
|
+
j = i + 1
|
|
109
|
+
while j < len(lines) and (lines[j].strip() == "" or lines[j].strip().startswith("#")):
|
|
110
|
+
j += 1
|
|
111
|
+
if j < len(lines):
|
|
112
|
+
next_indent = _leading_spaces(lines[j])
|
|
113
|
+
next_content = lines[j][next_indent:]
|
|
114
|
+
if next_indent > indent and (next_content == "-" or next_content.startswith("- ")):
|
|
115
|
+
arr: List[Any] = []
|
|
116
|
+
if isinstance(container, dict):
|
|
117
|
+
container[key] = arr
|
|
118
|
+
stack.append((indent, arr))
|
|
119
|
+
i += 1
|
|
120
|
+
continue
|
|
121
|
+
elif next_indent > indent:
|
|
122
|
+
child: Dict[str, Any] = {}
|
|
123
|
+
if isinstance(container, dict):
|
|
124
|
+
container[key] = child
|
|
125
|
+
stack.append((indent, child))
|
|
126
|
+
i += 1
|
|
127
|
+
continue
|
|
128
|
+
if isinstance(container, dict):
|
|
129
|
+
container[key] = ""
|
|
130
|
+
i += 1
|
|
131
|
+
continue
|
|
132
|
+
|
|
133
|
+
if val in ("|", "|-", ">"):
|
|
134
|
+
block_lines: List[str] = []
|
|
135
|
+
j = i + 1
|
|
136
|
+
while j < len(lines):
|
|
137
|
+
nr = lines[j]
|
|
138
|
+
if nr.strip() == "":
|
|
139
|
+
block_lines.append("")
|
|
140
|
+
j += 1
|
|
141
|
+
continue
|
|
142
|
+
ni = _leading_spaces(nr)
|
|
143
|
+
if ni <= indent:
|
|
144
|
+
break
|
|
145
|
+
block_lines.append(nr[min(indent + 2, len(nr)):])
|
|
146
|
+
j += 1
|
|
147
|
+
joined = "\n".join(block_lines)
|
|
148
|
+
if val == "|":
|
|
149
|
+
value = re.sub(r"\n*$", "\n", joined)
|
|
150
|
+
else:
|
|
151
|
+
value = re.sub(r"\n*$", "", joined)
|
|
152
|
+
if isinstance(container, dict):
|
|
153
|
+
container[key] = value
|
|
154
|
+
i = j
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
if isinstance(container, dict):
|
|
158
|
+
container[key] = _strip_scalar(val)
|
|
159
|
+
i += 1
|
|
160
|
+
|
|
161
|
+
return root
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _leading_spaces(s: str) -> int:
|
|
165
|
+
n = 0
|
|
166
|
+
for ch in s:
|
|
167
|
+
if ch == " ":
|
|
168
|
+
n += 1
|
|
169
|
+
elif ch == "\t":
|
|
170
|
+
n += 2
|
|
171
|
+
else:
|
|
172
|
+
break
|
|
173
|
+
return n
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _strip_scalar(v: str) -> Any:
|
|
177
|
+
v = v.strip()
|
|
178
|
+
if (v.startswith('"') and v.endswith('"')) or (v.startswith("'") and v.endswith("'")):
|
|
179
|
+
return v[1:-1]
|
|
180
|
+
if v.startswith("[") and v.endswith("]"):
|
|
181
|
+
inner = v[1:-1].strip()
|
|
182
|
+
if not inner:
|
|
183
|
+
return []
|
|
184
|
+
return [_strip_scalar(s.strip()) for s in inner.split(",")]
|
|
185
|
+
lv = v.lower()
|
|
186
|
+
if lv == "true":
|
|
187
|
+
return True
|
|
188
|
+
if lv == "false":
|
|
189
|
+
return False
|
|
190
|
+
if lv in ("null", "~", ""):
|
|
191
|
+
return None
|
|
192
|
+
if re.match(r"^[+-]?\d+$", v):
|
|
193
|
+
return int(v)
|
|
194
|
+
if re.match(r"^[+-]?\d+\.\d+$", v):
|
|
195
|
+
return float(v)
|
|
196
|
+
return v
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# ---------------------------------------------------------------------------
|
|
200
|
+
# Argparse setup
|
|
201
|
+
# ---------------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
204
|
+
p = argparse.ArgumentParser(
|
|
205
|
+
prog="agentproc",
|
|
206
|
+
description="Run an AgentProc profile against a message.",
|
|
207
|
+
add_help=False,
|
|
208
|
+
)
|
|
209
|
+
p.add_argument("-h", "--help", action="store_true")
|
|
210
|
+
p.add_argument("--version", action="store_true")
|
|
211
|
+
p.add_argument("-p", "--profile")
|
|
212
|
+
p.add_argument("--prompt")
|
|
213
|
+
p.add_argument("--session", default="")
|
|
214
|
+
p.add_argument("--session-name", default="default")
|
|
215
|
+
p.add_argument("--from", dest="from_user", default="")
|
|
216
|
+
p.add_argument("--cwd")
|
|
217
|
+
p.add_argument("--env", action="append", default=[])
|
|
218
|
+
p.add_argument("--timeout", type=int)
|
|
219
|
+
p.add_argument("--no-stream", action="store_true")
|
|
220
|
+
p.add_argument("--verbose", action="store_true")
|
|
221
|
+
p.add_argument("--quiet", action="store_true")
|
|
222
|
+
p.add_argument("--raw", action="store_true")
|
|
223
|
+
p.add_argument("--stdin", action="store_true")
|
|
224
|
+
return p
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def show_help() -> None:
|
|
228
|
+
sys.stdout.write(f"""agentproc v{PKG_VERSION} (protocol {PROTOCOL_VERSION})
|
|
229
|
+
|
|
230
|
+
Usage:
|
|
231
|
+
agentproc --profile <path.yaml> --prompt "hello" [options]
|
|
232
|
+
|
|
233
|
+
Required:
|
|
234
|
+
--profile, -p <path> Profile YAML path
|
|
235
|
+
--prompt <text> User message (or use --stdin)
|
|
236
|
+
|
|
237
|
+
Session:
|
|
238
|
+
--session <id> Previous session id (multi-turn)
|
|
239
|
+
--session-name <name> Human-readable session name (default: "default")
|
|
240
|
+
--from <user> Sender identifier
|
|
241
|
+
|
|
242
|
+
Execution:
|
|
243
|
+
--cwd <path> Override profile.cwd
|
|
244
|
+
--env KEY=VALUE Extra env var (repeatable)
|
|
245
|
+
--timeout <secs> Override profile.timeout_secs
|
|
246
|
+
--no-stream Set AGENT_STREAMING=0
|
|
247
|
+
|
|
248
|
+
Output:
|
|
249
|
+
--verbose Forward protocol lines to stderr (default)
|
|
250
|
+
--quiet Suppress protocol lines on stderr
|
|
251
|
+
--raw Don't parse stdout; forward agent output verbatim
|
|
252
|
+
--stdin Read prompt from stdin instead of --prompt
|
|
253
|
+
|
|
254
|
+
Other:
|
|
255
|
+
--version Print version and exit
|
|
256
|
+
--help, -h Show this help
|
|
257
|
+
|
|
258
|
+
Output semantics:
|
|
259
|
+
stderr → protocol lines (AGENT_PARTIAL:, AGENT_SESSION:, AGENT_ERROR:)
|
|
260
|
+
stdout → final reply body (non-protocol lines)
|
|
261
|
+
exit → 0 success · 1 error · 124 timeout (per spec)
|
|
262
|
+
|
|
263
|
+
The final session id is printed on stderr as: agentproc:session:<id>
|
|
264
|
+
|
|
265
|
+
Examples:
|
|
266
|
+
agentproc --profile hub/echo-agent/profile.yaml --prompt "hi"
|
|
267
|
+
agentproc -p hub/claude-code/profile.yaml --prompt "hello" --verbose
|
|
268
|
+
cat prompt.txt | agentproc -p prof.yaml --stdin
|
|
269
|
+
""")
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def show_version() -> None:
|
|
273
|
+
sys.stdout.write(f"agentproc {PKG_VERSION} (protocol {PROTOCOL_VERSION})\n")
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
# ---------------------------------------------------------------------------
|
|
277
|
+
# Hub subcommand dispatcher
|
|
278
|
+
# ---------------------------------------------------------------------------
|
|
279
|
+
|
|
280
|
+
def _run_hub_subcommand(args: List[str]) -> int:
|
|
281
|
+
"""Handle `agentproc hub <list|show|install|run> [args]`."""
|
|
282
|
+
from . import hub as hub_mod
|
|
283
|
+
|
|
284
|
+
if not args or args[0] in ("-h", "--help"):
|
|
285
|
+
_show_hub_help()
|
|
286
|
+
return 0
|
|
287
|
+
|
|
288
|
+
sub = args[0]
|
|
289
|
+
rest = args[1:]
|
|
290
|
+
|
|
291
|
+
# Common flag
|
|
292
|
+
refresh = "--refresh" in rest
|
|
293
|
+
positional = [a for a in rest if not a.startswith("--")]
|
|
294
|
+
|
|
295
|
+
def _log(msg: str) -> None:
|
|
296
|
+
sys.stderr.write(msg + "\n")
|
|
297
|
+
|
|
298
|
+
if sub == "list":
|
|
299
|
+
profiles = hub_mod.list_profiles(on_log=_log)
|
|
300
|
+
sys.stdout.write("Available profiles in the official hub:\n\n")
|
|
301
|
+
for p in profiles:
|
|
302
|
+
sys.stdout.write(
|
|
303
|
+
f" {p['name']:<15} {p['tested']:<12} {p['description'][:60]}\n"
|
|
304
|
+
)
|
|
305
|
+
sys.stdout.write('\nRun `agentproc hub run <name> -p "hi"` to use one.\n')
|
|
306
|
+
return 0
|
|
307
|
+
|
|
308
|
+
if sub == "show":
|
|
309
|
+
if not positional:
|
|
310
|
+
sys.stderr.write("error: hub show requires a profile name\n")
|
|
311
|
+
return 2
|
|
312
|
+
readme = hub_mod.show_readme(positional[0], refresh=refresh, on_log=_log)
|
|
313
|
+
sys.stdout.write(readme)
|
|
314
|
+
if not readme.endswith("\n"):
|
|
315
|
+
sys.stdout.write("\n")
|
|
316
|
+
return 0
|
|
317
|
+
|
|
318
|
+
if sub == "install":
|
|
319
|
+
if not positional:
|
|
320
|
+
sys.stderr.write("error: hub install requires a profile name\n")
|
|
321
|
+
return 2
|
|
322
|
+
hub_mod.install_profile(positional[0], Path.cwd(), refresh=refresh, on_log=_log)
|
|
323
|
+
return 0
|
|
324
|
+
|
|
325
|
+
if sub == "run":
|
|
326
|
+
if not positional:
|
|
327
|
+
sys.stderr.write("error: hub run requires a profile name\n")
|
|
328
|
+
return 2
|
|
329
|
+
profile_name = positional[0]
|
|
330
|
+
cache_dir = hub_mod.fetch_profile(profile_name, refresh=refresh, on_log=_log)
|
|
331
|
+
profile_path = str(cache_dir / "profile.yaml")
|
|
332
|
+
|
|
333
|
+
# Re-parse remaining args (excluding the profile name) as runner options.
|
|
334
|
+
parser = build_parser()
|
|
335
|
+
# Drop the first positional (profile name) and --refresh from rest.
|
|
336
|
+
runner_args = [a for i, a in enumerate(rest) if not (
|
|
337
|
+
(not a.startswith("--") and i == 0) or a == "--refresh"
|
|
338
|
+
)]
|
|
339
|
+
opts = parser.parse_args(runner_args)
|
|
340
|
+
|
|
341
|
+
if not opts.prompt and not opts.stdin:
|
|
342
|
+
sys.stderr.write("error: hub run requires --prompt <text> or --stdin\n")
|
|
343
|
+
return 2
|
|
344
|
+
|
|
345
|
+
return _run_agent_with_profile(profile_path, opts)
|
|
346
|
+
|
|
347
|
+
sys.stderr.write(f"error: unknown hub subcommand: {sub}\n\n")
|
|
348
|
+
_show_hub_help()
|
|
349
|
+
return 2
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _show_hub_help() -> None:
|
|
353
|
+
sys.stdout.write("""agentproc hub — manage profiles from the official Hub
|
|
354
|
+
|
|
355
|
+
Usage:
|
|
356
|
+
agentproc hub list List all profiles in the hub
|
|
357
|
+
agentproc hub show <name> Show a profile's README
|
|
358
|
+
agentproc hub install <name> Copy a profile to the current directory
|
|
359
|
+
agentproc hub run <name> [run-options] Fetch (if needed) and run a profile
|
|
360
|
+
|
|
361
|
+
Hub run options (same as the regular --profile runner):
|
|
362
|
+
-p, --prompt <text> User message (or use --stdin)
|
|
363
|
+
--cwd <path> Override profile.cwd (default: current dir)
|
|
364
|
+
--env KEY=VALUE Extra env var (repeatable)
|
|
365
|
+
--session <id> Previous session id for multi-turn
|
|
366
|
+
--timeout <secs> Override profile.timeout_secs
|
|
367
|
+
--no-stream Disable streaming
|
|
368
|
+
--verbose / --quiet Protocol line visibility (default: verbose)
|
|
369
|
+
--stdin Read prompt from stdin
|
|
370
|
+
|
|
371
|
+
Common options:
|
|
372
|
+
--refresh Force re-fetch from GitHub (ignore cache)
|
|
373
|
+
-h, --help Show this help
|
|
374
|
+
|
|
375
|
+
Examples:
|
|
376
|
+
agentproc hub list
|
|
377
|
+
agentproc hub run echo-agent -p "hello"
|
|
378
|
+
cd ~/projects/my-app && agentproc hub run claude-code -p "explain this" --env ANTHROPIC_API_KEY=$KEY
|
|
379
|
+
agentproc hub show codex
|
|
380
|
+
agentproc hub install agy
|
|
381
|
+
|
|
382
|
+
Profiles are cached at ~/.agentproc/cache/hub/<name>/ (24h TTL).
|
|
383
|
+
""")
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def _run_agent_with_profile(profile_path: str, opts) -> int:
|
|
387
|
+
"""Shared runner logic for both --profile path and hub run path."""
|
|
388
|
+
try:
|
|
389
|
+
yaml_text = Path(profile_path).resolve().read_text(encoding="utf-8")
|
|
390
|
+
profile_raw = parse_yaml(yaml_text)
|
|
391
|
+
except FileNotFoundError:
|
|
392
|
+
sys.stderr.write(f"error: profile not found: {profile_path}\n")
|
|
393
|
+
return 2
|
|
394
|
+
except Exception as e:
|
|
395
|
+
sys.stderr.write(f"error: failed to parse profile {profile_path}: {e}\n")
|
|
396
|
+
return 2
|
|
397
|
+
|
|
398
|
+
# Read prompt.
|
|
399
|
+
prompt = opts.prompt
|
|
400
|
+
if opts.stdin:
|
|
401
|
+
try:
|
|
402
|
+
prompt = sys.stdin.read().rstrip("\n")
|
|
403
|
+
except KeyboardInterrupt:
|
|
404
|
+
return 1
|
|
405
|
+
if prompt is None:
|
|
406
|
+
sys.stderr.write("error: --prompt (or --stdin) is required\n")
|
|
407
|
+
return 2
|
|
408
|
+
|
|
409
|
+
extra_env: Dict[str, str] = {}
|
|
410
|
+
for kv in opts.env or []:
|
|
411
|
+
eq = kv.find("=")
|
|
412
|
+
if eq < 0:
|
|
413
|
+
sys.stderr.write(f"error: --env expects KEY=VALUE, got: {kv}\n")
|
|
414
|
+
return 2
|
|
415
|
+
extra_env[kv[:eq]] = kv[eq + 1:]
|
|
416
|
+
|
|
417
|
+
streaming = False if opts.no_stream else None
|
|
418
|
+
|
|
419
|
+
if opts.raw:
|
|
420
|
+
r = run(
|
|
421
|
+
profile_raw,
|
|
422
|
+
RunOptions(
|
|
423
|
+
message=prompt,
|
|
424
|
+
session_id=opts.session,
|
|
425
|
+
session_name=opts.session_name,
|
|
426
|
+
from_user=opts.from_user,
|
|
427
|
+
streaming=streaming,
|
|
428
|
+
cwd=opts.cwd,
|
|
429
|
+
extra_env=extra_env,
|
|
430
|
+
timeout_secs=opts.timeout,
|
|
431
|
+
),
|
|
432
|
+
)
|
|
433
|
+
sys.stdout.write(r.reply)
|
|
434
|
+
if r.reply and not r.reply.endswith("\n"):
|
|
435
|
+
sys.stdout.write("\n")
|
|
436
|
+
return 0 if r.exit_code == 0 else 1
|
|
437
|
+
|
|
438
|
+
verbose = opts.verbose or not opts.quiet
|
|
439
|
+
|
|
440
|
+
r = run(
|
|
441
|
+
profile_raw,
|
|
442
|
+
RunOptions(
|
|
443
|
+
message=prompt,
|
|
444
|
+
session_id=opts.session,
|
|
445
|
+
session_name=opts.session_name,
|
|
446
|
+
from_user=opts.from_user,
|
|
447
|
+
streaming=streaming,
|
|
448
|
+
cwd=opts.cwd,
|
|
449
|
+
extra_env=extra_env,
|
|
450
|
+
timeout_secs=opts.timeout,
|
|
451
|
+
on_partial=lambda t: verbose and sys.stderr.write(
|
|
452
|
+
f"AGENT_PARTIAL:{json.dumps(t, ensure_ascii=False)}\n"
|
|
453
|
+
),
|
|
454
|
+
on_session=lambda sid: verbose and sys.stderr.write(f"AGENT_SESSION:{sid}\n"),
|
|
455
|
+
on_error=lambda msg: verbose and sys.stderr.write(
|
|
456
|
+
f"AGENT_ERROR:{json.dumps(msg, ensure_ascii=False)}\n"
|
|
457
|
+
),
|
|
458
|
+
on_stderr=lambda line: verbose and sys.stderr.write(f"[agent stderr] {line}\n"),
|
|
459
|
+
),
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
if r.reply:
|
|
463
|
+
sys.stdout.write(r.reply)
|
|
464
|
+
if not r.reply.endswith("\n"):
|
|
465
|
+
sys.stdout.write("\n")
|
|
466
|
+
if r.session_id:
|
|
467
|
+
sys.stderr.write(f"agentproc:session:{r.session_id}\n")
|
|
468
|
+
if r.error:
|
|
469
|
+
sys.stderr.write(f"agentproc:error:{r.error}\n")
|
|
470
|
+
return 0 if r.exit_code == 0 else 1
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
# ---------------------------------------------------------------------------
|
|
474
|
+
# Main
|
|
475
|
+
# ---------------------------------------------------------------------------
|
|
476
|
+
|
|
477
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
478
|
+
if argv is None:
|
|
479
|
+
argv = sys.argv[1:]
|
|
480
|
+
|
|
481
|
+
# `agentproc hub <subcommand>` — defer to hub dispatcher.
|
|
482
|
+
if argv and argv[0] == "hub":
|
|
483
|
+
return _run_hub_subcommand(argv[1:])
|
|
484
|
+
|
|
485
|
+
parser = build_parser()
|
|
486
|
+
opts = parser.parse_args(argv)
|
|
487
|
+
|
|
488
|
+
if opts.help:
|
|
489
|
+
show_help()
|
|
490
|
+
return 0
|
|
491
|
+
if opts.version:
|
|
492
|
+
show_version()
|
|
493
|
+
return 0
|
|
494
|
+
|
|
495
|
+
if not opts.profile:
|
|
496
|
+
sys.stderr.write("error: --profile is required\n\n")
|
|
497
|
+
show_help()
|
|
498
|
+
return 2
|
|
499
|
+
|
|
500
|
+
return _run_agent_with_profile(opts.profile, opts)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
if __name__ == "__main__":
|
|
504
|
+
sys.exit(main())
|