getbased-agent-stack 0.5.5__tar.gz → 0.5.7__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.5 → getbased_agent_stack-0.5.7}/PKG-INFO +27 -12
- getbased_agent_stack-0.5.5/src/getbased_agent_stack.egg-info/PKG-INFO → getbased_agent_stack-0.5.7/README.md +22 -26
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/pyproject.toml +5 -5
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack/__init__.py +1 -1
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack/cli.py +64 -26
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack/env_file.py +0 -1
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack/mcp_configs.py +68 -16
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack/units.py +1 -1
- getbased_agent_stack-0.5.5/README.md → getbased_agent_stack-0.5.7/src/getbased_agent_stack.egg-info/PKG-INFO +41 -7
- getbased_agent_stack-0.5.7/src/getbased_agent_stack.egg-info/requires.txt +11 -0
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/tests/test_cli.py +94 -32
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/tests/test_mcp_configs.py +29 -0
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/tests/test_units.py +1 -6
- getbased_agent_stack-0.5.5/src/getbased_agent_stack.egg-info/requires.txt +0 -11
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/LICENSE +0 -0
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/setup.cfg +0 -0
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack/systemd/getbased-dashboard.service +0 -0
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack/systemd/getbased-rag.service +0 -0
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack.egg-info/SOURCES.txt +0 -0
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack.egg-info/dependency_links.txt +0 -0
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack.egg-info/entry_points.txt +0 -0
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack.egg-info/top_level.txt +0 -0
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/tests/test_env_file.py +0 -0
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/tests/test_integration.py +0 -0
- {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/tests/test_systemd_units.py +0 -0
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: getbased-agent-stack
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.7
|
|
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
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
9
|
-
Requires-Dist: getbased-mcp>=0.2.
|
|
10
|
-
Requires-Dist: getbased-rag>=0.7.
|
|
11
|
-
Requires-Dist: getbased-dashboard>=0.6.
|
|
9
|
+
Requires-Dist: getbased-mcp>=0.2.7
|
|
10
|
+
Requires-Dist: getbased-rag>=0.7.4
|
|
11
|
+
Requires-Dist: getbased-dashboard>=0.6.6
|
|
12
12
|
Provides-Extra: full
|
|
13
|
-
Requires-Dist: getbased-rag[full]>=0.7.
|
|
13
|
+
Requires-Dist: getbased-rag[full]>=0.7.4; extra == "full"
|
|
14
14
|
Provides-Extra: test
|
|
15
15
|
Requires-Dist: pytest>=8.0; extra == "test"
|
|
16
16
|
Requires-Dist: httpx>=0.27; extra == "test"
|
|
@@ -19,7 +19,7 @@ Dynamic: license-file
|
|
|
19
19
|
|
|
20
20
|
# getbased-agent-stack
|
|
21
21
|
|
|
22
|
-
Meta-package bundling the full [getbased](https://getbased.health) agent stack into one install: the MCP adapter, the
|
|
22
|
+
Meta-package bundling the full [getbased](https://getbased.health) agent stack into one install: the MCP adapter, the local knowledge server, the browser dashboard, an orchestration CLI (`init` / `install` / `connect` / `mcp-config`), systemd user units for the knowledge server + dashboard, and paste-ready configs for Claude Code, Claude Desktop, Cursor, Cline, Codex CLI, Hermes, and OpenClaw.
|
|
23
23
|
|
|
24
24
|
Part of the [getbased-agents monorepo](https://github.com/elkimek/getbased-agents).
|
|
25
25
|
|
|
@@ -31,7 +31,15 @@ Linux, one command:
|
|
|
31
31
|
curl -sSL https://getbased.health/install.sh | bash
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
Runs end-to-end: installs `getbased-agent-stack[full]` via whichever of `uv` or `pipx` is available, exposes sibling binaries, runs
|
|
34
|
+
Runs end-to-end: installs `getbased-agent-stack[full]` via whichever of `uv` or `pipx` is available, exposes sibling binaries, runs a non-interactive local-only init, and starts the knowledge server + dashboard as systemd user services. [Read the script first](https://github.com/elkimek/get-based-site/blob/main/install.sh) if you're cautious.
|
|
35
|
+
|
|
36
|
+
If you are connecting a private getbased profile, copy the command from **Settings → Agent Access** in the app. It uses the same installer and then runs `getbased-stack connect` for the selected client:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
curl -fsSL https://getbased.health/install.sh | bash -s -- connect <target> --setup 'gbsetup_v1_...'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That setup payload contains private credentials. Keep it out of logs and public issues.
|
|
35
43
|
|
|
36
44
|
Manual — pipx:
|
|
37
45
|
|
|
@@ -56,7 +64,7 @@ uv tool install \
|
|
|
56
64
|
Pulls:
|
|
57
65
|
|
|
58
66
|
- [`getbased-mcp`](https://github.com/elkimek/getbased-agents/tree/main/packages/mcp) — stdio MCP server that Claude Code / Hermes / OpenClaw spawn
|
|
59
|
-
- [`getbased-rag`](https://github.com/elkimek/getbased-agents/tree/main/packages/rag) — local
|
|
67
|
+
- [`getbased-rag`](https://github.com/elkimek/getbased-agents/tree/main/packages/rag) — local knowledge server (FastAPI + Qdrant + MiniLM/BGE)
|
|
60
68
|
- [`getbased-dashboard`](https://github.com/elkimek/getbased-agents/tree/main/packages/dashboard) — web UI for library management, MCP config generation, and agent-activity inspection
|
|
61
69
|
- The `getbased-stack` discovery CLI
|
|
62
70
|
- `[full]` extra: PDF/DOCX parsers + ONNX runtime for hardware-accelerated embeddings
|
|
@@ -74,7 +82,7 @@ getbased-stack init
|
|
|
74
82
|
Non-interactive (for scripted installs and curl | bash; `install.sh` uses this):
|
|
75
83
|
|
|
76
84
|
```bash
|
|
77
|
-
getbased-stack init --yes
|
|
85
|
+
getbased-stack init --yes --local-only
|
|
78
86
|
```
|
|
79
87
|
|
|
80
88
|
The wizard (~30 seconds):
|
|
@@ -86,10 +94,16 @@ The wizard (~30 seconds):
|
|
|
86
94
|
|
|
87
95
|
On non-systemd hosts (Docker, macOS, WSL1) step 4 writes the unit files but skips activation with a clear message instead of crashing — re-run on a systemd-enabled host or start `lens serve` + `getbased-dashboard serve` manually.
|
|
88
96
|
|
|
89
|
-
|
|
97
|
+
Private setup from the app:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
getbased-stack connect hermes --setup 'gbsetup_v1_...'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Manual config helper:
|
|
90
104
|
|
|
91
105
|
```bash
|
|
92
|
-
getbased-stack mcp-config claude-desktop # or: claude-code, cursor, cline, hermes
|
|
106
|
+
getbased-stack mcp-config claude-desktop # or: claude-code, cursor, cline, codex, hermes, openclaw
|
|
93
107
|
```
|
|
94
108
|
|
|
95
109
|
The snippet carries only `GETBASED_STACK_MANAGED=1` in its env block. No secrets in client configs — every MCP spawn reads the shared env file and loads the token + rag URL + API key path from there.
|
|
@@ -144,7 +158,7 @@ Claude Code / Hermes / OpenClaw Browser
|
|
|
144
158
|
│ │ │ │
|
|
145
159
|
│ HTTP │ HTTP │ proxies │ spawns stdio for Test
|
|
146
160
|
▼ ▼ ▼ ▼
|
|
147
|
-
|
|
161
|
+
context gateway getbased-rag ◀────────┘ getbased-mcp
|
|
148
162
|
(localhost:8322)
|
|
149
163
|
```
|
|
150
164
|
|
|
@@ -162,6 +176,7 @@ The dashboard is likewise stateless — it proxies rag for Knowledge operations,
|
|
|
162
176
|
| 0.1.x | ≥0.2.0 | ≥0.1.0 | — | v1 (multi-library) |
|
|
163
177
|
| 0.2.x | ≥0.2.2 | ≥0.6.0 | ≥0.5.0 | v1 (+ streaming ingest, per-library models) |
|
|
164
178
|
| 0.4.x | ≥0.2.3 | ≥0.7.1 | ≥0.6.1 | v1 (+ shared env file, `getbased-stack init`, systemd units) |
|
|
179
|
+
| 0.5.x | ≥0.2.7 | ≥0.7.4 | ≥0.6.6 | v1 (+ `getbased-stack connect`, multi-client setup targets; explicit local-only scripted init) |
|
|
165
180
|
|
|
166
181
|
Bump the meta's major when sibling protocols break; bump siblings freely for normal features.
|
|
167
182
|
|
|
@@ -1,25 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: getbased-agent-stack
|
|
3
|
-
Version: 0.5.5
|
|
4
|
-
Summary: One-command install of the full getbased agent stack — getbased-mcp + getbased-rag + getbased-dashboard
|
|
5
|
-
License-Expression: AGPL-3.0-or-later
|
|
6
|
-
Requires-Python: >=3.10
|
|
7
|
-
Description-Content-Type: text/markdown
|
|
8
|
-
License-File: LICENSE
|
|
9
|
-
Requires-Dist: getbased-mcp>=0.2.6
|
|
10
|
-
Requires-Dist: getbased-rag>=0.7.2
|
|
11
|
-
Requires-Dist: getbased-dashboard>=0.6.4
|
|
12
|
-
Provides-Extra: full
|
|
13
|
-
Requires-Dist: getbased-rag[full]>=0.7.2; extra == "full"
|
|
14
|
-
Provides-Extra: test
|
|
15
|
-
Requires-Dist: pytest>=8.0; extra == "test"
|
|
16
|
-
Requires-Dist: httpx>=0.27; extra == "test"
|
|
17
|
-
Requires-Dist: pytest-timeout>=2.3; extra == "test"
|
|
18
|
-
Dynamic: license-file
|
|
19
|
-
|
|
20
1
|
# getbased-agent-stack
|
|
21
2
|
|
|
22
|
-
Meta-package bundling the full [getbased](https://getbased.health) agent stack into one install: the MCP adapter, the
|
|
3
|
+
Meta-package bundling the full [getbased](https://getbased.health) agent stack into one install: the MCP adapter, the local knowledge server, the browser dashboard, an orchestration CLI (`init` / `install` / `connect` / `mcp-config`), systemd user units for the knowledge server + dashboard, and paste-ready configs for Claude Code, Claude Desktop, Cursor, Cline, Codex CLI, Hermes, and OpenClaw.
|
|
23
4
|
|
|
24
5
|
Part of the [getbased-agents monorepo](https://github.com/elkimek/getbased-agents).
|
|
25
6
|
|
|
@@ -31,7 +12,15 @@ Linux, one command:
|
|
|
31
12
|
curl -sSL https://getbased.health/install.sh | bash
|
|
32
13
|
```
|
|
33
14
|
|
|
34
|
-
Runs end-to-end: installs `getbased-agent-stack[full]` via whichever of `uv` or `pipx` is available, exposes sibling binaries, runs
|
|
15
|
+
Runs end-to-end: installs `getbased-agent-stack[full]` via whichever of `uv` or `pipx` is available, exposes sibling binaries, runs a non-interactive local-only init, and starts the knowledge server + dashboard as systemd user services. [Read the script first](https://github.com/elkimek/get-based-site/blob/main/install.sh) if you're cautious.
|
|
16
|
+
|
|
17
|
+
If you are connecting a private getbased profile, copy the command from **Settings → Agent Access** in the app. It uses the same installer and then runs `getbased-stack connect` for the selected client:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
curl -fsSL https://getbased.health/install.sh | bash -s -- connect <target> --setup 'gbsetup_v1_...'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
That setup payload contains private credentials. Keep it out of logs and public issues.
|
|
35
24
|
|
|
36
25
|
Manual — pipx:
|
|
37
26
|
|
|
@@ -56,7 +45,7 @@ uv tool install \
|
|
|
56
45
|
Pulls:
|
|
57
46
|
|
|
58
47
|
- [`getbased-mcp`](https://github.com/elkimek/getbased-agents/tree/main/packages/mcp) — stdio MCP server that Claude Code / Hermes / OpenClaw spawn
|
|
59
|
-
- [`getbased-rag`](https://github.com/elkimek/getbased-agents/tree/main/packages/rag) — local
|
|
48
|
+
- [`getbased-rag`](https://github.com/elkimek/getbased-agents/tree/main/packages/rag) — local knowledge server (FastAPI + Qdrant + MiniLM/BGE)
|
|
60
49
|
- [`getbased-dashboard`](https://github.com/elkimek/getbased-agents/tree/main/packages/dashboard) — web UI for library management, MCP config generation, and agent-activity inspection
|
|
61
50
|
- The `getbased-stack` discovery CLI
|
|
62
51
|
- `[full]` extra: PDF/DOCX parsers + ONNX runtime for hardware-accelerated embeddings
|
|
@@ -74,7 +63,7 @@ getbased-stack init
|
|
|
74
63
|
Non-interactive (for scripted installs and curl | bash; `install.sh` uses this):
|
|
75
64
|
|
|
76
65
|
```bash
|
|
77
|
-
getbased-stack init --yes
|
|
66
|
+
getbased-stack init --yes --local-only
|
|
78
67
|
```
|
|
79
68
|
|
|
80
69
|
The wizard (~30 seconds):
|
|
@@ -86,10 +75,16 @@ The wizard (~30 seconds):
|
|
|
86
75
|
|
|
87
76
|
On non-systemd hosts (Docker, macOS, WSL1) step 4 writes the unit files but skips activation with a clear message instead of crashing — re-run on a systemd-enabled host or start `lens serve` + `getbased-dashboard serve` manually.
|
|
88
77
|
|
|
89
|
-
|
|
78
|
+
Private setup from the app:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
getbased-stack connect hermes --setup 'gbsetup_v1_...'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Manual config helper:
|
|
90
85
|
|
|
91
86
|
```bash
|
|
92
|
-
getbased-stack mcp-config claude-desktop # or: claude-code, cursor, cline, hermes
|
|
87
|
+
getbased-stack mcp-config claude-desktop # or: claude-code, cursor, cline, codex, hermes, openclaw
|
|
93
88
|
```
|
|
94
89
|
|
|
95
90
|
The snippet carries only `GETBASED_STACK_MANAGED=1` in its env block. No secrets in client configs — every MCP spawn reads the shared env file and loads the token + rag URL + API key path from there.
|
|
@@ -144,7 +139,7 @@ Claude Code / Hermes / OpenClaw Browser
|
|
|
144
139
|
│ │ │ │
|
|
145
140
|
│ HTTP │ HTTP │ proxies │ spawns stdio for Test
|
|
146
141
|
▼ ▼ ▼ ▼
|
|
147
|
-
|
|
142
|
+
context gateway getbased-rag ◀────────┘ getbased-mcp
|
|
148
143
|
(localhost:8322)
|
|
149
144
|
```
|
|
150
145
|
|
|
@@ -162,6 +157,7 @@ The dashboard is likewise stateless — it proxies rag for Knowledge operations,
|
|
|
162
157
|
| 0.1.x | ≥0.2.0 | ≥0.1.0 | — | v1 (multi-library) |
|
|
163
158
|
| 0.2.x | ≥0.2.2 | ≥0.6.0 | ≥0.5.0 | v1 (+ streaming ingest, per-library models) |
|
|
164
159
|
| 0.4.x | ≥0.2.3 | ≥0.7.1 | ≥0.6.1 | v1 (+ shared env file, `getbased-stack init`, systemd units) |
|
|
160
|
+
| 0.5.x | ≥0.2.7 | ≥0.7.4 | ≥0.6.6 | v1 (+ `getbased-stack connect`, multi-client setup targets; explicit local-only scripted init) |
|
|
165
161
|
|
|
166
162
|
Bump the meta's major when sibling protocols break; bump siblings freely for normal features.
|
|
167
163
|
|
|
@@ -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.7"
|
|
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"
|
|
@@ -12,15 +12,15 @@ requires-python = ">=3.10"
|
|
|
12
12
|
# Pulls every sibling package. Bump this meta when a sibling protocol
|
|
13
13
|
# bump requires coordinated release.
|
|
14
14
|
dependencies = [
|
|
15
|
-
"getbased-mcp>=0.2.
|
|
16
|
-
"getbased-rag>=0.7.
|
|
17
|
-
"getbased-dashboard>=0.6.
|
|
15
|
+
"getbased-mcp>=0.2.7",
|
|
16
|
+
"getbased-rag>=0.7.4",
|
|
17
|
+
"getbased-dashboard>=0.6.6",
|
|
18
18
|
]
|
|
19
19
|
|
|
20
20
|
[project.optional-dependencies]
|
|
21
21
|
# `full` includes PDF/DOCX parsers and ONNX acceleration (pre-exported
|
|
22
22
|
# weights — no PyTorch/transformers/optimum dance).
|
|
23
|
-
full = ["getbased-rag[full]>=0.7.
|
|
23
|
+
full = ["getbased-rag[full]>=0.7.4"]
|
|
24
24
|
# For contributors running the integration test harness.
|
|
25
25
|
test = ["pytest>=8.0", "httpx>=0.27", "pytest-timeout>=2.3"]
|
|
26
26
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""getbased-stack — orchestration CLI.
|
|
2
2
|
|
|
3
3
|
Subcommands:
|
|
4
|
-
init — interactive one-time setup:
|
|
4
|
+
init — interactive one-time setup: API key, systemd units
|
|
5
5
|
install — install/refresh the bundled systemd user units
|
|
6
6
|
uninstall — stop, disable, and remove the systemd units
|
|
7
7
|
status — show env file, units, and linger state
|
|
@@ -93,6 +93,7 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
93
93
|
# for scripted installs (curl | bash) where stdin is unavailable and
|
|
94
94
|
# the EOF-returns-default path still triggers a getpass echo warning.
|
|
95
95
|
non_interactive = getattr(args, "yes", False)
|
|
96
|
+
local_only = getattr(args, "local_only", False)
|
|
96
97
|
|
|
97
98
|
print("getbased-stack init — one-time setup")
|
|
98
99
|
if non_interactive:
|
|
@@ -110,21 +111,43 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
110
111
|
masked = "****" + current_token[-4:] if current_token else "(unset)"
|
|
111
112
|
key_masked = "****" + current_context_key[-4:] if current_context_key else "(unset)"
|
|
112
113
|
print(f"[1/4] Agent Access token + context key (token: {masked}, key: {key_masked})")
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
114
|
+
token = current_token
|
|
115
|
+
context_key = current_context_key
|
|
116
|
+
gateway = existing.get("GETBASED_GATEWAY", "")
|
|
117
|
+
setup_code = (getattr(args, "setup", "") or "").strip()
|
|
118
|
+
if setup_code:
|
|
119
|
+
try:
|
|
120
|
+
setup_values = _decode_setup_payload(setup_code)
|
|
121
|
+
except ValueError as e:
|
|
122
|
+
print(f"Invalid setup: {e}", file=sys.stderr)
|
|
123
|
+
return 2
|
|
124
|
+
token = setup_values["token"]
|
|
125
|
+
context_key = setup_values["context_key"]
|
|
126
|
+
gateway = setup_values["gateway"]
|
|
127
|
+
print(" setup code accepted — Agent Access credentials will be stored.")
|
|
128
|
+
elif token and context_key:
|
|
129
|
+
print(" existing Agent Access credentials found — keeping them.")
|
|
130
|
+
elif local_only:
|
|
131
|
+
print(
|
|
132
|
+
" no complete Agent Access credentials yet — continuing local-only. "
|
|
133
|
+
"Blood-work Agent Access tools stay disabled until you run "
|
|
134
|
+
"`getbased-stack connect hermes --setup gbsetup_v1_…` or rerun "
|
|
135
|
+
"`getbased-stack init --setup gbsetup_v1_…`."
|
|
122
136
|
)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
137
|
+
elif non_interactive:
|
|
138
|
+
print(
|
|
139
|
+
" Agent Access setup code required for non-interactive first-run setup. "
|
|
140
|
+
"Pass `--setup gbsetup_v1_…`, or add `--local-only` to install only "
|
|
141
|
+
"rag/dashboard without Agent Access."
|
|
142
|
+
)
|
|
143
|
+
return 2
|
|
144
|
+
else:
|
|
145
|
+
print(
|
|
146
|
+
" Agent Access setup code required for full first-run setup. Rerun "
|
|
147
|
+
"with `getbased-stack init --setup gbsetup_v1_…`, or pass "
|
|
148
|
+
"`--local-only` if you only want rag/dashboard without Agent Access."
|
|
127
149
|
)
|
|
150
|
+
return 2
|
|
128
151
|
|
|
129
152
|
# 2. API key
|
|
130
153
|
key_path = Path(existing.get("LENS_API_KEY_FILE", str(_default_api_key_file())))
|
|
@@ -144,6 +167,8 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
144
167
|
merged["GETBASED_TOKEN"] = token
|
|
145
168
|
if context_key:
|
|
146
169
|
merged["GETBASED_AGENT_CONTEXT_KEY"] = context_key
|
|
170
|
+
if gateway:
|
|
171
|
+
merged["GETBASED_GATEWAY"] = gateway
|
|
147
172
|
merged["LENS_API_KEY_FILE"] = str(key_path)
|
|
148
173
|
merged.setdefault("LENS_URL", "http://127.0.0.1:8322")
|
|
149
174
|
path = env_file.write_env_file(merged)
|
|
@@ -166,17 +191,20 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
166
191
|
_print_linger_hint(strict=False)
|
|
167
192
|
|
|
168
193
|
# 6. MCP config pointers
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
print(
|
|
194
|
+
if token and context_key:
|
|
195
|
+
print("\nAgent Access credentials are configured ✓")
|
|
196
|
+
print(" Generate or refresh client config with:")
|
|
197
|
+
for client in mcp_configs.SUPPORTED_CLIENTS:
|
|
198
|
+
print(f" getbased-stack mcp-config {client}")
|
|
199
|
+
else:
|
|
200
|
+
print("\nNext steps for Agent Access:")
|
|
201
|
+
print(" 1. In getbased, enable Cross-device Sync and Agent Access.")
|
|
202
|
+
print(" 2. Copy the private setup code from Settings → Agent Access.")
|
|
203
|
+
print(" 3. Store it here and register your MCP client with:")
|
|
204
|
+
print(" getbased-stack connect hermes --setup gbsetup_v1_...")
|
|
205
|
+
print(" 4. Or generate a manual client config with:")
|
|
206
|
+
for client in mcp_configs.SUPPORTED_CLIENTS:
|
|
207
|
+
print(f" getbased-stack mcp-config {client}")
|
|
180
208
|
|
|
181
209
|
return 0
|
|
182
210
|
|
|
@@ -483,7 +511,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
483
511
|
sub = p.add_subparsers(dest="command")
|
|
484
512
|
|
|
485
513
|
pinit = sub.add_parser(
|
|
486
|
-
"init", help="Interactive one-time setup (
|
|
514
|
+
"init", help="Interactive one-time setup (API key, units)."
|
|
487
515
|
)
|
|
488
516
|
pinit.add_argument(
|
|
489
517
|
"-y",
|
|
@@ -491,6 +519,16 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
491
519
|
action="store_true",
|
|
492
520
|
help="Non-interactive: take defaults on every prompt. Use for scripted installs.",
|
|
493
521
|
)
|
|
522
|
+
pinit.add_argument(
|
|
523
|
+
"--setup",
|
|
524
|
+
default="",
|
|
525
|
+
help="Private gbsetup_v1_... setup code from getbased Settings → Agent Access.",
|
|
526
|
+
)
|
|
527
|
+
pinit.add_argument(
|
|
528
|
+
"--local-only",
|
|
529
|
+
action="store_true",
|
|
530
|
+
help="Install rag/dashboard without Agent Access credentials; run connect later.",
|
|
531
|
+
)
|
|
494
532
|
|
|
495
533
|
pi = sub.add_parser("install", help="Install + start the systemd user units.")
|
|
496
534
|
pi.add_argument("--no-enable", action="store_true", help="Copy files only; don't enable.")
|
{getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack/mcp_configs.py
RENAMED
|
@@ -16,7 +16,15 @@ import shutil
|
|
|
16
16
|
from typing import Callable
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
SUPPORTED_CLIENTS = (
|
|
19
|
+
SUPPORTED_CLIENTS = (
|
|
20
|
+
"claude-desktop",
|
|
21
|
+
"claude-code",
|
|
22
|
+
"cursor",
|
|
23
|
+
"cline",
|
|
24
|
+
"hermes",
|
|
25
|
+
"openclaw",
|
|
26
|
+
"codex",
|
|
27
|
+
)
|
|
20
28
|
|
|
21
29
|
# Single-token list for the Hermes snippet. Matches the lab-context +
|
|
22
30
|
# knowledge tools exposed by current getbased-mcp. Keep alphabetical for
|
|
@@ -118,6 +126,49 @@ def emit_cline(resolver: Callable[[str], "str | None"] = shutil.which) -> str:
|
|
|
118
126
|
return banner + json.dumps(payload, indent=2) + "\n"
|
|
119
127
|
|
|
120
128
|
|
|
129
|
+
def emit_openclaw(resolver: Callable[[str], "str | None"] = shutil.which) -> str:
|
|
130
|
+
"""OpenClaw nests MCP servers under `mcp.servers.<name>` rather than
|
|
131
|
+
the Anthropic-style top-level `mcpServers` key."""
|
|
132
|
+
cmd = _resolve_mcp_binary(resolver)
|
|
133
|
+
body = {
|
|
134
|
+
"mcp": {
|
|
135
|
+
"servers": {
|
|
136
|
+
"getbased": {
|
|
137
|
+
"command": cmd,
|
|
138
|
+
"args": [],
|
|
139
|
+
"env": {"GETBASED_STACK_MANAGED": "1"},
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
banner = "// Paste into ~/.openclaw/openclaw.json, or adapt for `openclaw mcp set getbased`.\n"
|
|
145
|
+
banner = _prepend_warning(banner, _resolver_warning(cmd))
|
|
146
|
+
return banner + json.dumps(body, indent=2) + "\n"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def emit_codex(resolver: Callable[[str], "str | None"] = shutil.which) -> str:
|
|
150
|
+
"""OpenAI Codex CLI uses TOML config under ~/.codex/config.toml."""
|
|
151
|
+
cmd = _resolve_mcp_binary(resolver)
|
|
152
|
+
warning = _resolver_warning(cmd)
|
|
153
|
+
lines = [
|
|
154
|
+
"# Paste into ~/.codex/config.toml",
|
|
155
|
+
"# Merge with existing [mcp_servers.*] tables if present.",
|
|
156
|
+
]
|
|
157
|
+
if warning:
|
|
158
|
+
lines.append("#")
|
|
159
|
+
lines.extend(f"# {line}" for line in warning.splitlines())
|
|
160
|
+
lines.extend([
|
|
161
|
+
"",
|
|
162
|
+
'[mcp_servers.getbased]',
|
|
163
|
+
f'command = {json.dumps(cmd)}',
|
|
164
|
+
"args = []",
|
|
165
|
+
"",
|
|
166
|
+
'[mcp_servers.getbased.env]',
|
|
167
|
+
'GETBASED_STACK_MANAGED = "1"',
|
|
168
|
+
])
|
|
169
|
+
return "\n".join(lines) + "\n"
|
|
170
|
+
|
|
171
|
+
|
|
121
172
|
def emit_hermes(resolver: Callable[[str], "str | None"] = shutil.which) -> str:
|
|
122
173
|
"""Hermes uses YAML — we emit by hand (no yaml dep) since the shape
|
|
123
174
|
is trivial. Includes enabled_tools for parity with the existing
|
|
@@ -164,18 +215,19 @@ def emit_hermes(resolver: Callable[[str], "str | None"] = shutil.which) -> str:
|
|
|
164
215
|
|
|
165
216
|
def emit(client: str, resolver: Callable[[str], "str | None"] = shutil.which) -> str:
|
|
166
217
|
"""Dispatch to the right emitter. Raises ValueError on unknown client."""
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
218
|
+
emitters = {
|
|
219
|
+
"claude-desktop": emit_claude_desktop,
|
|
220
|
+
"claude-code": emit_claude_code,
|
|
221
|
+
"cursor": emit_cursor,
|
|
222
|
+
"cline": emit_cline,
|
|
223
|
+
"hermes": emit_hermes,
|
|
224
|
+
"openclaw": emit_openclaw,
|
|
225
|
+
"codex": emit_codex,
|
|
226
|
+
}
|
|
227
|
+
try:
|
|
228
|
+
emitter = emitters[client]
|
|
229
|
+
except KeyError as e:
|
|
230
|
+
raise ValueError(
|
|
231
|
+
f"unknown client {client!r}. Supported: {', '.join(SUPPORTED_CLIENTS)}"
|
|
232
|
+
) from e
|
|
233
|
+
return emitter(resolver)
|
|
@@ -13,7 +13,7 @@ import subprocess
|
|
|
13
13
|
from dataclasses import dataclass
|
|
14
14
|
from importlib import resources
|
|
15
15
|
from pathlib import Path
|
|
16
|
-
from typing import Callable
|
|
16
|
+
from typing import Callable
|
|
17
17
|
|
|
18
18
|
# Units this package ships. Order matters for start: rag first (dashboard
|
|
19
19
|
# Wants=+After= rag), so systemctl picks the right order naturally anyway.
|
|
@@ -1,6 +1,25 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: getbased-agent-stack
|
|
3
|
+
Version: 0.5.7
|
|
4
|
+
Summary: One-command install of the full getbased agent stack — getbased-mcp + getbased-rag + getbased-dashboard
|
|
5
|
+
License-Expression: AGPL-3.0-or-later
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: getbased-mcp>=0.2.7
|
|
10
|
+
Requires-Dist: getbased-rag>=0.7.4
|
|
11
|
+
Requires-Dist: getbased-dashboard>=0.6.6
|
|
12
|
+
Provides-Extra: full
|
|
13
|
+
Requires-Dist: getbased-rag[full]>=0.7.4; extra == "full"
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
16
|
+
Requires-Dist: httpx>=0.27; extra == "test"
|
|
17
|
+
Requires-Dist: pytest-timeout>=2.3; extra == "test"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
1
20
|
# getbased-agent-stack
|
|
2
21
|
|
|
3
|
-
Meta-package bundling the full [getbased](https://getbased.health) agent stack into one install: the MCP adapter, the
|
|
22
|
+
Meta-package bundling the full [getbased](https://getbased.health) agent stack into one install: the MCP adapter, the local knowledge server, the browser dashboard, an orchestration CLI (`init` / `install` / `connect` / `mcp-config`), systemd user units for the knowledge server + dashboard, and paste-ready configs for Claude Code, Claude Desktop, Cursor, Cline, Codex CLI, Hermes, and OpenClaw.
|
|
4
23
|
|
|
5
24
|
Part of the [getbased-agents monorepo](https://github.com/elkimek/getbased-agents).
|
|
6
25
|
|
|
@@ -12,7 +31,15 @@ Linux, one command:
|
|
|
12
31
|
curl -sSL https://getbased.health/install.sh | bash
|
|
13
32
|
```
|
|
14
33
|
|
|
15
|
-
Runs end-to-end: installs `getbased-agent-stack[full]` via whichever of `uv` or `pipx` is available, exposes sibling binaries, runs
|
|
34
|
+
Runs end-to-end: installs `getbased-agent-stack[full]` via whichever of `uv` or `pipx` is available, exposes sibling binaries, runs a non-interactive local-only init, and starts the knowledge server + dashboard as systemd user services. [Read the script first](https://github.com/elkimek/get-based-site/blob/main/install.sh) if you're cautious.
|
|
35
|
+
|
|
36
|
+
If you are connecting a private getbased profile, copy the command from **Settings → Agent Access** in the app. It uses the same installer and then runs `getbased-stack connect` for the selected client:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
curl -fsSL https://getbased.health/install.sh | bash -s -- connect <target> --setup 'gbsetup_v1_...'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
That setup payload contains private credentials. Keep it out of logs and public issues.
|
|
16
43
|
|
|
17
44
|
Manual — pipx:
|
|
18
45
|
|
|
@@ -37,7 +64,7 @@ uv tool install \
|
|
|
37
64
|
Pulls:
|
|
38
65
|
|
|
39
66
|
- [`getbased-mcp`](https://github.com/elkimek/getbased-agents/tree/main/packages/mcp) — stdio MCP server that Claude Code / Hermes / OpenClaw spawn
|
|
40
|
-
- [`getbased-rag`](https://github.com/elkimek/getbased-agents/tree/main/packages/rag) — local
|
|
67
|
+
- [`getbased-rag`](https://github.com/elkimek/getbased-agents/tree/main/packages/rag) — local knowledge server (FastAPI + Qdrant + MiniLM/BGE)
|
|
41
68
|
- [`getbased-dashboard`](https://github.com/elkimek/getbased-agents/tree/main/packages/dashboard) — web UI for library management, MCP config generation, and agent-activity inspection
|
|
42
69
|
- The `getbased-stack` discovery CLI
|
|
43
70
|
- `[full]` extra: PDF/DOCX parsers + ONNX runtime for hardware-accelerated embeddings
|
|
@@ -55,7 +82,7 @@ getbased-stack init
|
|
|
55
82
|
Non-interactive (for scripted installs and curl | bash; `install.sh` uses this):
|
|
56
83
|
|
|
57
84
|
```bash
|
|
58
|
-
getbased-stack init --yes
|
|
85
|
+
getbased-stack init --yes --local-only
|
|
59
86
|
```
|
|
60
87
|
|
|
61
88
|
The wizard (~30 seconds):
|
|
@@ -67,10 +94,16 @@ The wizard (~30 seconds):
|
|
|
67
94
|
|
|
68
95
|
On non-systemd hosts (Docker, macOS, WSL1) step 4 writes the unit files but skips activation with a clear message instead of crashing — re-run on a systemd-enabled host or start `lens serve` + `getbased-dashboard serve` manually.
|
|
69
96
|
|
|
70
|
-
|
|
97
|
+
Private setup from the app:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
getbased-stack connect hermes --setup 'gbsetup_v1_...'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Manual config helper:
|
|
71
104
|
|
|
72
105
|
```bash
|
|
73
|
-
getbased-stack mcp-config claude-desktop # or: claude-code, cursor, cline, hermes
|
|
106
|
+
getbased-stack mcp-config claude-desktop # or: claude-code, cursor, cline, codex, hermes, openclaw
|
|
74
107
|
```
|
|
75
108
|
|
|
76
109
|
The snippet carries only `GETBASED_STACK_MANAGED=1` in its env block. No secrets in client configs — every MCP spawn reads the shared env file and loads the token + rag URL + API key path from there.
|
|
@@ -125,7 +158,7 @@ Claude Code / Hermes / OpenClaw Browser
|
|
|
125
158
|
│ │ │ │
|
|
126
159
|
│ HTTP │ HTTP │ proxies │ spawns stdio for Test
|
|
127
160
|
▼ ▼ ▼ ▼
|
|
128
|
-
|
|
161
|
+
context gateway getbased-rag ◀────────┘ getbased-mcp
|
|
129
162
|
(localhost:8322)
|
|
130
163
|
```
|
|
131
164
|
|
|
@@ -143,6 +176,7 @@ The dashboard is likewise stateless — it proxies rag for Knowledge operations,
|
|
|
143
176
|
| 0.1.x | ≥0.2.0 | ≥0.1.0 | — | v1 (multi-library) |
|
|
144
177
|
| 0.2.x | ≥0.2.2 | ≥0.6.0 | ≥0.5.0 | v1 (+ streaming ingest, per-library models) |
|
|
145
178
|
| 0.4.x | ≥0.2.3 | ≥0.7.1 | ≥0.6.1 | v1 (+ shared env file, `getbased-stack init`, systemd units) |
|
|
179
|
+
| 0.5.x | ≥0.2.7 | ≥0.7.4 | ≥0.6.6 | v1 (+ `getbased-stack connect`, multi-client setup targets; explicit local-only scripted init) |
|
|
146
180
|
|
|
147
181
|
Bump the meta's major when sibling protocols break; bump siblings freely for normal features.
|
|
148
182
|
|
|
@@ -14,7 +14,7 @@ from pathlib import Path
|
|
|
14
14
|
import pytest
|
|
15
15
|
|
|
16
16
|
from getbased_agent_stack import cli, env_file, units
|
|
17
|
-
from getbased_agent_stack.units import CommandResult
|
|
17
|
+
from getbased_agent_stack.units import CommandResult
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
@pytest.fixture
|
|
@@ -217,6 +217,22 @@ def test_mcp_config_hermes(stack_home):
|
|
|
217
217
|
assert "enabled_tools:" in out
|
|
218
218
|
|
|
219
219
|
|
|
220
|
+
def test_mcp_config_openclaw(stack_home):
|
|
221
|
+
rc, out, _ = _run(["mcp-config", "openclaw"])
|
|
222
|
+
assert rc == 0
|
|
223
|
+
assert '"mcp"' in out
|
|
224
|
+
assert '"servers"' in out
|
|
225
|
+
assert '"GETBASED_STACK_MANAGED": "1"' in out
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def test_mcp_config_codex(stack_home):
|
|
229
|
+
rc, out, _ = _run(["mcp-config", "codex"])
|
|
230
|
+
assert rc == 0
|
|
231
|
+
assert "[mcp_servers.getbased]" in out
|
|
232
|
+
assert "[mcp_servers.getbased.env]" in out
|
|
233
|
+
assert 'GETBASED_STACK_MANAGED = "1"' in out
|
|
234
|
+
|
|
235
|
+
|
|
220
236
|
def test_mcp_config_unknown_client():
|
|
221
237
|
rc, _, _ = _run(["mcp-config", "vim"])
|
|
222
238
|
# argparse rejects before our code runs — exit code 2, SystemExit
|
|
@@ -286,6 +302,26 @@ def test_connect_rejects_malformed_setup_blob(stack_home):
|
|
|
286
302
|
assert env_file.read_env_file() == {}
|
|
287
303
|
|
|
288
304
|
|
|
305
|
+
def test_connect_non_hermes_clients_store_credentials_and_point_to_config(stack_home, monkeypatch):
|
|
306
|
+
monkeypatch.setattr(cli.shutil, "which", lambda name: "/usr/local/bin/getbased-mcp" if name == "getbased-mcp" else None)
|
|
307
|
+
rc, out, err = _run(["connect", "openclaw", "--setup", _setup_blob()])
|
|
308
|
+
assert rc == 0, err
|
|
309
|
+
data = env_file.read_env_file()
|
|
310
|
+
assert data["GETBASED_STACK_MANAGED"] == "1"
|
|
311
|
+
assert data["GETBASED_TOKEN"] == "tok_123"
|
|
312
|
+
assert data["GETBASED_AGENT_CONTEXT_KEY"] == "ctx_456"
|
|
313
|
+
assert "getbased-stack mcp-config openclaw" in out
|
|
314
|
+
assert "tok_123" not in out
|
|
315
|
+
assert "ctx_456" not in out
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def test_connect_codex_is_an_allowed_setup_target(stack_home, monkeypatch):
|
|
319
|
+
monkeypatch.setattr(cli.shutil, "which", lambda name: "/usr/local/bin/getbased-mcp" if name == "getbased-mcp" else None)
|
|
320
|
+
rc, out, err = _run(["connect", "codex", "--setup", _setup_blob()])
|
|
321
|
+
assert rc == 0, err
|
|
322
|
+
assert "getbased-stack mcp-config codex" in out
|
|
323
|
+
|
|
324
|
+
|
|
289
325
|
def test_connect_skips_gateway_restart_when_running_inside_hermes(stack_home, monkeypatch):
|
|
290
326
|
monkeypatch.setenv("GETBASED_STACK_ASSUME_HERMES_CHILD", "1")
|
|
291
327
|
calls = []
|
|
@@ -315,6 +351,14 @@ def test_connect_skips_gateway_restart_when_running_inside_hermes(stack_home, mo
|
|
|
315
351
|
# ── init (non-interactive via EOF on stdin) ───────────────────────────
|
|
316
352
|
|
|
317
353
|
|
|
354
|
+
def test_init_requires_setup_or_local_only_on_clean_interactive_run(stack_home, fake_shell):
|
|
355
|
+
rc, out, _ = _run(["init"])
|
|
356
|
+
assert rc == 2
|
|
357
|
+
assert "--setup" in out
|
|
358
|
+
assert "--local-only" in out
|
|
359
|
+
assert env_file.read_env_file() == {}
|
|
360
|
+
|
|
361
|
+
|
|
318
362
|
def test_init_idempotent_with_empty_input(stack_home, fake_shell, monkeypatch):
|
|
319
363
|
"""Feed EOF for every prompt: init should complete using defaults
|
|
320
364
|
without crashing. This validates the wizard is non-blocking when
|
|
@@ -323,7 +367,7 @@ def test_init_idempotent_with_empty_input(stack_home, fake_shell, monkeypatch):
|
|
|
323
367
|
monkeypatch.setattr("builtins.input", lambda *a, **kw: "")
|
|
324
368
|
monkeypatch.setattr("getpass.getpass", lambda *a, **kw: "")
|
|
325
369
|
|
|
326
|
-
rc, out, _ = _run(["init"])
|
|
370
|
+
rc, out, _ = _run(["init", "--local-only"])
|
|
327
371
|
assert rc == 0
|
|
328
372
|
# Env file created
|
|
329
373
|
path = env_file.env_file_path()
|
|
@@ -337,32 +381,46 @@ def test_init_idempotent_with_empty_input(stack_home, fake_shell, monkeypatch):
|
|
|
337
381
|
|
|
338
382
|
|
|
339
383
|
def test_init_preserves_existing_token(stack_home, fake_shell, monkeypatch):
|
|
340
|
-
"""Re-running init
|
|
341
|
-
value. The wizard uses the previous token as default."""
|
|
384
|
+
"""Re-running init must not wipe an explicitly stored token."""
|
|
342
385
|
env_file.write_env_file(
|
|
343
386
|
{"GETBASED_TOKEN": "preserved_value", "GETBASED_STACK_MANAGED": "1"}
|
|
344
387
|
)
|
|
345
388
|
monkeypatch.setattr("builtins.input", lambda *a, **kw: "")
|
|
346
389
|
monkeypatch.setattr("getpass.getpass", lambda *a, **kw: "")
|
|
347
390
|
|
|
348
|
-
_run(["init"])
|
|
391
|
+
_run(["init", "--local-only"])
|
|
349
392
|
assert env_file.read_env_file()["GETBASED_TOKEN"] == "preserved_value"
|
|
350
393
|
|
|
351
394
|
|
|
352
|
-
def
|
|
395
|
+
def test_init_accepts_setup_blob(stack_home, fake_shell, monkeypatch):
|
|
396
|
+
"""First-run init can store the private Agent Access setup code without
|
|
397
|
+
prompting for raw token/context-key values separately."""
|
|
353
398
|
inputs = iter([""]) # accept install prompt with default
|
|
354
399
|
monkeypatch.setattr("builtins.input", lambda *a, **kw: next(inputs, ""))
|
|
355
|
-
monkeypatch.setattr(
|
|
400
|
+
monkeypatch.setattr(
|
|
401
|
+
"getpass.getpass",
|
|
402
|
+
lambda *a, **kw: (_ for _ in ()).throw(AssertionError("getpass called")),
|
|
403
|
+
)
|
|
356
404
|
|
|
357
|
-
_run([
|
|
358
|
-
|
|
405
|
+
rc, out, err = _run([
|
|
406
|
+
"init",
|
|
407
|
+
"--setup",
|
|
408
|
+
_setup_blob(token="brand_new_token", context_key="ctx_new", gateway="https://sync.example"),
|
|
409
|
+
])
|
|
410
|
+
assert rc == 0, err
|
|
411
|
+
data = env_file.read_env_file()
|
|
412
|
+
assert data["GETBASED_TOKEN"] == "brand_new_token"
|
|
413
|
+
assert data["GETBASED_AGENT_CONTEXT_KEY"] == "ctx_new"
|
|
414
|
+
assert data["GETBASED_GATEWAY"] == "https://sync.example"
|
|
415
|
+
assert "brand_new_token" not in out
|
|
416
|
+
assert "ctx_new" not in out
|
|
359
417
|
|
|
360
418
|
|
|
361
419
|
def test_init_generates_api_key(stack_home, fake_shell, monkeypatch):
|
|
362
420
|
monkeypatch.setattr("builtins.input", lambda *a, **kw: "")
|
|
363
421
|
monkeypatch.setattr("getpass.getpass", lambda *a, **kw: "")
|
|
364
422
|
|
|
365
|
-
_run(["init"])
|
|
423
|
+
_run(["init", "--local-only"])
|
|
366
424
|
key_file = Path(env_file.read_env_file()["LENS_API_KEY_FILE"])
|
|
367
425
|
assert key_file.exists()
|
|
368
426
|
key = key_file.read_text().strip()
|
|
@@ -383,15 +441,14 @@ def test_init_reuses_existing_api_key(stack_home, fake_shell, monkeypatch):
|
|
|
383
441
|
monkeypatch.setattr("builtins.input", lambda *a, **kw: "")
|
|
384
442
|
monkeypatch.setattr("getpass.getpass", lambda *a, **kw: "")
|
|
385
443
|
|
|
386
|
-
_run(["init"])
|
|
444
|
+
_run(["init", "--local-only"])
|
|
387
445
|
assert key_path.read_text().strip() == "preexisting_key"
|
|
388
446
|
|
|
389
447
|
|
|
390
|
-
def
|
|
448
|
+
def test_init_yes_requires_setup_or_local_only_without_prompts(stack_home, fake_shell, monkeypatch):
|
|
391
449
|
"""`init --yes` must not call input() or getpass() at all. Scripted
|
|
392
|
-
installers (curl | bash)
|
|
393
|
-
|
|
394
|
-
Strict assertion: any prompt call fails the test."""
|
|
450
|
+
installers (curl | bash) cannot service prompts, so a clean full setup
|
|
451
|
+
needs --setup; local-only installs must say so explicitly."""
|
|
395
452
|
def _forbid_input(*a, **kw):
|
|
396
453
|
raise AssertionError("input() called under --yes")
|
|
397
454
|
|
|
@@ -402,24 +459,25 @@ def test_init_yes_flag_skips_all_prompts(stack_home, fake_shell, monkeypatch):
|
|
|
402
459
|
monkeypatch.setattr("getpass.getpass", _forbid_getpass)
|
|
403
460
|
|
|
404
461
|
rc, out, _ = _run(["init", "--yes"])
|
|
405
|
-
assert rc ==
|
|
462
|
+
assert rc == 2
|
|
406
463
|
# Banner reflects the mode so the user sees what happened
|
|
407
464
|
assert "non-interactive" in out.lower()
|
|
408
|
-
assert "
|
|
409
|
-
assert "
|
|
410
|
-
#
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
465
|
+
assert "--setup" in out
|
|
466
|
+
assert "--local-only" in out
|
|
467
|
+
# A successful-looking first-run install without credentials would leave
|
|
468
|
+
# Agent Access broken, so --yes must stop before writing env/units unless
|
|
469
|
+
# the caller explicitly opts into --local-only.
|
|
470
|
+
assert not env_file.env_file_path().exists()
|
|
471
|
+
assert not (stack_home / "config" / "systemd" / "user" / "getbased-rag.service").exists()
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def test_init_yes_local_only_installs_units_without_asking(stack_home, fake_shell, monkeypatch):
|
|
475
|
+
"""When the caller explicitly chooses local-only, --yes must install
|
|
476
|
+
+ start without prompting."""
|
|
419
477
|
monkeypatch.setattr("builtins.input", lambda *a, **kw: "")
|
|
420
478
|
monkeypatch.setattr("getpass.getpass", lambda *a, **kw: "")
|
|
421
479
|
|
|
422
|
-
_run(["init", "--yes"])
|
|
480
|
+
_run(["init", "--yes", "--local-only"])
|
|
423
481
|
# UnitManager.install() writes service files under XDG_CONFIG_HOME
|
|
424
482
|
assert (stack_home / "config" / "systemd" / "user" / "getbased-rag.service").exists()
|
|
425
483
|
assert (stack_home / "config" / "systemd" / "user" / "getbased-dashboard.service").exists()
|
|
@@ -435,7 +493,7 @@ def test_init_yes_survives_missing_systemctl(stack_home, fake_shell, monkeypatch
|
|
|
435
493
|
monkeypatch.setattr("builtins.input", lambda *a, **kw: "")
|
|
436
494
|
monkeypatch.setattr("getpass.getpass", lambda *a, **kw: "")
|
|
437
495
|
|
|
438
|
-
rc, out, _ = _run(["init", "--yes"])
|
|
496
|
+
rc, out, _ = _run(["init", "--yes", "--local-only"])
|
|
439
497
|
assert rc == 0
|
|
440
498
|
# Unit files still written (for re-run on a systemd-enabled host)
|
|
441
499
|
assert (stack_home / "config" / "systemd" / "user" / "getbased-rag.service").exists()
|
|
@@ -448,7 +506,11 @@ def test_init_yes_preserves_existing_token(stack_home, fake_shell, monkeypatch):
|
|
|
448
506
|
"""Non-interactive mode must not nuke a previously-saved token —
|
|
449
507
|
it takes the 'keep current' default, same as pressing Enter."""
|
|
450
508
|
env_file.write_env_file(
|
|
451
|
-
{
|
|
509
|
+
{
|
|
510
|
+
"GETBASED_TOKEN": "keep_me",
|
|
511
|
+
"GETBASED_AGENT_CONTEXT_KEY": "ctx_keep",
|
|
512
|
+
"GETBASED_STACK_MANAGED": "1",
|
|
513
|
+
}
|
|
452
514
|
)
|
|
453
515
|
# No input/getpass expected, but stub defensively in case a future
|
|
454
516
|
# code path adds an unguarded prompt — test still catches it.
|
|
@@ -465,10 +527,10 @@ def test_init_is_reentrant(stack_home, fake_shell, monkeypatch):
|
|
|
465
527
|
monkeypatch.setattr("builtins.input", lambda *a, **kw: "")
|
|
466
528
|
monkeypatch.setattr("getpass.getpass", lambda *a, **kw: "")
|
|
467
529
|
|
|
468
|
-
_run(["init"])
|
|
530
|
+
_run(["init", "--local-only"])
|
|
469
531
|
key1 = (stack_home / "data" / "getbased" / "lens" / "api_key").read_text()
|
|
470
532
|
|
|
471
|
-
_run(["init"])
|
|
533
|
+
_run(["init", "--local-only"])
|
|
472
534
|
key2 = (stack_home / "data" / "getbased" / "lens" / "api_key").read_text()
|
|
473
535
|
|
|
474
536
|
assert key1 == key2, "init must not rotate an existing API key"
|
|
@@ -33,6 +33,8 @@ def test_supported_clients_stable_list():
|
|
|
33
33
|
"cursor",
|
|
34
34
|
"cline",
|
|
35
35
|
"hermes",
|
|
36
|
+
"openclaw",
|
|
37
|
+
"codex",
|
|
36
38
|
)
|
|
37
39
|
|
|
38
40
|
|
|
@@ -63,6 +65,27 @@ def test_hermes_emits_yaml_shape():
|
|
|
63
65
|
assert " - knowledge_search" in out
|
|
64
66
|
|
|
65
67
|
|
|
68
|
+
def test_openclaw_emits_nested_mcp_servers_shape():
|
|
69
|
+
out = mcp_configs.emit("openclaw", resolver=_fake_resolver())
|
|
70
|
+
json_lines = [line for line in out.splitlines() if not line.startswith("//")]
|
|
71
|
+
payload = json.loads("\n".join(json_lines))
|
|
72
|
+
assert "mcpServers" not in payload
|
|
73
|
+
server = payload["mcp"]["servers"]["getbased"]
|
|
74
|
+
assert server["command"] == "/usr/local/bin/getbased-mcp"
|
|
75
|
+
assert server["args"] == []
|
|
76
|
+
assert server["env"] == {"GETBASED_STACK_MANAGED": "1"}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_codex_emits_toml_shape():
|
|
80
|
+
out = mcp_configs.emit("codex", resolver=_fake_resolver())
|
|
81
|
+
assert "# Paste into ~/.codex/config.toml" in out
|
|
82
|
+
assert "[mcp_servers.getbased]" in out
|
|
83
|
+
assert 'command = "/usr/local/bin/getbased-mcp"' in out
|
|
84
|
+
assert "args = []" in out
|
|
85
|
+
assert "[mcp_servers.getbased.env]" in out
|
|
86
|
+
assert 'GETBASED_STACK_MANAGED = "1"' in out
|
|
87
|
+
|
|
88
|
+
|
|
66
89
|
def test_no_secret_values_in_snippets():
|
|
67
90
|
"""A snippet must never carry a concrete token or key value. Prose
|
|
68
91
|
comments may mention these variable names for context, but the emitted
|
|
@@ -104,6 +127,12 @@ def test_fallback_emits_warning_for_hermes():
|
|
|
104
127
|
assert "WARNING" in out
|
|
105
128
|
|
|
106
129
|
|
|
130
|
+
@pytest.mark.parametrize("client", ["openclaw", "codex"])
|
|
131
|
+
def test_fallback_emits_warning_for_new_agent_clients(client):
|
|
132
|
+
out = mcp_configs.emit(client, resolver=_fake_resolver(found=None))
|
|
133
|
+
assert "WARNING" in out
|
|
134
|
+
|
|
135
|
+
|
|
107
136
|
def test_no_warning_when_binary_resolved():
|
|
108
137
|
"""When shutil.which does find the binary, the warning banner must
|
|
109
138
|
not appear — we don't want to cry wolf on successful installs."""
|
|
@@ -6,11 +6,6 @@ is contacted — these tests run on a dev laptop, CI, Windows, anywhere.
|
|
|
6
6
|
"""
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from typing import Callable
|
|
11
|
-
|
|
12
|
-
import pytest
|
|
13
|
-
|
|
14
9
|
from getbased_agent_stack import units
|
|
15
10
|
from getbased_agent_stack.units import CommandResult, SERVICE_NAMES, UnitManager
|
|
16
11
|
|
|
@@ -230,7 +225,7 @@ def test_uninstall_sequence(tmp_path):
|
|
|
230
225
|
def test_uninstall_noop_when_nothing_installed(tmp_path):
|
|
231
226
|
shell = FakeShell()
|
|
232
227
|
mgr = UnitManager(unit_dir=tmp_path, shell=shell)
|
|
233
|
-
|
|
228
|
+
mgr.uninstall()
|
|
234
229
|
# Disable is still attempted (safe) but no daemon-reload because no files removed
|
|
235
230
|
reloads = [c for c in shell.calls if c == ["systemctl", "--user", "daemon-reload"]]
|
|
236
231
|
assert reloads == []
|
|
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
|