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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agentproc
3
- Version: 0.2.1
3
+ Version: 0.4.0
4
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "agentproc"
7
- version = "0.2.1"
7
+ version = "0.4.0"
8
8
  description = "AgentProc Protocol SDK + CLI — connect any Agent CLI to a messaging platform"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -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 version from pyproject.toml at runtime (zero-dep)."""
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
- agentproc --profile hub/echo-agent/profile.yaml --prompt "hi"
254
- agentproc -p hub/claude-code/profile.yaml --prompt "hello" --verbose
255
- cat prompt.txt | agentproc -p prof.yaml --stdin
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
- # Main
306
+ # Hub subcommand dispatcher
265
307
  # ---------------------------------------------------------------------------
266
308
 
267
- def main(argv: Optional[List[str]] = None) -> int:
268
- if argv is None:
269
- argv = sys.argv[1:]
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
- parser = build_parser()
272
- opts = parser.parse_args(argv)
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
- if opts.help:
275
- show_help()
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
- if opts.version:
278
- show_version()
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 not opts.profile:
282
- sys.stderr.write("error: --profile is required\n\n")
283
- show_help()
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 EXIT_ERROR
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())