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.
Files changed (25) hide show
  1. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/PKG-INFO +27 -12
  2. getbased_agent_stack-0.5.5/src/getbased_agent_stack.egg-info/PKG-INFO → getbased_agent_stack-0.5.7/README.md +22 -26
  3. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/pyproject.toml +5 -5
  4. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack/__init__.py +1 -1
  5. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack/cli.py +64 -26
  6. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack/env_file.py +0 -1
  7. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack/mcp_configs.py +68 -16
  8. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack/units.py +1 -1
  9. getbased_agent_stack-0.5.5/README.md → getbased_agent_stack-0.5.7/src/getbased_agent_stack.egg-info/PKG-INFO +41 -7
  10. getbased_agent_stack-0.5.7/src/getbased_agent_stack.egg-info/requires.txt +11 -0
  11. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/tests/test_cli.py +94 -32
  12. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/tests/test_mcp_configs.py +29 -0
  13. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/tests/test_units.py +1 -6
  14. getbased_agent_stack-0.5.5/src/getbased_agent_stack.egg-info/requires.txt +0 -11
  15. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/LICENSE +0 -0
  16. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/setup.cfg +0 -0
  17. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack/systemd/getbased-dashboard.service +0 -0
  18. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack/systemd/getbased-rag.service +0 -0
  19. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack.egg-info/SOURCES.txt +0 -0
  20. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack.egg-info/dependency_links.txt +0 -0
  21. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack.egg-info/entry_points.txt +0 -0
  22. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/src/getbased_agent_stack.egg-info/top_level.txt +0 -0
  23. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/tests/test_env_file.py +0 -0
  24. {getbased_agent_stack-0.5.5 → getbased_agent_stack-0.5.7}/tests/test_integration.py +0 -0
  25. {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.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.6
10
- Requires-Dist: getbased-rag>=0.7.2
11
- Requires-Dist: getbased-dashboard>=0.6.4
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.2; extra == "full"
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 RAG engine, the browser dashboard, an orchestration CLI (`init` / `install` / `mcp-config`), hardened systemd units for rag + dashboard, and paste-ready configs for Claude Desktop/Code, Cursor, Cline, and Hermes.
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 `getbased-stack init --yes`, and starts rag + dashboard as systemd user services. [Read the script first](https://github.com/elkimek/get-based-site/blob/main/install.sh) if you're cautious.
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 RAG knowledge server (FastAPI + Qdrant + MiniLM/BGE)
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
- Then paste one line into your MCP client:
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
- sync GW getbased-rag ◀──────────────┘ getbased-mcp
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 RAG engine, the browser dashboard, an orchestration CLI (`init` / `install` / `mcp-config`), hardened systemd units for rag + dashboard, and paste-ready configs for Claude Desktop/Code, Cursor, Cline, and Hermes.
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 `getbased-stack init --yes`, and starts rag + dashboard as systemd user services. [Read the script first](https://github.com/elkimek/get-based-site/blob/main/install.sh) if you're cautious.
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 RAG knowledge server (FastAPI + Qdrant + MiniLM/BGE)
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
- Then paste one line into your MCP client:
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
- sync GW getbased-rag ◀──────────────┘ getbased-mcp
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.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.6",
16
- "getbased-rag>=0.7.2",
17
- "getbased-dashboard>=0.6.4",
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.2"]
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
 
@@ -5,4 +5,4 @@ plus a small CLI that proxies to the real binaries. Everything
5
5
  interesting lives in the sibling repos.
6
6
  """
7
7
 
8
- __version__ = "0.5.5"
8
+ __version__ = "0.5.7"
@@ -1,7 +1,7 @@
1
1
  """getbased-stack — orchestration CLI.
2
2
 
3
3
  Subcommands:
4
- init — interactive one-time setup: token, API key, systemd units
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
- if non_interactive:
114
- token = current_token
115
- context_key = current_context_key
116
- print(" keeping current values (set with `getbased-stack set GETBASED_TOKEN=…` and `getbased-stack set GETBASED_AGENT_CONTEXT_KEY=…` later).")
117
- else:
118
- token = _prompt(
119
- "Paste GETBASED_TOKEN (press Enter to keep current / skip)",
120
- default=current_token,
121
- secret=True,
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
- context_key = _prompt(
124
- "Paste GETBASED_AGENT_CONTEXT_KEY (press Enter to keep current / skip)",
125
- default=current_context_key,
126
- secret=True,
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
- print("\nNext steps for Agent Access:")
170
- print(" 1. In getbased, enable Cross-device Sync and Agent Access.")
171
- print(" 2. Copy both values from Settings → Agent Access:")
172
- print(" GETBASED_TOKEN and GETBASED_AGENT_CONTEXT_KEY.")
173
- print(" 3. Save them here with:")
174
- print(" getbased-stack set GETBASED_TOKEN=...")
175
- print(" getbased-stack set GETBASED_AGENT_CONTEXT_KEY=...")
176
-
177
- print("\nConfigure your MCP client(s):")
178
- for client in mcp_configs.SUPPORTED_CLIENTS:
179
- print(f" getbased-stack mcp-config {client}")
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 (token, API key, units)."
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.")
@@ -10,7 +10,6 @@ from __future__ import annotations
10
10
 
11
11
  import os
12
12
  from pathlib import Path
13
- from typing import Iterable
14
13
 
15
14
 
16
15
  ENV_PATH_COMMENT = """\
@@ -16,7 +16,15 @@ import shutil
16
16
  from typing import Callable
17
17
 
18
18
 
19
- SUPPORTED_CLIENTS = ("claude-desktop", "claude-code", "cursor", "cline", "hermes")
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
- match client:
168
- case "claude-desktop":
169
- return emit_claude_desktop(resolver)
170
- case "claude-code":
171
- return emit_claude_code(resolver)
172
- case "cursor":
173
- return emit_cursor(resolver)
174
- case "cline":
175
- return emit_cline(resolver)
176
- case "hermes":
177
- return emit_hermes(resolver)
178
- case _:
179
- raise ValueError(
180
- f"unknown client {client!r}. Supported: {', '.join(SUPPORTED_CLIENTS)}"
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, Iterable
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 RAG engine, the browser dashboard, an orchestration CLI (`init` / `install` / `mcp-config`), hardened systemd units for rag + dashboard, and paste-ready configs for Claude Desktop/Code, Cursor, Cline, and Hermes.
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 `getbased-stack init --yes`, and starts rag + dashboard as systemd user services. [Read the script first](https://github.com/elkimek/get-based-site/blob/main/install.sh) if you're cautious.
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 RAG knowledge server (FastAPI + Qdrant + MiniLM/BGE)
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
- Then paste one line into your MCP client:
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
- sync GW getbased-rag ◀──────────────┘ getbased-mcp
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
 
@@ -0,0 +1,11 @@
1
+ getbased-mcp>=0.2.7
2
+ getbased-rag>=0.7.4
3
+ getbased-dashboard>=0.6.6
4
+
5
+ [full]
6
+ getbased-rag[full]>=0.7.4
7
+
8
+ [test]
9
+ pytest>=8.0
10
+ httpx>=0.27
11
+ pytest-timeout>=2.3
@@ -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, UnitManager
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 with EOF on token prompt must not wipe the stored
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 test_init_accepts_new_token(stack_home, fake_shell, monkeypatch):
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("getpass.getpass", lambda *a, **kw: "brand_new_token")
400
+ monkeypatch.setattr(
401
+ "getpass.getpass",
402
+ lambda *a, **kw: (_ for _ in ()).throw(AssertionError("getpass called")),
403
+ )
356
404
 
357
- _run(["init"])
358
- assert env_file.read_env_file()["GETBASED_TOKEN"] == "brand_new_token"
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 test_init_yes_flag_skips_all_prompts(stack_home, fake_shell, monkeypatch):
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) can't service prompts and the EOF fallback
393
- triggers a Python getpass echo warning that pollutes output.
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 == 0
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 "GETBASED_TOKEN and GETBASED_AGENT_CONTEXT_KEY" in out
409
- assert "getbased-stack set GETBASED_AGENT_CONTEXT_KEY" in out
410
- # Env file + units still land
411
- assert env_file.env_file_path().exists()
412
- assert (stack_home / "config" / "systemd" / "user" / "getbased-rag.service").exists()
413
-
414
-
415
- def test_init_yes_installs_units_without_asking(stack_home, fake_shell, monkeypatch):
416
- """Default for the install-units prompt is Yes, so --yes must also
417
- install + start. If this regressed to skip, install.sh would
418
- silently leave services off."""
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
- {"GETBASED_TOKEN": "keep_me", "GETBASED_STACK_MANAGED": "1"}
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
- log = mgr.uninstall()
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 == []
@@ -1,11 +0,0 @@
1
- getbased-mcp>=0.2.6
2
- getbased-rag>=0.7.2
3
- getbased-dashboard>=0.6.4
4
-
5
- [full]
6
- getbased-rag[full]>=0.7.2
7
-
8
- [test]
9
- pytest>=8.0
10
- httpx>=0.27
11
- pytest-timeout>=2.3