authyouragent 0.3.2__tar.gz → 0.3.4__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: authyouragent
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Auth Your Agent SDK: let AI agents act for a user on websites, with the user's approval on their phone.
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://authyouragent.com
@@ -40,7 +40,9 @@ authyouragent vault up --agent-id ag_xxxxx --key /path/to/agent-key.pem
40
40
 
41
41
  `vault up` starts the browser vault: the browser your agent uses, in a
42
42
  container on your machine. The agent drives it through the vault and never
43
- gets its cookies. It prints the `env` block for your MCP client. See
43
+ gets its cookies. It prints the `env` block for your MCP client. If you skip
44
+ it, the MCP server starts the vault itself the first time the agent needs the
45
+ browser (`AYA_VAULT_AUTOSTART=0` turns that off). See
44
46
  [vault/README.md](https://github.com/kjames2001/authyouragent/blob/main/vault/README.md)
45
47
  for what protects what, and the known limits.
46
48
 
@@ -50,8 +52,9 @@ For just the SDK (no browser, no MCP): `pip install authyouragent`.
50
52
 
51
53
  The `authyouragent-mcp` command starts an MCP server with eleven tools:
52
54
 
53
- - **Browser:** `navigate`, `click`, `type_text`, `read_page`. Clicks that
54
- submit, send, delete, pay or publish wait for your approval on your phone.
55
+ - **Browser:** `navigate`, `click`, `type_text`, `read_page`. Only public
56
+ websites open. Clicks that submit a form, or whose button says create, send,
57
+ save, delete, pay and the like, wait for your approval on your phone.
55
58
  - **`check_login_wall`**: is the page asking for a password, a code or a
56
59
  sign-in approval?
57
60
  - **`request_takeover`** / **`wait_for_takeover`**: you take over the browser
@@ -22,7 +22,9 @@ authyouragent vault up --agent-id ag_xxxxx --key /path/to/agent-key.pem
22
22
 
23
23
  `vault up` starts the browser vault: the browser your agent uses, in a
24
24
  container on your machine. The agent drives it through the vault and never
25
- gets its cookies. It prints the `env` block for your MCP client. See
25
+ gets its cookies. It prints the `env` block for your MCP client. If you skip
26
+ it, the MCP server starts the vault itself the first time the agent needs the
27
+ browser (`AYA_VAULT_AUTOSTART=0` turns that off). See
26
28
  [vault/README.md](https://github.com/kjames2001/authyouragent/blob/main/vault/README.md)
27
29
  for what protects what, and the known limits.
28
30
 
@@ -32,8 +34,9 @@ For just the SDK (no browser, no MCP): `pip install authyouragent`.
32
34
 
33
35
  The `authyouragent-mcp` command starts an MCP server with eleven tools:
34
36
 
35
- - **Browser:** `navigate`, `click`, `type_text`, `read_page`. Clicks that
36
- submit, send, delete, pay or publish wait for your approval on your phone.
37
+ - **Browser:** `navigate`, `click`, `type_text`, `read_page`. Only public
38
+ websites open. Clicks that submit a form, or whose button says create, send,
39
+ save, delete, pay and the like, wait for your approval on your phone.
37
40
  - **`check_login_wall`**: is the page asking for a password, a code or a
38
41
  sign-in approval?
39
42
  - **`request_takeover`** / **`wait_for_takeover`**: you take over the browser
@@ -30,5 +30,5 @@ from .site import SiteVerifier, AuthError, Auth
30
30
 
31
31
  __all__ = ["AgentClient", "AgentError", "keygen", "agent_jwk",
32
32
  "SiteVerifier", "AuthError", "Auth"]
33
- __version__ = "0.3.2"
33
+ __version__ = "0.3.4"
34
34
  DEFAULT_CLOUD = "https://authyouragent.com"
@@ -17,6 +17,8 @@ Environment:
17
17
  AYA_CLOUD default https://authyouragent.com
18
18
  AYA_VAULT_URL the vault broker (default http://127.0.0.1:7801)
19
19
  AYA_VAULT_TOKEN_FILE file holding the vault's bearer token (or AYA_VAULT_TOKEN)
20
+ AYA_VAULT_AUTOSTART default 1: start the local vault (Docker) on first use if
21
+ it is not running. Set 0 to manage it with `authyouragent vault`.
20
22
 
21
23
  Tools: navigate, click, type_text, read_page, check_login_wall, request_takeover,
22
24
  wait_for_takeover, request_approval, end_session, check_agent_status,
@@ -30,6 +32,8 @@ import os
30
32
  import re
31
33
  import sys
32
34
  import time
35
+ from pathlib import Path
36
+ from urllib.parse import urlparse
33
37
 
34
38
  import httpx
35
39
 
@@ -44,11 +48,13 @@ DEFAULT_CLOUD = "https://authyouragent.com"
44
48
  WAIT_DEFAULT = 240 # stay under common MCP client call timeouts (300s)
45
49
  WAIT_MAX = 290
46
50
  HEARTBEAT_S = 20
51
+ AUTOSTART_WAIT = 120 # the first start downloads the image (~500 MB)
47
52
 
48
53
  mcp = FastMCP("authyouragent")
49
54
  _agent = None
50
55
  _vault = None
51
56
  _hb = None
57
+ _starting = None # the background task starting the vault, if any
52
58
 
53
59
 
54
60
  def _log(*a):
@@ -67,6 +73,46 @@ def _get_agent():
67
73
  return _agent
68
74
 
69
75
 
76
+ class NoVaultToken(RuntimeError):
77
+ pass
78
+
79
+
80
+ def _autostart_ok():
81
+ """Start the vault ourselves only when it is the local one `vault up` would
82
+ create: default address, and the token file it writes (or none set)."""
83
+ if os.environ.get("AYA_VAULT_AUTOSTART", "1").strip().lower() in ("0", "false", "no", "off"):
84
+ return False
85
+ if os.environ.get("AYA_VAULT_TOKEN"):
86
+ return False
87
+ u = urlparse(os.environ.get("AYA_VAULT_URL", "http://127.0.0.1:7801"))
88
+ if u.hostname not in ("127.0.0.1", "localhost") or u.port != 7801:
89
+ return False
90
+ from . import vault_cli
91
+ tf = os.environ.get("AYA_VAULT_TOKEN_FILE")
92
+ return not tf or Path(tf).expanduser().resolve() == (vault_cli.HOME / "token").resolve()
93
+
94
+
95
+ async def _ensure_vault():
96
+ """Start the local vault in the background and wait for it, up to
97
+ AUTOSTART_WAIT. If it is still starting, say so; the next call waits again."""
98
+ global _starting, _vault
99
+ from . import vault_cli
100
+ if _starting is None or (_starting.done() and (_starting.cancelled() or _starting.exception())):
101
+ _log("the browser vault is not running: starting it")
102
+ _starting = asyncio.create_task(asyncio.to_thread(
103
+ vault_cli.start, os.environ.get("AYA_AGENT_ID"), os.environ.get("AYA_KEY_FILE"),
104
+ cloud=os.environ.get("AYA_CLOUD", DEFAULT_CLOUD), log=_log))
105
+ done, _ = await asyncio.wait({_starting}, timeout=AUTOSTART_WAIT)
106
+ if not done:
107
+ raise RuntimeError("the browser vault is starting (the first start downloads about 500 MB). "
108
+ "Call the tool again in a minute.")
109
+ if _starting.exception():
110
+ raise RuntimeError(f"could not start the browser vault: {_starting.exception()}")
111
+ if _vault is not None: # the token may be new: connect again
112
+ await _vault.aclose()
113
+ _vault = None
114
+
115
+
70
116
  def _vault_client():
71
117
  global _vault
72
118
  if _vault is None:
@@ -79,7 +125,7 @@ def _vault_client():
79
125
  if os.path.exists(default):
80
126
  token = open(default).read().strip()
81
127
  if not token:
82
- raise RuntimeError("no vault token: run `authyouragent vault up`, or set AYA_VAULT_TOKEN_FILE")
128
+ raise NoVaultToken("no vault token: run `authyouragent vault up`, or set AYA_VAULT_TOKEN_FILE")
83
129
  _vault = httpx.AsyncClient(base_url=os.environ.get("AYA_VAULT_URL", "http://127.0.0.1:7801"),
84
130
  headers={"Authorization": f"Bearer {token}"},
85
131
  timeout=httpx.Timeout(60, read=WAIT_MAX + 30))
@@ -101,10 +147,21 @@ async def _call(method, path, **kw):
101
147
  global _hb
102
148
  if _hb is None or _hb.done():
103
149
  _hb = asyncio.create_task(_heartbeat())
104
- try:
105
- r = await _vault_client().request(method, path, **kw)
106
- except httpx.HTTPError as e:
107
- raise RuntimeError(f"cannot reach the browser vault ({type(e).__name__}); is it running?")
150
+ if _starting is not None and not _starting.done():
151
+ await _ensure_vault() # a start is under way: wait for it, never race it
152
+ for attempt in (1, 2):
153
+ try:
154
+ r = await _vault_client().request(method, path, **kw)
155
+ break
156
+ except (httpx.ConnectError, NoVaultToken) as e:
157
+ if attempt == 2 or not _autostart_ok():
158
+ if isinstance(e, NoVaultToken):
159
+ raise
160
+ raise RuntimeError("cannot reach the browser vault (not running). "
161
+ "Start it with `authyouragent vault up`.")
162
+ await _ensure_vault()
163
+ except httpx.HTTPError as e:
164
+ raise RuntimeError(f"cannot reach the browser vault ({type(e).__name__}); is it running?")
108
165
  data = r.json()
109
166
  if r.status_code == 409:
110
167
  raise RuntimeError(data.get("detail", "busy"))
@@ -34,9 +34,12 @@ PORT = 7801
34
34
  VAULT_UID = 10001
35
35
 
36
36
 
37
+ class VaultError(RuntimeError):
38
+ """Starting or reaching the vault failed; the message says why."""
39
+
40
+
37
41
  def _die(msg) -> NoReturn:
38
- print(f"authyouragent vault: {msg}", file=sys.stderr)
39
- sys.exit(1)
42
+ raise VaultError(msg)
40
43
 
41
44
 
42
45
  def _docker(*args, check=True, capture=True):
@@ -118,37 +121,57 @@ def _wait_ready(token, seconds=60):
118
121
  return False
119
122
 
120
123
 
121
- def up(a):
122
- agent_id = a.agent_id or os.environ.get("AYA_AGENT_ID")
123
- key = a.key or os.environ.get("AYA_KEY_FILE")
124
+ def start(agent_id, key, cloud="https://authyouragent.com", image=IMAGE, gvisor="auto",
125
+ size="412x860", scale="2", pull=True, log=None):
126
+ """Start the vault, or wait for it if its container is already running.
127
+ Returns "started" or "running". Raises VaultError. Used by `vault up` and
128
+ by the MCP server when the vault is needed and not running; `log` gets
129
+ progress lines (the MCP server sends them to stderr, never stdout)."""
130
+ log = log or (lambda *a: None)
124
131
  if not agent_id or not key:
125
132
  _die("need --agent-id and --key (or AYA_AGENT_ID / AYA_KEY_FILE)")
126
133
  if _running():
127
- print(f"already running ({NAME}). `authyouragent vault down` first to restart.")
128
- return
134
+ # e.g. Docker is restarting it after a reboot: give it time to come up
135
+ if not _wait_ready(_token()):
136
+ _die(f"the vault container is running but not answering; see `docker logs {NAME}`")
137
+ return "running"
129
138
  _docker("rm", "-f", NAME, check=False)
130
139
  token = _token()
131
- gvisor = {"auto": _has_gvisor(), "on": True, "off": False}[a.gvisor]
132
- if a.gvisor == "on" and not _has_gvisor():
140
+ use_gvisor = {"auto": _has_gvisor(), "on": True, "off": False}[gvisor]
141
+ if gvisor == "on" and not _has_gvisor():
133
142
  _die("--gvisor on, but Docker has no 'runsc' runtime. See https://gvisor.dev/docs/user_guide/install/")
134
- if not a.no_pull:
135
- print(f"pulling {a.image} ...")
136
- _docker("pull", "-q", a.image)
143
+ if pull:
144
+ log(f"pulling {image} ...")
145
+ r = _docker("pull", "-q", image, check=False)
146
+ if r.returncode != 0:
147
+ if _docker("image", "inspect", image, check=False).returncode != 0:
148
+ _die(f"docker pull failed: {(r.stderr or r.stdout).strip()[:400]}")
149
+ log("pull failed; using the copy already on this machine")
137
150
  args = ["run", "-d", "--name", NAME, "--restart", "unless-stopped",
138
151
  "--shm-size=1g", "--stop-timeout", "120",
139
152
  "--security-opt", f"seccomp={_seccomp()}",
140
153
  "--security-opt", "no-new-privileges", "--cap-drop", "ALL",
141
154
  "-e", f"VAULT_TOKEN={token}", "-e", f"AYA_AGENT_ID={agent_id}",
142
- "-e", f"AYA_CLOUD={a.cloud}", "-e", f"VAULT_SIZE={a.size}", "-e", f"VAULT_SCALE={a.scale}",
155
+ "-e", f"AYA_CLOUD={cloud}", "-e", f"VAULT_SIZE={size}", "-e", f"VAULT_SCALE={scale}",
143
156
  "-v", f"{_key_copy(key)}:/run/secrets/agent.pem:ro",
144
157
  "-v", f"{_state_dir()}:/var/lib/vault",
145
158
  "-p", f"127.0.0.1:{PORT}:{PORT}"]
146
- if gvisor:
159
+ if use_gvisor:
147
160
  args += ["--runtime", "runsc"]
148
- _docker(*args, a.image)
161
+ _docker(*args, image)
149
162
  if not _wait_ready(token):
150
163
  _die(f"started but not answering; see `docker logs {NAME}`")
151
- print(f"vault running on 127.0.0.1:{PORT} (browser sandbox: on, gVisor: {'on' if gvisor else 'off'})")
164
+ log(f"vault running on 127.0.0.1:{PORT} (browser sandbox: on, gVisor: {'on' if use_gvisor else 'off'})")
165
+ return "started"
166
+
167
+
168
+ def up(a):
169
+ if _running():
170
+ print(f"already running ({NAME}). `authyouragent vault down` first to restart.")
171
+ return
172
+ start(a.agent_id or os.environ.get("AYA_AGENT_ID"), a.key or os.environ.get("AYA_KEY_FILE"),
173
+ cloud=a.cloud, image=a.image, gvisor=a.gvisor, size=a.size, scale=a.scale,
174
+ pull=not a.no_pull, log=print)
152
175
  print("MCP server env:")
153
176
  env(a, quiet=True)
154
177
 
@@ -210,5 +233,8 @@ def main(argv=None):
210
233
  a = ap.parse_args(argv)
211
234
  try:
212
235
  a.fn(a)
236
+ except VaultError as e:
237
+ print(f"authyouragent vault: {e}", file=sys.stderr)
238
+ sys.exit(1)
213
239
  except BrokenPipeError: # output piped into e.g. `head`
214
240
  pass
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: authyouragent
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: Auth Your Agent SDK: let AI agents act for a user on websites, with the user's approval on their phone.
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://authyouragent.com
@@ -40,7 +40,9 @@ authyouragent vault up --agent-id ag_xxxxx --key /path/to/agent-key.pem
40
40
 
41
41
  `vault up` starts the browser vault: the browser your agent uses, in a
42
42
  container on your machine. The agent drives it through the vault and never
43
- gets its cookies. It prints the `env` block for your MCP client. See
43
+ gets its cookies. It prints the `env` block for your MCP client. If you skip
44
+ it, the MCP server starts the vault itself the first time the agent needs the
45
+ browser (`AYA_VAULT_AUTOSTART=0` turns that off). See
44
46
  [vault/README.md](https://github.com/kjames2001/authyouragent/blob/main/vault/README.md)
45
47
  for what protects what, and the known limits.
46
48
 
@@ -50,8 +52,9 @@ For just the SDK (no browser, no MCP): `pip install authyouragent`.
50
52
 
51
53
  The `authyouragent-mcp` command starts an MCP server with eleven tools:
52
54
 
53
- - **Browser:** `navigate`, `click`, `type_text`, `read_page`. Clicks that
54
- submit, send, delete, pay or publish wait for your approval on your phone.
55
+ - **Browser:** `navigate`, `click`, `type_text`, `read_page`. Only public
56
+ websites open. Clicks that submit a form, or whose button says create, send,
57
+ save, delete, pay and the like, wait for your approval on your phone.
55
58
  - **`check_login_wall`**: is the page asking for a password, a code or a
56
59
  sign-in approval?
57
60
  - **`request_takeover`** / **`wait_for_takeover`**: you take over the browser
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "authyouragent"
7
- version = "0.3.2"
7
+ version = "0.3.4"
8
8
  description = "Auth Your Agent SDK: let AI agents act for a user on websites, with the user's approval on their phone."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
File without changes