chore: bump honcho-cli to 0.1.4 (#1080)
* feat(cli): fix API key typing Co-authored-by: Cursor <cursoragent@cursor.com> * 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 <cursoragent@cursor.com> * feat(cli): check for newer version * docs: nit --------- Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
dea2917fa9
commit
2ddd819a28
|
|
@ -1170,7 +1170,17 @@ Welcome to the Honcho changelog! This section documents all notable changes to t
|
|||
</Tab>
|
||||
<Tab title="Honcho CLI">
|
||||
[Honcho CLI](https://pypi.org/project/honcho-cli/)
|
||||
<Update label="v0.1.3 (Current)">
|
||||
<Update label="v0.1.4 (Current)">
|
||||
### 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
|
||||
</Update>
|
||||
<Update label="v0.1.3">
|
||||
### 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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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`) |
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
"""Honcho CLI — a terminal for Honcho."""
|
||||
|
||||
__version__ = "0.1.3"
|
||||
__version__ = "0.1.4"
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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 "***"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
Loading…
Reference in New Issue