Skip to content

Add make doctor + secure dashboard bind under Tailscale + install UX fixes - #213

Draft
david-hummingbot wants to merge 8 commits into
mainfrom
feature/doctor-command-install-ux
Draft

Add make doctor + secure dashboard bind under Tailscale + install UX fixes#213
david-hummingbot wants to merge 8 commits into
mainfrom
feature/doctor-command-install-ux

Conversation

@david-hummingbot

Copy link
Copy Markdown
Contributor

Summary

  • make doctor (condor/doctor.py): checks dependencies (uv/tmux/node/npm/tsc), .env/config.yml sanity, AI model readiness, whether the web dashboard port is exposed on all interfaces vs. loopback, and connectivity+auth to every Hummingbot API server in config.yml. Non-zero exit on real failures; wired into make install as a non-fatal final step.
  • Dashboard no longer sits on 0.0.0.0 under Tailscale: when USE_TAILSCALE=true, the dashboard binds 127.0.0.1 and confirms a tailscale serve proxy (utils/tailscale.py), mirroring the pattern hummingbot-api's own Gateway/dev-mode already use. Fails loud (logs an error) instead of silently falling back to a public bind if tailscale serve can't be confirmed.
  • Install UX: Step 2 of setup-environment.sh now asks before it pays the uv sync cost, so skipping the AI model picker is instant, and shows a real cost estimate ("~250MB first run, 1-3 min") when the user opts in. Step 6's stale "Next steps" copy is fixed to describe what make install actually still does (frontend deps + Chrome setup) and points at make doctor.
  • uv.lock resynced to the hummingbot-api-client==1.5.7 pin already declared in pyproject.toml (lockfile had drifted out of sync) — incidental, not part of the feature work.

Test plan

  • uv run python -m condor.doctor / make doctor — runs correctly standalone, correct exit code on real failures (e.g. hummingbot-api not running)
  • make -n install — confirms sequencing (setupuv sync --dev → frontend install → chrome setup → doctor)
  • bash -n setup-environment.sh — syntax check passes
  • black --check / isort --check clean on all touched files
  • uv run pytest — 1415 passed, 1 pre-existing unrelated failure (stale assistants/ dir check, untouched by this PR)
  • Manual smoke test of tailscale serve against a live tailnet (not available in the dev environment this was built in) — recommend verifying before merge/deploy

🤖 Generated with Claude Code

david-hummingbot added a commit that referenced this pull request Aug 21, 2026
Resolves PR #213's conflict with main (339 files changed there since this
branch diverged -- telemetry, DEX trading UI, and a large batch of new
tests, among others). main.py and utils/config.py merged cleanly on their
own; the only real conflict was uv.lock, which was regenerated fresh
against the merged pyproject.toml with `uv lock` rather than hand-resolved
(pyproject.toml itself merged without conflict -- both sides already
agreed on hummingbot-api-client==1.5.7).

Verified post-merge: full pytest suite (2268 passed, same one
pre-existing unrelated failure -- a stale local `assistants/` dir check --
as before this branch touched anything), and this branch's own additions
(condor/doctor.py, condor/setup_llm.py's bridge auto-install, USE_TAILSCALE
wiring in main.py/utils/config.py) all still import and run correctly.
david-hummingbot and others added 8 commits August 22, 2026 01:09
…fixes

- condor/doctor.py: verifies dependencies, .env/config.yml, AI model
  readiness, dashboard port exposure, and Hummingbot API connectivity —
  wired into `make install` and available standalone via `make doctor`.
- main.py/utils/tailscale.py: bind the web dashboard to 127.0.0.1 (with a
  `tailscale serve` proxy) instead of 0.0.0.0 when USE_TAILSCALE is set,
  matching the pattern hummingbot-api's Gateway already uses.
- setup-environment.sh: Step 2 no longer pays the uv sync cost before the
  user can skip picking a model, and shows a cost estimate when they don't;
  Step 6's stale "Next steps" copy now describes what `make install`
  actually still does.
- uv.lock: resync to the hummingbot-api-client==1.5.7 pin already declared
  in pyproject.toml (lockfile had drifted out of sync).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ntil model choice

- Makefile: doctor target now sources ~/.nvm/nvm.sh before running
  condor.doctor, matching the pattern install/build-frontend already use.
  Without it, shutil.which("node"/"npm"/"tsc") and the AI-model readiness
  probe (which shells out to `npm root -g`) cannot see nvm-installed
  Node, so `make doctor` -- including the non-fatal check at the end of
  `make install` -- reported node/npm/typescript as missing and the AI
  model as not installed right after a clean install that actually
  succeeded.

- setup-environment.sh: move the global
  `npm install -g @agentclientprotocol/claude-agent-acp` install out of
  Step 0 (Dependencies), which ran it unconditionally before the user had
  picked a model, into a new step right after Step 2 (AI Model). It now
  only runs if the resolved default agent actually uses the
  claude-agent-acp bridge -- checked via the same
  condor.setup_llm.current_default/base_of + condor.acp.client.ACP_COMMANDS
  mapping the bot itself uses at runtime, so it can never disagree with
  what will actually run. Users who pick Codex/Gemini/Copilot/etc. (or an
  Ollama/custom model) no longer get an unused global npm package
  installed on their machine.

Verified in a clean container: `make install` -> `make doctor` now shows
node/npm/tsc detected and the AI model correctly read as installed; full
pytest suite (1410 passed, 6 skipped) still green.
…sconnect

Two related install-flow fixes:

