agentproc 0.2.1__tar.gz → 0.4.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.2.1 → agentproc-0.4.0}/PKG-INFO +1 -1
- {agentproc-0.2.1 → agentproc-0.4.0}/pyproject.toml +1 -1
- {agentproc-0.2.1 → agentproc-0.4.0}/src/agentproc/__init__.py +27 -0
- {agentproc-0.2.1 → agentproc-0.4.0}/src/agentproc/cli.py +263 -32
- agentproc-0.4.0/src/agentproc/hub.py +505 -0
- {agentproc-0.2.1 → agentproc-0.4.0}/src/agentproc/runner.py +252 -23
- {agentproc-0.2.1 → agentproc-0.4.0}/src/agentproc.egg-info/PKG-INFO +1 -1
- {agentproc-0.2.1 → agentproc-0.4.0}/src/agentproc.egg-info/SOURCES.txt +3 -0
- agentproc-0.4.0/tests/test_conformance.py +33 -0
- agentproc-0.4.0/tests/test_hub.py +312 -0
- {agentproc-0.2.1 → agentproc-0.4.0}/setup.cfg +0 -0
- {agentproc-0.2.1 → agentproc-0.4.0}/src/agentproc.egg-info/dependency_links.txt +0 -0
- {agentproc-0.2.1 → agentproc-0.4.0}/src/agentproc.egg-info/entry_points.txt +0 -0
- {agentproc-0.2.1 → agentproc-0.4.0}/src/agentproc.egg-info/top_level.txt +0 -0
- {agentproc-0.2.1 → agentproc-0.4.0}/tests/test_agentproc.py +0 -0
- {agentproc-0.2.1 → agentproc-0.4.0}/tests/test_runner.py +0 -0
|
@@ -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
|
|
|
@@ -25,7 +25,20 @@ from .runner import (
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
def _read_pkg_version() -> str:
|
|
28
|
-
"""Read
|
|
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).
|
|
29
42
|
try:
|
|
30
43
|
toml_path = Path(__file__).resolve().parents[2] / "pyproject.toml"
|
|
31
44
|
if toml_path.exists():
|
|
@@ -214,6 +227,27 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
214
227
|
def show_help() -> None:
|
|
215
228
|
sys.stdout.write(f"""agentproc v{PKG_VERSION} (protocol {PROTOCOL_VERSION})
|
|
216
229
|
|
|
230
|
+
The fastest way in:
|
|
231
|
+
agentproc hub list # see what's available
|
|
232
|
+
agentproc hub run echo-agent -p "hello" # smoke test (no API key)
|
|
233
|
+
cd ~/projects/my-app && agentproc hub run claude-code -p "explain this"
|
|
234
|
+
|
|
235
|
+
The CLI fetches the profile from the GitHub hub on first use, caches it at
|
|
236
|
+
~/.agentproc/cache/hub/<name>/ (24h TTL), and uses your current directory as
|
|
237
|
+
the agent's cwd. Set GITHUB_TOKEN to raise the rate limit (see `agentproc hub --help`).
|
|
238
|
+
|
|
239
|
+
Hub subcommands:
|
|
240
|
+
hub list List all profiles in the hub
|
|
241
|
+
hub show <name> Show a profile's README
|
|
242
|
+
hub run <name> [run-options] Fetch (if needed) and run a profile
|
|
243
|
+
hub install <name> Copy a profile to the current directory
|
|
244
|
+
|
|
245
|
+
Run `agentproc hub --help` for the full hub reference.
|
|
246
|
+
|
|
247
|
+
───────────────────────────────────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
Advanced: run a local profile YAML directly (no hub fetch)
|
|
250
|
+
|
|
217
251
|
Usage:
|
|
218
252
|
agentproc --profile <path.yaml> --prompt "hello" [options]
|
|
219
253
|
|
|
@@ -227,7 +261,8 @@ Session:
|
|
|
227
261
|
--from <user> Sender identifier
|
|
228
262
|
|
|
229
263
|
Execution:
|
|
230
|
-
--cwd <path> Override profile.cwd
|
|
264
|
+
--cwd <path> Override profile.cwd (relative paths resolve
|
|
265
|
+
against the profile.yaml's directory)
|
|
231
266
|
--env KEY=VALUE Extra env var (repeatable)
|
|
232
267
|
--timeout <secs> Override profile.timeout_secs
|
|
233
268
|
--no-stream Set AGENT_STREAMING=0
|
|
@@ -250,9 +285,16 @@ Output semantics:
|
|
|
250
285
|
The final session id is printed on stderr as: agentproc:session:<id>
|
|
251
286
|
|
|
252
287
|
Examples:
|
|
253
|
-
|
|
254
|
-
agentproc
|
|
255
|
-
|
|
288
|
+
# Local profile (relative cwd resolves next to profile.yaml):
|
|
289
|
+
agentproc --profile ./hub/echo-agent/profile.yaml --prompt "hi"
|
|
290
|
+
|
|
291
|
+
# Local claude-code profile, claude runs against your project:
|
|
292
|
+
agentproc --profile ./hub/claude-code/profile.yaml \\
|
|
293
|
+
--prompt "explain this codebase" \\
|
|
294
|
+
--cwd /path/to/your/project
|
|
295
|
+
|
|
296
|
+
# Prompt from stdin:
|
|
297
|
+
cat prompt.txt | agentproc --profile prof.yaml --stdin
|
|
256
298
|
""")
|
|
257
299
|
|
|
258
300
|
|
|
@@ -261,56 +303,193 @@ def show_version() -> None:
|
|
|
261
303
|
|
|
262
304
|
|
|
263
305
|
# ---------------------------------------------------------------------------
|
|
264
|
-
#
|
|
306
|
+
# Hub subcommand dispatcher
|
|
265
307
|
# ---------------------------------------------------------------------------
|
|
266
308
|
|
|
267
|
-
def
|
|
268
|
-
|
|
269
|
-
|
|
309
|
+
def _run_hub_subcommand(args: List[str]) -> int:
|
|
310
|
+
"""Handle `agentproc hub <list|show|install|run> [args]`."""
|
|
311
|
+
from . import hub as hub_mod
|
|
312
|
+
|
|
313
|
+
if not args or args[0] in ("-h", "--help"):
|
|
314
|
+
_show_hub_help()
|
|
315
|
+
return 0
|
|
316
|
+
|
|
317
|
+
sub = args[0]
|
|
318
|
+
rest = args[1:]
|
|
270
319
|
|
|
271
|
-
|
|
272
|
-
|
|
320
|
+
# Any subcommand with --help/-h shows the hub help uniformly.
|
|
321
|
+
if "--help" in rest or "-h" in rest:
|
|
322
|
+
_show_hub_help()
|
|
323
|
+
return 0
|
|
324
|
+
|
|
325
|
+
# Common flag
|
|
326
|
+
refresh = "--refresh" in rest
|
|
327
|
+
|
|
328
|
+
# Separate hub-level flags from runner-level flags and positional args.
|
|
329
|
+
# In the `hub run` context, `-p` means `--prompt` (the profile name is
|
|
330
|
+
# positional, not a path), so normalize it before handing off to argparse.
|
|
331
|
+
positional: List[str] = []
|
|
332
|
+
runner_args: List[str] = []
|
|
333
|
+
takes_value = {"--prompt", "-p", "--session", "--session-name", "--from",
|
|
334
|
+
"--cwd", "--env", "--timeout"}
|
|
335
|
+
boolean_flags = {"--no-stream", "--verbose", "--quiet", "--raw", "--stdin"}
|
|
336
|
+
i = 0
|
|
337
|
+
while i < len(rest):
|
|
338
|
+
a = rest[i]
|
|
339
|
+
if a in ("--refresh", "-h", "--help"):
|
|
340
|
+
i += 1
|
|
341
|
+
continue
|
|
342
|
+
if a in takes_value:
|
|
343
|
+
runner_args.append("--prompt" if a == "-p" else a)
|
|
344
|
+
if i + 1 < len(rest):
|
|
345
|
+
runner_args.append(rest[i + 1])
|
|
346
|
+
i += 2
|
|
347
|
+
else:
|
|
348
|
+
i += 1
|
|
349
|
+
continue
|
|
350
|
+
if a in boolean_flags:
|
|
351
|
+
runner_args.append(a)
|
|
352
|
+
i += 1
|
|
353
|
+
continue
|
|
354
|
+
if a.startswith("-"):
|
|
355
|
+
sys.stderr.write(f"error: unknown option: {a}\n\n")
|
|
356
|
+
_show_hub_help()
|
|
357
|
+
return 2
|
|
358
|
+
positional.append(a)
|
|
359
|
+
i += 1
|
|
273
360
|
|
|
274
|
-
|
|
275
|
-
|
|
361
|
+
def _log(msg: str) -> None:
|
|
362
|
+
sys.stderr.write(msg + "\n")
|
|
363
|
+
|
|
364
|
+
if sub == "list":
|
|
365
|
+
profiles = hub_mod.list_profiles(on_log=_log)
|
|
366
|
+
sys.stdout.write("Available profiles in the official hub:\n\n")
|
|
367
|
+
for p in profiles:
|
|
368
|
+
sys.stdout.write(
|
|
369
|
+
f" {p['name']:<15} {p['tested']:<12} {p['description'][:60]}\n"
|
|
370
|
+
)
|
|
371
|
+
sys.stdout.write('\nRun `agentproc hub run <name> -p "hi"` to use one.\n')
|
|
276
372
|
return 0
|
|
277
|
-
|
|
278
|
-
|
|
373
|
+
|
|
374
|
+
if sub == "show":
|
|
375
|
+
if not positional:
|
|
376
|
+
sys.stderr.write("error: hub show requires a profile name\n")
|
|
377
|
+
return 2
|
|
378
|
+
readme = hub_mod.show_readme(positional[0], refresh=refresh, on_log=_log)
|
|
379
|
+
sys.stdout.write(readme)
|
|
380
|
+
if not readme.endswith("\n"):
|
|
381
|
+
sys.stdout.write("\n")
|
|
382
|
+
return 0
|
|
383
|
+
|
|
384
|
+
if sub == "install":
|
|
385
|
+
if not positional:
|
|
386
|
+
sys.stderr.write("error: hub install requires a profile name\n")
|
|
387
|
+
return 2
|
|
388
|
+
dest = hub_mod.install_profile(positional[0], Path.cwd(), refresh=refresh, on_log=_log)
|
|
389
|
+
# Tell the user exactly what they got and how to run it next.
|
|
390
|
+
rel_profile = os.path.relpath(str(dest / "profile.yaml"), str(Path.cwd()))
|
|
391
|
+
sys.stderr.write("\n")
|
|
392
|
+
sys.stderr.write(f"Next: edit {rel_profile} if you want, then run:\n")
|
|
393
|
+
sys.stderr.write(
|
|
394
|
+
f' agentproc --profile {rel_profile} --prompt "hi" --cwd <your-project>\n'
|
|
395
|
+
)
|
|
279
396
|
return 0
|
|
280
397
|
|
|
281
|
-
if
|
|
282
|
-
|
|
283
|
-
|
|
398
|
+
if sub == "run":
|
|
399
|
+
if not positional:
|
|
400
|
+
sys.stderr.write("error: hub run requires a profile name\n")
|
|
401
|
+
return 2
|
|
402
|
+
profile_name = positional[0]
|
|
403
|
+
cache_dir = hub_mod.fetch_profile(profile_name, refresh=refresh, on_log=_log)
|
|
404
|
+
profile_path = str(cache_dir / "profile.yaml")
|
|
405
|
+
|
|
406
|
+
# Parse the runner-level flags we separated out above.
|
|
407
|
+
parser = build_parser()
|
|
408
|
+
opts = parser.parse_args(runner_args)
|
|
409
|
+
|
|
410
|
+
if not opts.prompt and not opts.stdin:
|
|
411
|
+
sys.stderr.write("error: hub run requires --prompt <text> or --stdin\n")
|
|
412
|
+
return 2
|
|
413
|
+
|
|
414
|
+
# hub run uses the user's current directory as the agent's cwd when
|
|
415
|
+
# --cwd is not given. Matches the hub docs and the right default for
|
|
416
|
+
# AI-CLI profiles where the agent should operate on the user's project.
|
|
417
|
+
if not opts.cwd:
|
|
418
|
+
opts.cwd = str(Path.cwd())
|
|
419
|
+
|
|
420
|
+
return _run_agent_with_profile(profile_path, opts)
|
|
421
|
+
|
|
422
|
+
sys.stderr.write(f"error: unknown hub subcommand: {sub}\n\n")
|
|
423
|
+
_show_hub_help()
|
|
424
|
+
return 2
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def _show_hub_help() -> None:
|
|
428
|
+
sys.stdout.write("""agentproc hub — manage profiles from the official Hub
|
|
429
|
+
|
|
430
|
+
Usage:
|
|
431
|
+
agentproc hub list List all profiles in the hub
|
|
432
|
+
agentproc hub show <name> Show a profile's README
|
|
433
|
+
agentproc hub install <name> Copy a profile to the current directory
|
|
434
|
+
agentproc hub run <name> [run-options] Fetch (if needed) and run a profile
|
|
435
|
+
|
|
436
|
+
Hub run options (same as the regular --profile runner):
|
|
437
|
+
-p, --prompt <text> User message (or use --stdin)
|
|
438
|
+
--cwd <path> Override profile.cwd (default: current dir)
|
|
439
|
+
--env KEY=VALUE Extra env var (repeatable)
|
|
440
|
+
--session <id> Previous session id for multi-turn
|
|
441
|
+
--timeout <secs> Override profile.timeout_secs
|
|
442
|
+
--no-stream Disable streaming
|
|
443
|
+
--verbose / --quiet Protocol line visibility (default: verbose)
|
|
444
|
+
--stdin Read prompt from stdin
|
|
445
|
+
|
|
446
|
+
Common options:
|
|
447
|
+
--refresh Force re-fetch from GitHub (ignore cache)
|
|
448
|
+
-h, --help Show this help
|
|
449
|
+
|
|
450
|
+
Examples:
|
|
451
|
+
agentproc hub list
|
|
452
|
+
agentproc hub run echo-agent -p "hello"
|
|
453
|
+
cd ~/projects/my-app && agentproc hub run claude-code -p "explain this" --env ANTHROPIC_API_KEY=$KEY
|
|
454
|
+
agentproc hub show codex
|
|
455
|
+
agentproc hub install agy
|
|
456
|
+
|
|
457
|
+
Profiles are cached at ~/.agentproc/cache/hub/<name>/ (24h TTL).
|
|
458
|
+
""")
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _run_agent_with_profile(profile_path: str, opts) -> int:
|
|
462
|
+
"""Shared runner logic for both --profile path and hub run path."""
|
|
463
|
+
profile_dir = str(Path(profile_path).resolve().parent)
|
|
464
|
+
try:
|
|
465
|
+
yaml_text = Path(profile_path).resolve().read_text(encoding="utf-8")
|
|
466
|
+
profile_raw = parse_yaml(yaml_text)
|
|
467
|
+
except FileNotFoundError:
|
|
468
|
+
sys.stderr.write(f"error: profile not found: {profile_path}\n")
|
|
469
|
+
return 2
|
|
470
|
+
except Exception as e:
|
|
471
|
+
sys.stderr.write(f"error: failed to parse profile {profile_path}: {e}\n")
|
|
284
472
|
return 2
|
|
285
473
|
|
|
474
|
+
# Read prompt.
|
|
286
475
|
prompt = opts.prompt
|
|
287
476
|
if opts.stdin:
|
|
288
477
|
try:
|
|
289
478
|
prompt = sys.stdin.read().rstrip("\n")
|
|
290
479
|
except KeyboardInterrupt:
|
|
291
|
-
return
|
|
480
|
+
return 1
|
|
292
481
|
if prompt is None:
|
|
293
482
|
sys.stderr.write("error: --prompt (or --stdin) is required\n")
|
|
294
483
|
return 2
|
|
295
484
|
|
|
296
485
|
extra_env: Dict[str, str] = {}
|
|
297
|
-
for kv in opts.env:
|
|
486
|
+
for kv in opts.env or []:
|
|
298
487
|
eq = kv.find("=")
|
|
299
488
|
if eq < 0:
|
|
300
489
|
sys.stderr.write(f"error: --env expects KEY=VALUE, got: {kv}\n")
|
|
301
490
|
return 2
|
|
302
491
|
extra_env[kv[:eq]] = kv[eq + 1:]
|
|
303
492
|
|
|
304
|
-
try:
|
|
305
|
-
yaml_text = Path(opts.profile).resolve().read_text(encoding="utf-8")
|
|
306
|
-
profile_raw = parse_yaml(yaml_text)
|
|
307
|
-
except FileNotFoundError:
|
|
308
|
-
sys.stderr.write(f"error: profile not found: {opts.profile}\n")
|
|
309
|
-
return 2
|
|
310
|
-
except Exception as e:
|
|
311
|
-
sys.stderr.write(f"error: failed to parse profile {opts.profile}: {e}\n")
|
|
312
|
-
return 2
|
|
313
|
-
|
|
314
493
|
streaming = False if opts.no_stream else None
|
|
315
494
|
|
|
316
495
|
if opts.raw:
|
|
@@ -323,6 +502,7 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
323
502
|
from_user=opts.from_user,
|
|
324
503
|
streaming=streaming,
|
|
325
504
|
cwd=opts.cwd,
|
|
505
|
+
profile_dir=profile_dir,
|
|
326
506
|
extra_env=extra_env,
|
|
327
507
|
timeout_secs=opts.timeout,
|
|
328
508
|
),
|
|
@@ -343,6 +523,7 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
343
523
|
from_user=opts.from_user,
|
|
344
524
|
streaming=streaming,
|
|
345
525
|
cwd=opts.cwd,
|
|
526
|
+
profile_dir=profile_dir,
|
|
346
527
|
extra_env=extra_env,
|
|
347
528
|
timeout_secs=opts.timeout,
|
|
348
529
|
on_partial=lambda t: verbose and sys.stderr.write(
|
|
@@ -360,14 +541,64 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|
|
360
541
|
sys.stdout.write(r.reply)
|
|
361
542
|
if not r.reply.endswith("\n"):
|
|
362
543
|
sys.stdout.write("\n")
|
|
363
|
-
|
|
364
544
|
if r.session_id:
|
|
365
545
|
sys.stderr.write(f"agentproc:session:{r.session_id}\n")
|
|
366
546
|
if r.error:
|
|
367
547
|
sys.stderr.write(f"agentproc:error:{r.error}\n")
|
|
368
|
-
|
|
369
548
|
return 0 if r.exit_code == 0 else 1
|
|
370
549
|
|
|
371
550
|
|
|
551
|
+
# ---------------------------------------------------------------------------
|
|
552
|
+
# Main
|
|
553
|
+
# ---------------------------------------------------------------------------
|
|
554
|
+
|
|
555
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
556
|
+
if argv is None:
|
|
557
|
+
argv = sys.argv[1:]
|
|
558
|
+
|
|
559
|
+
try:
|
|
560
|
+
# `agentproc hub <subcommand>` — defer to hub dispatcher.
|
|
561
|
+
if argv and argv[0] == "hub":
|
|
562
|
+
return _run_hub_subcommand(argv[1:])
|
|
563
|
+
|
|
564
|
+
parser = build_parser()
|
|
565
|
+
opts = parser.parse_args(argv)
|
|
566
|
+
|
|
567
|
+
if opts.help:
|
|
568
|
+
show_help()
|
|
569
|
+
return 0
|
|
570
|
+
if opts.version:
|
|
571
|
+
show_version()
|
|
572
|
+
return 0
|
|
573
|
+
|
|
574
|
+
if not opts.profile:
|
|
575
|
+
sys.stderr.write("error: --profile is required\n\n")
|
|
576
|
+
show_help()
|
|
577
|
+
return 2
|
|
578
|
+
|
|
579
|
+
return _run_agent_with_profile(opts.profile, opts)
|
|
580
|
+
except KeyboardInterrupt:
|
|
581
|
+
return 130
|
|
582
|
+
except Exception as e:
|
|
583
|
+
# Friendly handling for known hub errors: print the message + hint,
|
|
584
|
+
# never a raw stack trace.
|
|
585
|
+
from .hub import HubError
|
|
586
|
+
if isinstance(e, HubError):
|
|
587
|
+
sys.stderr.write(f"error: {e}\n")
|
|
588
|
+
if e.hint:
|
|
589
|
+
sys.stderr.write(f"\n{e.hint}\n")
|
|
590
|
+
return 1
|
|
591
|
+
# Network errors that slipped through unwrapped.
|
|
592
|
+
msg = str(e)
|
|
593
|
+
if any(s in msg for s in ("fetch failed", "ENOTFOUND", "ECONNREFUSED", "ECONNRESET", "urlopen error")):
|
|
594
|
+
sys.stderr.write(f"error: network error talking to GitHub: {msg}\n")
|
|
595
|
+
sys.stderr.write("\nThis is usually transient. Re-run the command, or run against a local checkout:\n")
|
|
596
|
+
sys.stderr.write(' agentproc --profile ./hub/<name>/profile.yaml --prompt "hi"\n')
|
|
597
|
+
return 1
|
|
598
|
+
# Everything else: surface the message without dumping a traceback.
|
|
599
|
+
sys.stderr.write(f"error: {msg}\n")
|
|
600
|
+
return 1
|
|
601
|
+
|
|
602
|
+
|
|
372
603
|
if __name__ == "__main__":
|
|
373
604
|
sys.exit(main())
|