@neoline/hostbridge 0.2.0 → 0.2.2

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.
package/README.md CHANGED
@@ -121,9 +121,10 @@ Recommended agent flow:
121
121
 
122
122
  1. Call `hosts_list`.
123
123
  2. If the target host exists, call `session_open`.
124
- 3. If it does not exist and the user identifies a similar bastion-backed host, run `hostbridge setup <new_id> --from-host <source_id> --menu-selection <ip-or-name> --final-prompt <regex>`.
125
- 4. Call `hosts_list` again, then `session_open(<new_id>)`.
126
- 5. Do not inspect `~/.ssh`, old expect scripts, or shell snapshots unless the user explicitly asks or the setup command lacks required information.
124
+ 3. If `session_open` times out or fails, run `hostbridge check <host_id>` to verify the recorded login path, then retry or repair setup.
125
+ 4. If it does not exist and the user identifies a similar bastion-backed host, run `hostbridge setup <new_id> --from-host <source_id> --menu-selection <ip-or-name> --final-prompt <regex>`.
126
+ 5. Call `hosts_list` again, then `session_open(<new_id>)`.
127
+ 6. Do not inspect `~/.ssh`, old expect scripts, or shell snapshots unless the user explicitly asks or the setup command lacks required information.
127
128
 
128
129
  ### Encrypted login automation
129
130
 
@@ -327,11 +328,12 @@ See `SECURITY.md` and `docs/THREAT_MODEL.md` for the trust model and audited eve
327
328
  1. Call `hosts_list` and choose a configured host id.
328
329
  2. If the host is missing, ask for only the missing fields and use `hostbridge setup <host_id>` rather than manually editing config.
329
330
  3. Call `session_open` once for the chosen host.
330
- 4. Use `command_run` for quick commands such as `nvidia-smi`, `pwd`, `df -h`, or `tmux ls`.
331
- 5. Use `task_start` for long training jobs, downloads, builds, or scripts.
332
- 6. Poll `task_status` until `state` is `finished`; inspect `exit_code` and `log_tail`.
333
- 7. Call `task_stop` early if a job should be aborted.
334
- 8. Call `session_close` when finished, or `sessions_close_all` to tear down every session.
331
+ 4. Only if `session_open` times out or fails, run `hostbridge check <host_id>` and retry after fixing the login path.
332
+ 5. Use `command_run` for quick commands such as `nvidia-smi`, `pwd`, `df -h`, or `tmux ls`.
333
+ 6. Use `task_start` for long training jobs, downloads, builds, or scripts.
334
+ 7. Poll `task_status` until `state` is `finished`; inspect `exit_code` and `log_tail`.
335
+ 8. Call `task_stop` early if a job should be aborted.
336
+ 9. Call `session_close` when finished, or `sessions_close_all` to tear down every session.
335
337
 
336
338
  ## Security notes
337
339
 
@@ -347,4 +349,4 @@ See `CONTRIBUTING.md`. Run tests with `python -m pytest -q`. The full suite shou
347
349
 
348
350
  ## License
349
351
 
350
- MIT. See `LICENSE`.
352
+ MIT. See `LICENSE`.
package/bin/hostbridge.js CHANGED
@@ -19,6 +19,8 @@ if (!existsSync(moduleDir)) {
19
19
 
20
20
  const env = { ...process.env };
21
21
  env.PYTHONPATH = env.PYTHONPATH ? `${bundledSrc}${delimiter}${env.PYTHONPATH}` : bundledSrc;
22
+ env.HOSTBRIDGE_DEFAULT_MCP_COMMAND = env.HOSTBRIDGE_DEFAULT_MCP_COMMAND || 'npx';
23
+ env.HOSTBRIDGE_DEFAULT_MCP_ARGS = env.HOSTBRIDGE_DEFAULT_MCP_ARGS || JSON.stringify(['-y', '@neoline/hostbridge']);
22
24
 
23
25
  const child = spawn(pythonPath, ['-m', 'server_control_mcp', ...process.argv.slice(2)], {
24
26
  stdio: 'inherit',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@neoline/hostbridge",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "HostBridge MCP server for persistent allowlisted SSH and shell sessions.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hostbridge"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "HostBridge MCP server for persistent allowlisted SSH and shell sessions."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -6,7 +6,7 @@ import sys
6
6
  from typing import NoReturn
7
7
 
8
8
  __all__ = ["__version__", "ensure_runtime"]
9
- __version__ = "0.2.0"
9
+ __version__ = "0.2.2"
10
10
 
11
11
  # (import_name, pip_name) pairs for runtime dependency probing.
12
12
  _REQUIRED_DEPENDENCIES = (
@@ -35,7 +35,7 @@ Use HostBridge when the user asks to connect to, inspect, or operate an allowlis
35
35
 
36
36
  - First call `hosts_list` to see configured hosts; then use `session_open("<host_id>")`.
37
37
  - If the host is missing, ask only for the missing connection details and run `hostbridge setup <host_id>` instead of editing JSON by hand.
38
- - Validate host entries with `hostbridge check <host_id>` before relying on them.
38
+ - Do not run `hostbridge check` before every connection; only use `hostbridge check <host_id>` when `session_open` times out or fails.
39
39
  - Do not search shell history, SSH config, or old scripts unless the setup flow lacks required information or the user explicitly asks.
40
40
  - Keep long-running work in `task_start`/`task_status`; use `session_close` or `sessions_close_all` when done.
41
41
  {HOSTBRIDGE_GUIDANCE_END}
@@ -228,12 +228,23 @@ def _looks_like_shell_script(command: str, args: list[str]) -> bool:
228
228
 
229
229
 
230
230
  def _default_mcp_command_args() -> tuple[str, list[str]]:
231
+ env_command = os.environ.get("HOSTBRIDGE_DEFAULT_MCP_COMMAND")
232
+ if env_command:
233
+ raw_args = os.environ.get("HOSTBRIDGE_DEFAULT_MCP_ARGS", "[]")
234
+ try:
235
+ parsed_args = json.loads(raw_args)
236
+ except json.JSONDecodeError:
237
+ parsed_args = []
238
+ if not isinstance(parsed_args, list) or not all(isinstance(arg, str) for arg in parsed_args):
239
+ parsed_args = []
240
+ return env_command, parsed_args
241
+
231
242
  repo_root = Path(__file__).resolve().parents[2]
232
243
  run_server = repo_root / "run_server.py"
233
244
  if run_server.exists():
234
245
  return "python3", [str(run_server)]
235
246
  command = Path(sys.argv[0]).name or PUBLIC_NAME
236
- if command in (LEGACY_PUBLIC_NAME, LEGACY_PACKAGE_NAME):
247
+ if command in (LEGACY_PUBLIC_NAME, LEGACY_PACKAGE_NAME, "__main__.py"):
237
248
  command = PUBLIC_NAME
238
249
  return command, []
239
250