- condor/setup_llm.py: selecting a model whose CLI bridge isn't installed
  (e.g. Gemini CLI, GitHub Copilot CLI) used to just say "isn't ready" and
  reprompt, leaving the user to go run `npm install -g ...` themselves and
  come back. It now runs that install command right there (reusing
  readiness.install_command against ACP_COMMANDS, so it can never disagree
  with what the menu row displays), streams real npm output, re-probes, and
  proceeds with the pick on success -- or reprompts with a clear failure
  reason otherwise. main() applies the same check to the final effective
  default even when the picker is skipped, so a fresh install's recommended
  claude-agent-acp default still gets installed -- but never for `--status`/
  non-tty, which promises to change nothing.

- setup-environment.sh: this made the separate "Install Claude ACP (only if
  the chosen model needs it)" block (from the nvm/ACP-defer patch) redundant
  and worse, the actual source of the reported disconnect -- a user would
  answer "pick a model now?", watch the Python env sync, pick a model, get a
  completion report from setup_llm.py, and then get bounced into a second,
  unlabeled install pass for a check the picker itself now already covers
  more generally (any bridge, not just Claude's). Removed it: the Python env
  sync (before the menu appears) and the picked model's install (inline, the
  moment it's chosen) now happen as one continuous flow in a single process,
  with nothing tacked on afterward.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e of it

uv's own venv-creation/install output (Using CPython..., Creating virtual
environment, the wheels progress bar, Installed N packages...) was showing
up sandwiched between "Pick an AI model now? [Y/n]" and the actual "Which
AI model should Condor use" menu -- both python invocations of the same
step, so it read as the prompt getting interrupted mid-conversation rather
than as one continuous flow.

Moved the sync (`uv run python -c "pass"`, same lockfile-driven sync `uv
run python -m condor.setup_llm` would trigger anyway) to the top of Step 2,
before the Y/n question, and made it unconditional -- both branches below
need the venv warm regardless (picking a model runs the wizard directly,
skipping still needs `--status` for the no-tty fallback). Verified: the
picker invocation immediately after the warm-up produces zero sync output,
going straight to the menu.

Trade-off: this reintroduces the sync cost on a straight "skip" (previously
made instant by gating the sync behind the Y/n itself) -- accepted in favor
of removing the disconnect, since in the common path (`make install`) the
sync happens moments later regardless via `uv sync --dev`; the instant-skip
win only mattered for a standalone `make setup` rerun.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…oise

- condor/doctor.py: check_hummingbot_api() called cm.get_client(name) and
  discarded the result. get_client() is written for the long-running bot
  process, which keeps the client (and its aiohttp session) cached for
  reuse -- but doctor is a one-shot script that exits right after the
  check, so nothing ever closed that session. aiohttp then printed an
  "Unclosed client session" / "Unclosed connector" warning on interpreter
  shutdown, which read like a real error even though the check itself
  passed. Added _probe_and_close() to close the client the moment the
  check is done.

- condor/doctor.py: the report was plain monochrome text. Added color
  (green/yellow/red badges + bold section headers) matching the palette
  setup-environment.sh already uses elsewhere in the install flow, gated
  on sys.stdout.isatty() and NO_COLOR so piped/CI output stays plain.

- Makefile: set MAKEFLAGS += --no-print-directory so the recursive
  $(MAKE) doctor call inside install doesn't print an "Entering/Leaving
  directory" line around it.

Verified: full pytest suite (1417 passed, 6 skipped) green; make doctor
piped shows no escape codes and no more aiohttp warnings; under a real
tty the report renders in color.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Bordered header/footer matching setup-environment.sh's own banner width
(46 chars) and bold style, so the two read as one design when make install
runs them back to back. Each section gets a header + a light divider
instead of just a trailing colon, check details are dimmed so the
glyph+name lead the eye, and the closing line is now a real tally
(✓ passed / ! warnings / ✗ failed counts, always all three) with the
footer border itself colored red/yellow/green by the worst outcome.

Also adds tests/test_doctor.py -- this file had zero coverage before.
Covers the new _tally() (counts + exit code) and the two existing pure
classifiers (_is_public_bind, _connection_hint) it was easy to pick up
alongside the rendering work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
main moved the LLM-provider readiness/options/openrouter modules from
handlers.agents.* into condor.llm.* (ARCH-190) while this branch was in
flight, with compat shims keeping the old import paths working. The rebase
onto main already picked up the new paths in condor/setup_llm.py's own
imports (clean 3-way merge); this finishes the job in the few spots that
still pointed at the old names by prose or by import, since new code is
meant to use the new homes:

- condor/doctor.py: import the readiness module from condor.llm directly
  instead of through the handlers.agents shim.
- condor/setup_llm.py: two docstring references (_default_agent(),
  install_command()) still named the pre-move module paths.
- tests/test_setup_llm.py: import AGENT_OPTIONS/readiness/openrouter_models
  from condor.llm.* instead of handlers.agents.*.
- main.py: _web_server_config()'s docstring only mentioned local mode as a
  reason WEB_HOST binds loopback; added Tailscale, the other reason this
  branch introduced.

No behavior change -- the shims meant all of this already worked; this is
purely pointing at the modules' real, current homes. Full pytest suite
still 2478 passed (same one pre-existing unrelated failure as before this
branch touched anything).
@david-hummingbot
david-hummingbot force-pushed the feature/doctor-command-install-ux branch from c67f22c to 847263b Compare August 21, 2026 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant