From 2ddd819a28cec3e1bd30a68b869d1ef6f112e85d Mon Sep 17 00:00:00 2001 From: ajspig <46900795+ajspig@users.noreply.github.com> Date: Wed, 26 Aug 2026 13:05:25 -0400 Subject: [PATCH] chore: bump honcho-cli to 0.1.4 (#1080) * feat(cli): fix API key typing Co-authored-by: Cursor * chore: bump honcho-cli to 0.1.4 Ship the masked --setup API key prompt plus the openai-compatible embedding base URL already on main. Co-authored-by: Cursor * feat(cli): check for newer version * docs: nit --------- Co-authored-by: Cursor --- docs/changelog/introduction.mdx | 12 +++- docs/v3/documentation/reference/cli.mdx | 3 + honcho-cli/CHANGELOG.md | 9 ++- honcho-cli/README.md | 1 + honcho-cli/pyproject.toml | 2 +- honcho-cli/src/honcho_cli/__init__.py | 2 +- honcho-cli/src/honcho_cli/_help.py | 2 + honcho-cli/src/honcho_cli/local/setup.py | 72 ++++++++++++++++++++--- honcho-cli/src/honcho_cli/main.py | 2 + honcho-cli/src/honcho_cli/update_check.py | 67 +++++++++++++++++++++ uv.lock | 2 +- 11 files changed, 162 insertions(+), 12 deletions(-) create mode 100644 honcho-cli/src/honcho_cli/update_check.py diff --git a/docs/changelog/introduction.mdx b/docs/changelog/introduction.mdx index 15003c7d..a152d16a 100644 --- a/docs/changelog/introduction.mdx +++ b/docs/changelog/introduction.mdx @@ -1170,7 +1170,17 @@ Welcome to the Honcho changelog! This section documents all notable changes to t [Honcho CLI](https://pypi.org/project/honcho-cli/) - + + ### Added + + - A TTY notice when a newer `honcho-cli` is on PyPI (`uv tool upgrade honcho-cli`). Skipped in JSON mode; disable with `HONCHO_NO_UPDATE_CHECK` + + ### Fixed + + - `--setup` for openai-compatible writes `EMBEDDING_MODEL_CONFIG__OVERRIDES__BASE_URL` into the profile `.env` alongside `LLM_OPENAI_BASE_URL` (#1068) + - `--setup` API key prompts echo `*` per character so a paste is visibly received instead of a blank getpass field + + ### Added - `honcho start`, `honcho stop`, and `honcho status` — run a personal Honcho stack in Docker (API, deriver, Postgres, Redis). Profiles live under `~/.honcho/profiles/`. First start pins `ghcr.io/plastic-labs/honcho:latest` by digest and copies the image `config.toml`. Optional `--setup basic` / `--setup advanced` wizard writes LLM overrides to `.env` (#1029) diff --git a/docs/v3/documentation/reference/cli.mdx b/docs/v3/documentation/reference/cli.mdx index 71a14305..9686199d 100644 --- a/docs/v3/documentation/reference/cli.mdx +++ b/docs/v3/documentation/reference/cli.mdx @@ -69,6 +69,7 @@ The CLI resolves config in this order: **flag → env var → config file → de | Peer | — | `HONCHO_PEER_ID` | `-p` / `--peer` | No | | Session | — | `HONCHO_SESSION_ID` | `-s` / `--session` | No | | JSON output | — | `HONCHO_JSON` | `--json` | No | +| Update nag | — | `HONCHO_NO_UPDATE_CHECK` | — | No | | Local stack | — | `HONCHO_PROFILE` | `--profile` | No | ### Persisted config @@ -124,6 +125,8 @@ Every command adapts its output to the context: - **Piped or redirected** — JSON automatically (detected via `isatty`). - **`--json` flag / `HONCHO_JSON=1`** — force JSON regardless of terminal. +Interactive sessions may print a one-line upgrade hint on stderr at most once a day when a newer `honcho-cli` is on PyPI (`uv tool upgrade honcho-cli`). JSON/piped output skips it; set `HONCHO_NO_UPDATE_CHECK=1` to disable it. + Collection commands emit JSON arrays; single-resource commands emit JSON objects. Errors are always structured: ```json diff --git a/honcho-cli/CHANGELOG.md b/honcho-cli/CHANGELOG.md index 1a1b50e3..80b7e5ef 100644 --- a/honcho-cli/CHANGELOG.md +++ b/honcho-cli/CHANGELOG.md @@ -7,9 +7,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +## [0.1.4] - 2026-08-26 + +### Added + +- A TTY notice when a newer `honcho-cli` is on PyPI (`uv tool upgrade honcho-cli`). Skipped in JSON mode; disable with `HONCHO_NO_UPDATE_CHECK` + ### Fixed -- `--setup` for openai-compatible writes `EMBEDDING_MODEL_CONFIG__OVERRIDES__BASE_URL` into the profile `.env` alongside `LLM_OPENAI_BASE_URL` +- `--setup` for openai-compatible writes `EMBEDDING_MODEL_CONFIG__OVERRIDES__BASE_URL` into the profile `.env` alongside `LLM_OPENAI_BASE_URL` (#1068) +- `--setup` API key prompts echo `*` per character so a paste is visibly received instead of a blank getpass field ## [0.1.3] - 2026-08-25 diff --git a/honcho-cli/README.md b/honcho-cli/README.md index 06898b5c..0a01e647 100644 --- a/honcho-cli/README.md +++ b/honcho-cli/README.md @@ -185,6 +185,7 @@ Precedence (highest first): **flag → env var → config file → default**. | `HONCHO_PEER_ID` | `-p` / `--peer` | Peer scope | | `HONCHO_SESSION_ID` | `-s` / `--session` | Session scope | | `HONCHO_JSON` | `--json` | Force JSON output (`1` / `true`) | +| `HONCHO_NO_UPDATE_CHECK` | — | Disable the once-a-day upgrade notice (`1` / `true`) | | `HONCHO_PROFILE` | `--profile` (start/stop/status) | Local stack profile (default: `local`) | | `LLM_OPENAI_API_KEY` | — | Provider key for `honcho start` (also `LLM_ANTHROPIC_API_KEY`, `LLM_GEMINI_API_KEY`) | diff --git a/honcho-cli/pyproject.toml b/honcho-cli/pyproject.toml index f2fa4d0c..25c376cb 100644 --- a/honcho-cli/pyproject.toml +++ b/honcho-cli/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "honcho-cli" -version = "0.1.3" +version = "0.1.4" description = "A terminal for Honcho — memory that reasons." readme = "README.md" requires-python = ">=3.11" diff --git a/honcho-cli/src/honcho_cli/__init__.py b/honcho-cli/src/honcho_cli/__init__.py index 5cbd28b3..33860e37 100644 --- a/honcho-cli/src/honcho_cli/__init__.py +++ b/honcho-cli/src/honcho_cli/__init__.py @@ -1,3 +1,3 @@ """Honcho CLI — a terminal for Honcho.""" -__version__ = "0.1.3" +__version__ = "0.1.4" diff --git a/honcho-cli/src/honcho_cli/_help.py b/honcho-cli/src/honcho_cli/_help.py index 936e26b7..dbb4f7cd 100644 --- a/honcho-cli/src/honcho_cli/_help.py +++ b/honcho-cli/src/honcho_cli/_help.py @@ -24,6 +24,7 @@ from typer.core import TyperGroup from honcho_cli import __version__ from honcho_cli.branding import BANNER, BRAND from honcho_cli.output import use_json +from honcho_cli.update_check import maybe_print_update_nag # Theme Typer's rich help renderer. Module-level side effect limited to @@ -113,6 +114,7 @@ def print_welcome(console: Console) -> None: console.print(_welcome_panel("memory", memory_rows)) console.print(_welcome_panel("options", option_rows)) console.print() + maybe_print_update_nag() class HonchoTyperGroup(TyperGroup): diff --git a/honcho-cli/src/honcho_cli/local/setup.py b/honcho-cli/src/honcho_cli/local/setup.py index 242f47d6..5922dfd4 100644 --- a/honcho-cli/src/honcho_cli/local/setup.py +++ b/honcho-cli/src/honcho_cli/local/setup.py @@ -7,6 +7,7 @@ only — the start command rejects ``--setup`` in JSON / non-TTY mode. from __future__ import annotations +import sys import tomllib from dataclasses import dataclass from pathlib import Path @@ -445,13 +446,7 @@ def _prompt_secret(label: str, current: str | None) -> str: if choice != "2": return current _console.print(f" [dim]{label}[/dim]") - raw = typer.prompt( - f" {label}", - default="", - show_default=False, - hide_input=True, - prompt_suffix=": ", - ).strip() + raw = _prompt_masked(f" {label}: ").strip() if not raw or is_placeholder_key(raw): print_error( "MISSING_LLM_KEY", @@ -461,6 +456,69 @@ def _prompt_secret(label: str, current: str | None) -> str: return raw +def _prompt_masked(prompt: str) -> str: + """Read a secret, echoing ``*`` per character so paste is visibly received.""" + stream = sys.stderr + stream.write(prompt) + stream.flush() + chars: list[str] = [] + + def _write(text: str) -> None: + stream.write(text) + stream.flush() + + def _feed(ch: str) -> bool: + """Return True when input is complete.""" + if not ch or ch in ("\n", "\r", "\x04"): + _write("\n") + return True + if ch in ("\x7f", "\x08"): + if chars: + chars.pop() + _write("\b \b") + return False + if ch == "\x1b": + return False + if ch.isprintable(): + chars.append(ch) + _write("*") + return False + + if sys.platform == "win32": + import msvcrt + + while True: + ch = msvcrt.getwch() + if ch in ("\x00", "\xe0"): + msvcrt.getwch() + continue + if _feed(ch): + return "".join(chars) + + import termios + import tty + + fd = sys.stdin.fileno() + old = termios.tcgetattr(fd) + try: + tty.setcbreak(fd) + while True: + ch = sys.stdin.read(1) + if ch == "\x1b": + nxt = sys.stdin.read(1) + if nxt == "[": + while True: + seq = sys.stdin.read(1) + if not seq or "@" <= seq <= "~": + break + continue + if _feed(ch): + return "".join(chars) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old) + return "".join(chars) + + def _redact(key: str) -> str: if len(key) <= 4: return "***" diff --git a/honcho-cli/src/honcho_cli/main.py b/honcho-cli/src/honcho_cli/main.py index 8a9c16f6..d2282c81 100644 --- a/honcho-cli/src/honcho_cli/main.py +++ b/honcho-cli/src/honcho_cli/main.py @@ -15,6 +15,7 @@ from honcho_cli import __version__ from honcho_cli._help import HonchoTyperGroup, print_welcome from honcho_cli.branding import BANNER from honcho_cli.output import set_json_mode +from honcho_cli.update_check import maybe_print_update_nag app = typer.Typer( @@ -60,6 +61,7 @@ def main( if ctx.invoked_subcommand is None: print_welcome(Console()) raise typer.Exit() + maybe_print_update_nag() # Register top-level commands diff --git a/honcho-cli/src/honcho_cli/update_check.py b/honcho-cli/src/honcho_cli/update_check.py new file mode 100644 index 00000000..97b433d1 --- /dev/null +++ b/honcho-cli/src/honcho_cli/update_check.py @@ -0,0 +1,67 @@ +"""Once-a-day stderr notice when a newer honcho-cli is on PyPI. + +Fail-open: any error is swallowed. Cache is ``update-check.json`` beside +config, not ``config.json``. +""" + +from __future__ import annotations + +import json +import os +import sys +import time + +import httpx + +from honcho_cli import __version__ +from honcho_cli.branding import ICON_RUN +from honcho_cli.config import _config_dir +from honcho_cli.output import console, use_json + +_INTERVAL_S = 24 * 60 * 60 +_PYPI_URL = "https://pypi.org/pypi/honcho-cli/json" + + +def maybe_print_update_nag() -> None: + if use_json() or "--json" in sys.argv: + return + if os.environ.get("HONCHO_NO_UPDATE_CHECK", "").lower() in ("1", "true"): + return + try: + path = _config_dir() / "update-check.json" + now = time.time() + try: + cache = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError, UnicodeDecodeError): + cache = {} + if isinstance(cache, dict) and now - float(cache.get("t") or 0) < _INTERVAL_S: + return + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps({"t": now}), encoding="utf-8") + latest = httpx.get(_PYPI_URL, timeout=1.0).json()["info"]["version"] + if not isinstance(latest, str) or not _is_newer(latest, __version__): + return + console.print(f" {ICON_RUN} honcho-cli {latest} is available (you have {__version__})") + console.print(" [dim]uv tool upgrade honcho-cli[/dim]") + except Exception: + return + + +def _is_newer(latest: str, current: str) -> bool: + def parts(version: str) -> tuple[int, ...]: + out: list[int] = [] + for segment in version.lstrip("v").split("."): + num = "" + for ch in segment: + if ch.isdigit(): + num += ch + else: + break + if not num: + break + out.append(int(num)) + return tuple(out) or (0,) + + a, b = parts(latest), parts(current) + n = max(len(a), len(b)) + return a + (0,) * (n - len(a)) > b + (0,) * (n - len(b)) diff --git a/uv.lock b/uv.lock index c92534ac..76f28e20 100644 --- a/uv.lock +++ b/uv.lock @@ -1170,7 +1170,7 @@ dev = [{ name = "ruff", specifier = ">=0.11.13" }] [[package]] name = "honcho-cli" -version = "0.1.3" +version = "0.1.4" source = { editable = "honcho-cli" } dependencies = [ { name = "click" },