getbased-agent-stack 0.5.3__tar.gz → 0.5.5__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.
- {getbased_agent_stack-0.5.3/src/getbased_agent_stack.egg-info → getbased_agent_stack-0.5.5}/PKG-INFO +2 -2
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/README.md +1 -1
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/pyproject.toml +1 -1
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/src/getbased_agent_stack/__init__.py +1 -1
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/src/getbased_agent_stack/cli.py +158 -0
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/src/getbased_agent_stack/mcp_configs.py +6 -3
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/src/getbased_agent_stack/units.py +16 -1
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5/src/getbased_agent_stack.egg-info}/PKG-INFO +2 -2
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/tests/test_cli.py +93 -1
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/tests/test_units.py +3 -1
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/LICENSE +0 -0
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/setup.cfg +0 -0
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/src/getbased_agent_stack/env_file.py +0 -0
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/src/getbased_agent_stack/systemd/getbased-dashboard.service +0 -0
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/src/getbased_agent_stack/systemd/getbased-rag.service +0 -0
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/src/getbased_agent_stack.egg-info/SOURCES.txt +0 -0
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/src/getbased_agent_stack.egg-info/dependency_links.txt +0 -0
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/src/getbased_agent_stack.egg-info/entry_points.txt +0 -0
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/src/getbased_agent_stack.egg-info/requires.txt +0 -0
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/src/getbased_agent_stack.egg-info/top_level.txt +0 -0
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/tests/test_env_file.py +0 -0
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/tests/test_integration.py +0 -0
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/tests/test_mcp_configs.py +0 -0
- {getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/tests/test_systemd_units.py +0 -0
{getbased_agent_stack-0.5.3/src/getbased_agent_stack.egg-info → getbased_agent_stack-0.5.5}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: getbased-agent-stack
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.5
|
|
4
4
|
Summary: One-command install of the full getbased agent stack — getbased-mcp + getbased-rag + getbased-dashboard
|
|
5
5
|
License-Expression: AGPL-3.0-or-later
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -79,7 +79,7 @@ getbased-stack init --yes
|
|
|
79
79
|
|
|
80
80
|
The wizard (~30 seconds):
|
|
81
81
|
|
|
82
|
-
1. Prompts for your `GETBASED_TOKEN` and `GETBASED_AGENT_CONTEXT_KEY` (skip if you don't use Agent Access; `--yes`
|
|
82
|
+
1. Prompts for your `GETBASED_TOKEN` and `GETBASED_AGENT_CONTEXT_KEY` when you opt into Agent Access (skip if you don't use Agent Access; `--yes` skips prompts)
|
|
83
83
|
2. Generates a rag API key if one doesn't exist
|
|
84
84
|
3. Writes `~/.config/getbased/env` (mode 0600) — the shared config file
|
|
85
85
|
4. Installs systemd user units for rag + dashboard, enables them, starts them
|
|
@@ -60,7 +60,7 @@ getbased-stack init --yes
|
|
|
60
60
|
|
|
61
61
|
The wizard (~30 seconds):
|
|
62
62
|
|
|
63
|
-
1. Prompts for your `GETBASED_TOKEN` and `GETBASED_AGENT_CONTEXT_KEY` (skip if you don't use Agent Access; `--yes`
|
|
63
|
+
1. Prompts for your `GETBASED_TOKEN` and `GETBASED_AGENT_CONTEXT_KEY` when you opt into Agent Access (skip if you don't use Agent Access; `--yes` skips prompts)
|
|
64
64
|
2. Generates a rag API key if one doesn't exist
|
|
65
65
|
3. Writes `~/.config/getbased/env` (mode 0600) — the shared config file
|
|
66
66
|
4. Installs systemd user units for rag + dashboard, enables them, starts them
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "getbased-agent-stack"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.5"
|
|
8
8
|
description = "One-command install of the full getbased agent stack — getbased-mcp + getbased-rag + getbased-dashboard"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "AGPL-3.0-or-later"
|
|
@@ -16,9 +16,12 @@ python-dotenv. The env file format is simple enough that we own it.
|
|
|
16
16
|
from __future__ import annotations
|
|
17
17
|
|
|
18
18
|
import argparse
|
|
19
|
+
import base64
|
|
20
|
+
import json
|
|
19
21
|
import os
|
|
20
22
|
import secrets
|
|
21
23
|
import shutil
|
|
24
|
+
import subprocess
|
|
22
25
|
import sys
|
|
23
26
|
from pathlib import Path
|
|
24
27
|
|
|
@@ -289,6 +292,143 @@ def cmd_mcp_config(args: argparse.Namespace) -> int:
|
|
|
289
292
|
return 0
|
|
290
293
|
|
|
291
294
|
|
|
295
|
+
def _decode_setup_payload(setup: str) -> "dict[str, str]":
|
|
296
|
+
prefix = "gbsetup_v1_"
|
|
297
|
+
if not setup.startswith(prefix):
|
|
298
|
+
raise ValueError("setup code must start with gbsetup_v1_")
|
|
299
|
+
raw = setup[len(prefix):].strip()
|
|
300
|
+
if not raw:
|
|
301
|
+
raise ValueError("empty setup payload")
|
|
302
|
+
padded = raw + "=" * (-len(raw) % 4)
|
|
303
|
+
try:
|
|
304
|
+
data = json.loads(base64.urlsafe_b64decode(padded.encode("ascii")).decode("utf-8"))
|
|
305
|
+
except Exception as exc:
|
|
306
|
+
raise ValueError("setup payload is not valid base64url JSON") from exc
|
|
307
|
+
if not isinstance(data, dict) or data.get("version") != 1:
|
|
308
|
+
raise ValueError("unsupported setup payload version")
|
|
309
|
+
token = str(data.get("token") or "").strip()
|
|
310
|
+
context_key = str(data.get("contextKey") or data.get("context_key") or "").strip()
|
|
311
|
+
gateway = str(data.get("gateway") or "https://sync.getbased.health").strip()
|
|
312
|
+
if not token:
|
|
313
|
+
raise ValueError("setup payload is missing token")
|
|
314
|
+
if not context_key:
|
|
315
|
+
raise ValueError("setup payload is missing contextKey")
|
|
316
|
+
if not gateway.startswith(("https://", "http://")):
|
|
317
|
+
raise ValueError("gateway must be an http(s) URL")
|
|
318
|
+
return {"token": token, "context_key": context_key, "gateway": gateway}
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def _run_checked(cmd: "list[str]", label: str, *, required: bool = True, input_text: str | None = None) -> bool:
|
|
322
|
+
try:
|
|
323
|
+
res = subprocess.run(cmd, text=True, input=input_text, capture_output=True, check=False)
|
|
324
|
+
except FileNotFoundError:
|
|
325
|
+
if required:
|
|
326
|
+
print(f"{label} failed: command not found: {cmd[0]}", file=sys.stderr)
|
|
327
|
+
else:
|
|
328
|
+
print(f" skipped {label}: command not found: {cmd[0]}")
|
|
329
|
+
return False
|
|
330
|
+
if res.returncode == 0:
|
|
331
|
+
return True
|
|
332
|
+
msg = (res.stderr or res.stdout or "").strip()
|
|
333
|
+
if required:
|
|
334
|
+
print(f"{label} failed ({res.returncode})" + (f": {msg}" if msg else ""), file=sys.stderr)
|
|
335
|
+
else:
|
|
336
|
+
print(f" skipped {label}: command exited {res.returncode}" + (f" ({msg})" if msg else ""))
|
|
337
|
+
return False
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _process_tree_contains_hermes() -> bool:
|
|
341
|
+
"""True when this command is running inside a Hermes-managed child process.
|
|
342
|
+
|
|
343
|
+
Restarting Hermes from a command spawned by the running gateway can SIGTERM
|
|
344
|
+
the setup process before it reports success. A normal SSH/shell terminal on
|
|
345
|
+
the same host will not have `hermes` in its parent process tree, so the
|
|
346
|
+
one-paste setup still performs the restart there.
|
|
347
|
+
"""
|
|
348
|
+
if os.environ.get("GETBASED_STACK_ASSUME_HERMES_CHILD") == "1":
|
|
349
|
+
return True
|
|
350
|
+
if os.environ.get("GETBASED_STACK_ASSUME_HERMES_CHILD") == "0":
|
|
351
|
+
return False
|
|
352
|
+
pid = os.getppid()
|
|
353
|
+
seen: set[int] = set()
|
|
354
|
+
while pid > 1 and pid not in seen:
|
|
355
|
+
seen.add(pid)
|
|
356
|
+
proc = Path("/proc") / str(pid)
|
|
357
|
+
try:
|
|
358
|
+
comm = (proc / "comm").read_text(errors="ignore").strip().lower()
|
|
359
|
+
cmdline = (proc / "cmdline").read_text(errors="ignore").replace("\x00", " ").lower()
|
|
360
|
+
if comm == "hermes" or "hermes" in cmdline:
|
|
361
|
+
return True
|
|
362
|
+
stat = (proc / "stat").read_text(errors="ignore")
|
|
363
|
+
# /proc/<pid>/stat format: pid (comm) state ppid ...; comm may contain
|
|
364
|
+
# spaces, so split after the final ')'.
|
|
365
|
+
rest = stat.rsplit(")", 1)[1].strip().split()
|
|
366
|
+
pid = int(rest[1]) if len(rest) > 1 else 1
|
|
367
|
+
except Exception:
|
|
368
|
+
return False
|
|
369
|
+
return False
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _maybe_restart_hermes_gateway(hermes: str, *, no_restart: bool) -> None:
|
|
373
|
+
if no_restart:
|
|
374
|
+
print("• Hermes gateway restart skipped by --no-restart. Run it manually after setup if Hermes is already running.")
|
|
375
|
+
return
|
|
376
|
+
if _process_tree_contains_hermes():
|
|
377
|
+
print("• Hermes gateway restart skipped because this setup is running inside Hermes. Restart Hermes from an external shell after this command finishes.")
|
|
378
|
+
return
|
|
379
|
+
if _run_checked([hermes, "gateway", "restart"], "Hermes gateway restart", required=False):
|
|
380
|
+
print("✓ Hermes gateway restart requested")
|
|
381
|
+
else:
|
|
382
|
+
print("• Restart Hermes manually if it is already running: hermes gateway restart")
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def cmd_connect(args: argparse.Namespace) -> int:
|
|
386
|
+
client = args.client
|
|
387
|
+
try:
|
|
388
|
+
setup = _decode_setup_payload(args.setup)
|
|
389
|
+
except ValueError as e:
|
|
390
|
+
print(f"Invalid setup: {e}", file=sys.stderr)
|
|
391
|
+
return 2
|
|
392
|
+
|
|
393
|
+
current = env_file.read_env_file()
|
|
394
|
+
current["GETBASED_STACK_MANAGED"] = "1"
|
|
395
|
+
current["GETBASED_TOKEN"] = setup["token"]
|
|
396
|
+
current["GETBASED_AGENT_CONTEXT_KEY"] = setup["context_key"]
|
|
397
|
+
current["GETBASED_GATEWAY"] = setup["gateway"]
|
|
398
|
+
current.setdefault("LENS_URL", "http://127.0.0.1:8322")
|
|
399
|
+
path = env_file.write_env_file(current)
|
|
400
|
+
print(f"✓ Agent Access credentials stored in {path} (mode 0600)")
|
|
401
|
+
|
|
402
|
+
mcp_cmd = shutil.which("getbased-mcp") or "getbased-mcp"
|
|
403
|
+
|
|
404
|
+
if client == "hermes":
|
|
405
|
+
hermes = shutil.which("hermes")
|
|
406
|
+
if not hermes:
|
|
407
|
+
print("Hermes CLI not found on PATH. Install Hermes or paste `getbased-stack mcp-config hermes` manually.", file=sys.stderr)
|
|
408
|
+
return 1
|
|
409
|
+
add_cmd = [
|
|
410
|
+
hermes, "mcp", "add", "getbased",
|
|
411
|
+
"--command", mcp_cmd,
|
|
412
|
+
"--env", "GETBASED_STACK_MANAGED=1",
|
|
413
|
+
]
|
|
414
|
+
if not _run_checked(add_cmd, "Hermes MCP registration", input_text="y\ny\n"):
|
|
415
|
+
return 1
|
|
416
|
+
print("✓ Hermes MCP server registered")
|
|
417
|
+
if _run_checked([hermes, "mcp", "test", "getbased"], "Hermes MCP test", required=False):
|
|
418
|
+
print("✓ Connection test passed")
|
|
419
|
+
else:
|
|
420
|
+
print("• Connection test skipped/failed; run `hermes mcp test getbased` after restarting Hermes.")
|
|
421
|
+
if not args.no_restart:
|
|
422
|
+
_maybe_restart_hermes_gateway(hermes, no_restart=args.no_restart)
|
|
423
|
+
else:
|
|
424
|
+
_maybe_restart_hermes_gateway(hermes, no_restart=True)
|
|
425
|
+
print("Done. Ask Hermes: “Read my getbased profile.”")
|
|
426
|
+
return 0
|
|
427
|
+
|
|
428
|
+
print(f"Stored Agent Access credentials. Generate/paste this client config next: getbased-stack mcp-config {client}")
|
|
429
|
+
return 0
|
|
430
|
+
|
|
431
|
+
|
|
292
432
|
def cmd_version(args: argparse.Namespace) -> int:
|
|
293
433
|
try:
|
|
294
434
|
import importlib.metadata as md
|
|
@@ -375,6 +515,23 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
375
515
|
help="Which client to emit for.",
|
|
376
516
|
)
|
|
377
517
|
|
|
518
|
+
pc = sub.add_parser("connect", help="Store an Agent Access setup code and configure a client.")
|
|
519
|
+
pc.add_argument(
|
|
520
|
+
"client",
|
|
521
|
+
choices=mcp_configs.SUPPORTED_CLIENTS,
|
|
522
|
+
help="Which client to configure.",
|
|
523
|
+
)
|
|
524
|
+
pc.add_argument(
|
|
525
|
+
"--setup",
|
|
526
|
+
required=True,
|
|
527
|
+
help="Private gbsetup_v1_... setup code copied from getbased Settings → Agent Access.",
|
|
528
|
+
)
|
|
529
|
+
pc.add_argument(
|
|
530
|
+
"--no-restart",
|
|
531
|
+
action="store_true",
|
|
532
|
+
help="Do not try to restart Hermes gateway after registering the MCP server.",
|
|
533
|
+
)
|
|
534
|
+
|
|
378
535
|
sub.add_parser("version", help="Print installed package versions.")
|
|
379
536
|
|
|
380
537
|
return p
|
|
@@ -387,6 +544,7 @@ COMMANDS = {
|
|
|
387
544
|
"status": cmd_status,
|
|
388
545
|
"set": cmd_set,
|
|
389
546
|
"mcp-config": cmd_mcp_config,
|
|
547
|
+
"connect": cmd_connect,
|
|
390
548
|
"version": cmd_version,
|
|
391
549
|
}
|
|
392
550
|
|
{getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/src/getbased_agent_stack/mcp_configs.py
RENAMED
|
@@ -18,12 +18,15 @@ from typing import Callable
|
|
|
18
18
|
|
|
19
19
|
SUPPORTED_CLIENTS = ("claude-desktop", "claude-code", "cursor", "cline", "hermes")
|
|
20
20
|
|
|
21
|
-
# Single-token list for the Hermes snippet. Matches the
|
|
22
|
-
#
|
|
21
|
+
# Single-token list for the Hermes snippet. Matches the lab-context +
|
|
22
|
+
# knowledge tools exposed by current getbased-mcp. Keep alphabetical for
|
|
23
|
+
# diff-friendliness.
|
|
23
24
|
HERMES_ENABLED_TOOLS = [
|
|
25
|
+
"getbased_lab_context",
|
|
24
26
|
"getbased_lens_config",
|
|
25
27
|
"getbased_list_profiles",
|
|
26
|
-
"
|
|
28
|
+
"getbased_section",
|
|
29
|
+
"getbased_wearables_series",
|
|
27
30
|
"knowledge_activate_library",
|
|
28
31
|
"knowledge_list_libraries",
|
|
29
32
|
"knowledge_search",
|
|
@@ -108,6 +108,17 @@ class UnitManager:
|
|
|
108
108
|
args.extend(SERVICE_NAMES)
|
|
109
109
|
return self._shell(args)
|
|
110
110
|
|
|
111
|
+
def restart(self) -> CommandResult:
|
|
112
|
+
"""Restart bundled services after an upgrade/reinstall.
|
|
113
|
+
|
|
114
|
+
`uv tool install` replaces files inside the tool environment. If the
|
|
115
|
+
services are already running, `systemctl enable --now …` is a no-op and
|
|
116
|
+
the old Python processes can keep serving from a half-replaced venv.
|
|
117
|
+
Restarting makes repeated `curl | bash` installs safe and starts the
|
|
118
|
+
units when they were previously inactive.
|
|
119
|
+
"""
|
|
120
|
+
return self._shell(["systemctl", "--user", "restart", *SERVICE_NAMES])
|
|
121
|
+
|
|
111
122
|
def disable(self, now: bool = True) -> CommandResult:
|
|
112
123
|
args = ["systemctl", "--user", "disable"]
|
|
113
124
|
if now:
|
|
@@ -154,7 +165,11 @@ class UnitManager:
|
|
|
154
165
|
else:
|
|
155
166
|
log.append("enabled " + ", ".join(SERVICE_NAMES))
|
|
156
167
|
if start:
|
|
157
|
-
|
|
168
|
+
r = self.restart()
|
|
169
|
+
if r.returncode != 0:
|
|
170
|
+
log.append(f"restart FAILED: {r.stderr.strip()}")
|
|
171
|
+
else:
|
|
172
|
+
log.append("restarted " + ", ".join(SERVICE_NAMES))
|
|
158
173
|
return log
|
|
159
174
|
|
|
160
175
|
def uninstall(self) -> "list[str]":
|
{getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5/src/getbased_agent_stack.egg-info}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: getbased-agent-stack
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.5
|
|
4
4
|
Summary: One-command install of the full getbased agent stack — getbased-mcp + getbased-rag + getbased-dashboard
|
|
5
5
|
License-Expression: AGPL-3.0-or-later
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -79,7 +79,7 @@ getbased-stack init --yes
|
|
|
79
79
|
|
|
80
80
|
The wizard (~30 seconds):
|
|
81
81
|
|
|
82
|
-
1. Prompts for your `GETBASED_TOKEN` and `GETBASED_AGENT_CONTEXT_KEY` (skip if you don't use Agent Access; `--yes`
|
|
82
|
+
1. Prompts for your `GETBASED_TOKEN` and `GETBASED_AGENT_CONTEXT_KEY` when you opt into Agent Access (skip if you don't use Agent Access; `--yes` skips prompts)
|
|
83
83
|
2. Generates a rag API key if one doesn't exist
|
|
84
84
|
3. Writes `~/.config/getbased/env` (mode 0600) — the shared config file
|
|
85
85
|
4. Installs systemd user units for rag + dashboard, enables them, starts them
|
|
@@ -96,10 +96,14 @@ def test_install_writes_units_and_enables(stack_home, fake_shell):
|
|
|
96
96
|
assert (unit_dir / "getbased-rag.service").exists()
|
|
97
97
|
assert (unit_dir / "getbased-dashboard.service").exists()
|
|
98
98
|
|
|
99
|
-
# daemon-reload
|
|
99
|
+
# daemon-reload, enable --now, and restart all run. The explicit restart
|
|
100
|
+
# matters for re-running install.sh over an active stack after uv replaces
|
|
101
|
+
# the tool environment: old Python processes must not keep serving from a
|
|
102
|
+
# half-replaced venv.
|
|
100
103
|
all_calls = [" ".join(cmd) for cmd in fake_shell]
|
|
101
104
|
assert any("daemon-reload" in c for c in all_calls)
|
|
102
105
|
assert any("enable --now" in c for c in all_calls)
|
|
106
|
+
assert any("restart getbased-rag.service getbased-dashboard.service" in c for c in all_calls)
|
|
103
107
|
|
|
104
108
|
|
|
105
109
|
def test_install_no_enable_flag(stack_home, fake_shell):
|
|
@@ -220,6 +224,94 @@ def test_mcp_config_unknown_client():
|
|
|
220
224
|
assert rc != 0
|
|
221
225
|
|
|
222
226
|
|
|
227
|
+
# ── connect ───────────────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _setup_blob(token="tok_123", context_key="ctx_456", gateway="https://sync.getbased.health"):
|
|
231
|
+
import base64
|
|
232
|
+
import json
|
|
233
|
+
|
|
234
|
+
payload = json.dumps({
|
|
235
|
+
"version": 1,
|
|
236
|
+
"token": token,
|
|
237
|
+
"contextKey": context_key,
|
|
238
|
+
"gateway": gateway,
|
|
239
|
+
}, separators=(",", ":")).encode()
|
|
240
|
+
return "gbsetup_v1_" + base64.urlsafe_b64encode(payload).decode().rstrip("=")
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def test_connect_hermes_setup_blob_stores_secrets_and_registers_mcp(stack_home, monkeypatch):
|
|
244
|
+
monkeypatch.setenv("GETBASED_STACK_ASSUME_HERMES_CHILD", "0")
|
|
245
|
+
calls = []
|
|
246
|
+
monkeypatch.setattr(cli.shutil, "which", lambda name: {
|
|
247
|
+
"getbased-mcp": "/usr/local/bin/getbased-mcp",
|
|
248
|
+
"hermes": "/usr/local/bin/hermes",
|
|
249
|
+
}.get(name))
|
|
250
|
+
|
|
251
|
+
def fake_run(cmd, **kwargs):
|
|
252
|
+
calls.append((cmd, kwargs.get("input")))
|
|
253
|
+
class Result:
|
|
254
|
+
returncode = 0
|
|
255
|
+
stdout = "ok"
|
|
256
|
+
stderr = ""
|
|
257
|
+
return Result()
|
|
258
|
+
|
|
259
|
+
monkeypatch.setattr(cli.subprocess, "run", fake_run)
|
|
260
|
+
|
|
261
|
+
rc, out, err = _run(["connect", "hermes", "--setup", _setup_blob()])
|
|
262
|
+
|
|
263
|
+
assert rc == 0, err
|
|
264
|
+
data = env_file.read_env_file()
|
|
265
|
+
assert data["GETBASED_STACK_MANAGED"] == "1"
|
|
266
|
+
assert data["GETBASED_TOKEN"] == "tok_123"
|
|
267
|
+
assert data["GETBASED_AGENT_CONTEXT_KEY"] == "ctx_456"
|
|
268
|
+
assert data["GETBASED_GATEWAY"] == "https://sync.getbased.health"
|
|
269
|
+
assert ([
|
|
270
|
+
"/usr/local/bin/hermes", "mcp", "add", "getbased",
|
|
271
|
+
"--command", "/usr/local/bin/getbased-mcp",
|
|
272
|
+
"--env", "GETBASED_STACK_MANAGED=1",
|
|
273
|
+
], "y\ny\n") in calls
|
|
274
|
+
assert (["/usr/local/bin/hermes", "mcp", "test", "getbased"], None) in calls
|
|
275
|
+
assert (["/usr/local/bin/hermes", "gateway", "restart"], None) in calls
|
|
276
|
+
assert "Hermes MCP server registered" in out
|
|
277
|
+
assert "Connection test passed" in out
|
|
278
|
+
assert "tok_123" not in out
|
|
279
|
+
assert "ctx_456" not in out
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def test_connect_rejects_malformed_setup_blob(stack_home):
|
|
283
|
+
rc, out, err = _run(["connect", "hermes", "--setup", "not-a-setup-code"])
|
|
284
|
+
assert rc == 2
|
|
285
|
+
assert "Invalid setup" in err
|
|
286
|
+
assert env_file.read_env_file() == {}
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def test_connect_skips_gateway_restart_when_running_inside_hermes(stack_home, monkeypatch):
|
|
290
|
+
monkeypatch.setenv("GETBASED_STACK_ASSUME_HERMES_CHILD", "1")
|
|
291
|
+
calls = []
|
|
292
|
+
monkeypatch.setattr(cli.shutil, "which", lambda name: {
|
|
293
|
+
"getbased-mcp": "/usr/local/bin/getbased-mcp",
|
|
294
|
+
"hermes": "/usr/local/bin/hermes",
|
|
295
|
+
}.get(name))
|
|
296
|
+
|
|
297
|
+
def fake_run(cmd, **kwargs):
|
|
298
|
+
calls.append((cmd, kwargs.get("input")))
|
|
299
|
+
class Result:
|
|
300
|
+
returncode = 0
|
|
301
|
+
stdout = "ok"
|
|
302
|
+
stderr = ""
|
|
303
|
+
return Result()
|
|
304
|
+
|
|
305
|
+
monkeypatch.setattr(cli.subprocess, "run", fake_run)
|
|
306
|
+
|
|
307
|
+
rc, out, err = _run(["connect", "hermes", "--setup", _setup_blob()])
|
|
308
|
+
|
|
309
|
+
assert rc == 0, err
|
|
310
|
+
assert (["/usr/local/bin/hermes", "mcp", "test", "getbased"], None) in calls
|
|
311
|
+
assert all(cmd != ["/usr/local/bin/hermes", "gateway", "restart"] for cmd, _ in calls)
|
|
312
|
+
assert "restart skipped because this setup is running inside Hermes" in out
|
|
313
|
+
|
|
314
|
+
|
|
223
315
|
# ── init (non-interactive via EOF on stdin) ───────────────────────────
|
|
224
316
|
|
|
225
317
|
|
|
@@ -145,11 +145,13 @@ def test_install_full_sequence(tmp_path):
|
|
|
145
145
|
# systemctl called in the expected order
|
|
146
146
|
assert shell.calls[0] == ["systemctl", "--user", "daemon-reload"]
|
|
147
147
|
assert shell.calls[1] == ["systemctl", "--user", "enable", "--now", *SERVICE_NAMES]
|
|
148
|
+
assert shell.calls[2] == ["systemctl", "--user", "restart", *SERVICE_NAMES]
|
|
148
149
|
# Log mentions what happened
|
|
149
150
|
joined = "\n".join(log)
|
|
150
151
|
assert "wrote" in joined
|
|
151
152
|
assert "enabled" in joined
|
|
152
|
-
assert "
|
|
153
|
+
assert "restarted" in joined
|
|
154
|
+
assert not any(line.startswith("started ") for line in log)
|
|
153
155
|
|
|
154
156
|
|
|
155
157
|
def test_install_daemon_reload_failure_short_circuits(tmp_path):
|
|
File without changes
|
|
File without changes
|
{getbased_agent_stack-0.5.3 → getbased_agent_stack-0.5.5}/src/getbased_agent_stack/env_file.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|