honcho-cli 0.1.3__tar.gz → 0.1.4__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 (46) hide show
  1. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/CHANGELOG.md +11 -0
  2. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/PKG-INFO +9 -9
  3. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/README.md +8 -8
  4. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/pyproject.toml +1 -1
  5. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/__init__.py +1 -1
  6. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/_help.py +17 -8
  7. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/setup.py +77 -15
  8. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/main.py +2 -0
  9. honcho_cli-0.1.4/src/honcho_cli/update_check.py +67 -0
  10. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_setup.py +28 -0
  11. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/.gitignore +0 -0
  12. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/scripts/generate_cli_docs.py +0 -0
  13. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/branding.py +0 -0
  14. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/__init__.py +0 -0
  15. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/conclusion.py +0 -0
  16. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/config_cmd.py +0 -0
  17. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/message.py +0 -0
  18. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/peer.py +0 -0
  19. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/session.py +0 -0
  20. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/setup.py +0 -0
  21. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/stack.py +0 -0
  22. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/workspace.py +0 -0
  23. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/common.py +0 -0
  24. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/config.py +0 -0
  25. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/__init__.py +0 -0
  26. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/docker.py +0 -0
  27. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/env.py +0 -0
  28. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/health.py +0 -0
  29. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/profile.py +0 -0
  30. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/templates/__init__.py +0 -0
  31. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/templates/docker-compose.yml +0 -0
  32. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/templates/init.sql +0 -0
  33. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/oauth.py +0 -0
  34. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/output.py +0 -0
  35. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/validation.py +0 -0
  36. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/__init__.py +0 -0
  37. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/conftest.py +0 -0
  38. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_commands.py +0 -0
  39. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_common.py +0 -0
  40. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_config.py +0 -0
  41. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_local.py +0 -0
  42. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_oauth.py +0 -0
  43. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_output.py +0 -0
  44. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_start.py +0 -0
  45. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_validation.py +0 -0
  46. {honcho_cli-0.1.3 → honcho_cli-0.1.4}/uv.lock +0 -0
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.4] - 2026-08-26
11
+
12
+ ### Added
13
+
14
+ - 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`
15
+
16
+ ### Fixed
17
+
18
+ - `--setup` for openai-compatible writes `EMBEDDING_MODEL_CONFIG__OVERRIDES__BASE_URL` into the profile `.env` alongside `LLM_OPENAI_BASE_URL` (#1068)
19
+ - `--setup` API key prompts echo `*` per character so a paste is visibly received instead of a blank getpass field
20
+
10
21
  ## [0.1.3] - 2026-08-25
11
22
 
12
23
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: honcho-cli
3
- Version: 0.1.3
3
+ Version: 0.1.4
4
4
  Summary: A terminal for Honcho — memory that reasons.
5
5
  Project-URL: Homepage, https://github.com/plastic-labs/honcho
6
6
  Project-URL: Repository, https://github.com/plastic-labs/honcho
@@ -47,19 +47,19 @@ uv tool install honcho-cli
47
47
  ## Quick Start
48
48
 
49
49
  ```bash
50
- honcho init # confirm/set apiKey + Honcho URL in ~/.honcho/config.json
51
- honcho start # optional: local API + deriver + Postgres + Redis (Docker)
52
- honcho doctor # verify your config + connectivity
53
- honcho # show banner + command list
50
+ honcho init # confirm/set apiKey + Honcho URL in ~/.honcho/config.json
51
+ honcho start --setup basic # local stack: LLM provider key + Docker
52
+ honcho doctor # verify config + connectivity
53
+ honcho # show banner + command list
54
54
  ```
55
55
 
56
- `honcho init` reads `apiKey` and `environmentUrl` from the top-level of `~/.honcho/config.json` (the same file other Honcho tools — plugins, host integrations — share). If both are present, it confirms them with you; if either is missing (or you decline), it prompts for the missing value(s) and writes them back. Host-specific entries under `hosts` are left untouched.
56
+ `honcho init` writes `apiKey` and `environmentUrl` to the top-level of `~/.honcho/config.json` (the same file other Honcho tools — plugins, host integrations — share) so the CLI can call a Honcho server. If both are present, it confirms them with you; if either is missing (or you decline), it prompts and writes them back. Host-specific entries under `hosts` are left untouched. It does **not** set the LLM provider key the local deriver needs — that is `honcho start --setup` (or `LLM_*_API_KEY` in the environment).
57
57
 
58
58
  Per-command scoping (workspace / peer / session) is handled via `-w` / `-p` / `-s` flags or `HONCHO_*` env vars — not persisted as CLI defaults.
59
59
 
60
60
  ### Local stack
61
61
 
62
- `honcho start` runs a personal Honcho server on your machine (API, deriver, Postgres, Redis) via Docker. Inference is cloud-side: set `LLM_OPENAI_API_KEY`, `LLM_ANTHROPIC_API_KEY`, or `LLM_GEMINI_API_KEY` (env overrides `config.toml`). Stack files live under `~/.honcho/profiles/local/` and are not committed to a project.
62
+ `honcho start --setup basic` runs a personal Honcho server on your machine (API, deriver, Postgres, Redis) via Docker. The wizard writes the LLM provider key into the profile `.env` — `honcho init` cannot do this; its `apiKey` is for calling a Honcho server, not for deriver/dialectic inference. You can also pass `LLM_OPENAI_API_KEY`, `LLM_ANTHROPIC_API_KEY`, or `LLM_GEMINI_API_KEY` in the environment and skip `--setup`. Stack files live under `~/.honcho/profiles/local/` and are not committed to a project.
63
63
 
64
64
  On first start, the CLI pulls `ghcr.io/plastic-labs/honcho:latest` and **pins that digest** in `profile.json`, then copies the image's `config.toml.example` to `config.toml` in the same directory. `honcho start` never overwrites `config.toml` after that — including when you re-pin the image. Delete the file yourself if you want a fresh copy from a new image.
65
65
 
@@ -74,7 +74,6 @@ HONCHO_BASE_URL=http://127.0.0.1:8000 honcho workspace list
74
74
  To make local the default, run `honcho init --base-url http://127.0.0.1:8000`.
75
75
 
76
76
  ```bash
77
- LLM_OPENAI_API_KEY=sk-... honcho start
78
77
  honcho start --setup basic
79
78
  honcho start --setup advanced
80
79
  honcho status
@@ -88,7 +87,7 @@ honcho stop --wipe # also delete volumes
88
87
 
89
88
  | Command | Description |
90
89
  |---------|-------------|
91
- | `honcho init` | Confirm/set `apiKey` + `environmentUrl` in `~/.honcho/config.json` |
90
+ | `honcho init` | Confirm/set `apiKey` + `environmentUrl` in `~/.honcho/config.json`. |
92
91
  | `honcho start` | Start a local Honcho stack (API, deriver, Postgres, Redis). Requires Docker and a cloud LLM key. `--setup basic` / `--setup advanced` runs an interactive config wizard (TTY only). Does not change `environmentUrl`. |
93
92
  | `honcho stop` | Stop the local stack. `--wipe` also deletes volumes. |
94
93
  | `honcho status` | Show every local stack (or `--profile` for one). |
@@ -211,6 +210,7 @@ Precedence (highest first): **flag → env var → config file → default**.
211
210
  | `HONCHO_PEER_ID` | `-p` / `--peer` | Peer scope |
212
211
  | `HONCHO_SESSION_ID` | `-s` / `--session` | Session scope |
213
212
  | `HONCHO_JSON` | `--json` | Force JSON output (`1` / `true`) |
213
+ | `HONCHO_NO_UPDATE_CHECK` | — | Disable the once-a-day upgrade notice (`1` / `true`) |
214
214
  | `HONCHO_PROFILE` | `--profile` (start/stop/status) | Local stack profile (default: `local`) |
215
215
  | `LLM_OPENAI_API_KEY` | — | Provider key for `honcho start` (also `LLM_ANTHROPIC_API_KEY`, `LLM_GEMINI_API_KEY`) |
216
216
 
@@ -22,19 +22,19 @@ uv tool install honcho-cli
22
22
  ## Quick Start
23
23
 
24
24
  ```bash
25
- honcho init # confirm/set apiKey + Honcho URL in ~/.honcho/config.json
26
- honcho start # optional: local API + deriver + Postgres + Redis (Docker)
27
- honcho doctor # verify your config + connectivity
28
- honcho # show banner + command list
25
+ honcho init # confirm/set apiKey + Honcho URL in ~/.honcho/config.json
26
+ honcho start --setup basic # local stack: LLM provider key + Docker
27
+ honcho doctor # verify config + connectivity
28
+ honcho # show banner + command list
29
29
  ```
30
30
 
31
- `honcho init` reads `apiKey` and `environmentUrl` from the top-level of `~/.honcho/config.json` (the same file other Honcho tools — plugins, host integrations — share). If both are present, it confirms them with you; if either is missing (or you decline), it prompts for the missing value(s) and writes them back. Host-specific entries under `hosts` are left untouched.
31
+ `honcho init` writes `apiKey` and `environmentUrl` to the top-level of `~/.honcho/config.json` (the same file other Honcho tools — plugins, host integrations — share) so the CLI can call a Honcho server. If both are present, it confirms them with you; if either is missing (or you decline), it prompts and writes them back. Host-specific entries under `hosts` are left untouched. It does **not** set the LLM provider key the local deriver needs — that is `honcho start --setup` (or `LLM_*_API_KEY` in the environment).
32
32
 
33
33
  Per-command scoping (workspace / peer / session) is handled via `-w` / `-p` / `-s` flags or `HONCHO_*` env vars — not persisted as CLI defaults.
34
34
 
35
35
  ### Local stack
36
36
 
37
- `honcho start` runs a personal Honcho server on your machine (API, deriver, Postgres, Redis) via Docker. Inference is cloud-side: set `LLM_OPENAI_API_KEY`, `LLM_ANTHROPIC_API_KEY`, or `LLM_GEMINI_API_KEY` (env overrides `config.toml`). Stack files live under `~/.honcho/profiles/local/` and are not committed to a project.
37
+ `honcho start --setup basic` runs a personal Honcho server on your machine (API, deriver, Postgres, Redis) via Docker. The wizard writes the LLM provider key into the profile `.env` — `honcho init` cannot do this; its `apiKey` is for calling a Honcho server, not for deriver/dialectic inference. You can also pass `LLM_OPENAI_API_KEY`, `LLM_ANTHROPIC_API_KEY`, or `LLM_GEMINI_API_KEY` in the environment and skip `--setup`. Stack files live under `~/.honcho/profiles/local/` and are not committed to a project.
38
38
 
39
39
  On first start, the CLI pulls `ghcr.io/plastic-labs/honcho:latest` and **pins that digest** in `profile.json`, then copies the image's `config.toml.example` to `config.toml` in the same directory. `honcho start` never overwrites `config.toml` after that — including when you re-pin the image. Delete the file yourself if you want a fresh copy from a new image.
40
40
 
@@ -49,7 +49,6 @@ HONCHO_BASE_URL=http://127.0.0.1:8000 honcho workspace list
49
49
  To make local the default, run `honcho init --base-url http://127.0.0.1:8000`.
50
50
 
51
51
  ```bash
52
- LLM_OPENAI_API_KEY=sk-... honcho start
53
52
  honcho start --setup basic
54
53
  honcho start --setup advanced
55
54
  honcho status
@@ -63,7 +62,7 @@ honcho stop --wipe # also delete volumes
63
62
 
64
63
  | Command | Description |
65
64
  |---------|-------------|
66
- | `honcho init` | Confirm/set `apiKey` + `environmentUrl` in `~/.honcho/config.json` |
65
+ | `honcho init` | Confirm/set `apiKey` + `environmentUrl` in `~/.honcho/config.json`. |
67
66
  | `honcho start` | Start a local Honcho stack (API, deriver, Postgres, Redis). Requires Docker and a cloud LLM key. `--setup basic` / `--setup advanced` runs an interactive config wizard (TTY only). Does not change `environmentUrl`. |
68
67
  | `honcho stop` | Stop the local stack. `--wipe` also deletes volumes. |
69
68
  | `honcho status` | Show every local stack (or `--profile` for one). |
@@ -186,6 +185,7 @@ Precedence (highest first): **flag → env var → config file → default**.
186
185
  | `HONCHO_PEER_ID` | `-p` / `--peer` | Peer scope |
187
186
  | `HONCHO_SESSION_ID` | `-s` / `--session` | Session scope |
188
187
  | `HONCHO_JSON` | `--json` | Force JSON output (`1` / `true`) |
188
+ | `HONCHO_NO_UPDATE_CHECK` | — | Disable the once-a-day upgrade notice (`1` / `true`) |
189
189
  | `HONCHO_PROFILE` | `--profile` (start/stop/status) | Local stack profile (default: `local`) |
190
190
  | `LLM_OPENAI_API_KEY` | — | Provider key for `honcho start` (also `LLM_ANTHROPIC_API_KEY`, `LLM_GEMINI_API_KEY`) |
191
191
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "honcho-cli"
3
- version = "0.1.3"
3
+ version = "0.1.4"
4
4
  description = "A terminal for Honcho — memory that reasons."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  """Honcho CLI — a terminal for Honcho."""
2
2
 
3
- __version__ = "0.1.3"
3
+ __version__ = "0.1.4"
@@ -24,6 +24,7 @@ from typer.core import TyperGroup
24
24
  from honcho_cli import __version__
25
25
  from honcho_cli.branding import BANNER, BRAND
26
26
  from honcho_cli.output import use_json
27
+ from honcho_cli.update_check import maybe_print_update_nag
27
28
 
28
29
 
29
30
  # Theme Typer's rich help renderer. Module-level side effect limited to
@@ -59,16 +60,21 @@ def _welcome_panel(title: str, rows: list[tuple[str, str]]) -> Panel:
59
60
 
60
61
 
61
62
  def print_welcome(console: Console) -> None:
62
- """Render the curated 3-panel welcome (banner + getting started / memory / commands)."""
63
+ """Render the curated welcome (banner + getting started / local stack / commands / memory)."""
63
64
  if use_json():
64
65
  return
65
66
  console.print(f"[bold {BRAND}]{BANNER}[/bold {BRAND}]")
66
67
  console.print(f" [dim]v{__version__}[/dim]\n", highlight=False)
67
68
 
68
69
  start_rows = [
69
- ("honcho init", "configure API key and server URL"),
70
- ("honcho start", "run a local Honcho stack (Docker)"),
71
- ("honcho doctor", "verify connection and workspace health"),
70
+ ("honcho init", "configure API key and server URL"),
71
+ ("honcho start [--setup basic]", "run a local Honcho stack (Docker)"),
72
+ ("honcho doctor", "verify connection and workspace health"),
73
+ ]
74
+ stack_rows = [
75
+ ("honcho start / status / stop", "lifecycle for the local Docker stack"),
76
+ ("honcho start --setup basic", "interactive LLM + feature wizard"),
77
+ ("HONCHO_BASE_URL=http://127.0.0.1:8000", "prefix any command — CLI stays on api.honcho.dev until you set this"),
72
78
  ]
73
79
  cmd_rows = [
74
80
  ("[dim]pattern[/dim]", r"[dim]honcho <command> \[args] \[-w workspace] \[-p peer] \[-s session][/dim]"),
@@ -85,14 +91,15 @@ def print_welcome(console: Console) -> None:
85
91
  ("config", "inspect current configuration"),
86
92
  ]
87
93
  memory_rows = [
88
- ("honcho peer chat \"...\" -p <peer> -w <workspace>","query the Dialectic about a peer"),
89
- ("honcho peer inspect -p <peer> -w <workspace>","dashboard: peer card + recent conclusions + configuration"),
94
+ ("honcho peer chat \"...\" -p <peer> -w <workspace>", "query the Dialectic about a peer"),
95
+ ("honcho peer inspect -p <peer> -w <workspace>", "dashboard: peer card + recent conclusions + configuration"),
90
96
  ("honcho peer representation -p <peer> -w <workspace>", "global peer representation"),
91
97
  ("honcho peer representation -p <peer> -w <workspace> -s <session>", "session-scoped peer representation"),
92
98
  ("honcho peer card -p <peer> -w <workspace>", "synthesized identity: traits, preferences, instructions"),
93
- ("honcho conclusion list -p <peer> -w <workspace>", "browse peer conclusions"),
99
+ ("honcho conclusion list -p <peer> -w <workspace>", "browse peer conclusions"),
100
+ ("honcho session view / context -s <session>", "transcript, or what an agent would see"),
101
+ ("honcho workspace queue-status", "is the deriver processing?"),
94
102
  ]
95
-
96
103
  option_rows = [
97
104
  ("-w / --workspace", "scope to a workspace"),
98
105
  ("-p / --peer", "scope to a peer"),
@@ -102,10 +109,12 @@ def print_welcome(console: Console) -> None:
102
109
  ]
103
110
 
104
111
  console.print(_welcome_panel("getting started", start_rows))
112
+ console.print(_welcome_panel("local stack", stack_rows))
105
113
  console.print(_welcome_panel("commands", cmd_rows))
106
114
  console.print(_welcome_panel("memory", memory_rows))
107
115
  console.print(_welcome_panel("options", option_rows))
108
116
  console.print()
117
+ maybe_print_update_nag()
109
118
 
110
119
 
111
120
  class HonchoTyperGroup(TyperGroup):
@@ -7,6 +7,7 @@ only — the start command rejects ``--setup`` in JSON / non-TTY mode.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import sys
10
11
  import tomllib
11
12
  from dataclasses import dataclass
12
13
  from pathlib import Path
@@ -14,11 +15,7 @@ from pathlib import Path
14
15
  import typer
15
16
  from rich.console import Console
16
17
 
17
- from honcho_cli.local.env import (
18
- is_placeholder_key,
19
- read_env_file,
20
- settings_from_environ,
21
- )
18
+ from honcho_cli.local.env import is_placeholder_key, read_env_file, settings_from_environ
22
19
  from honcho_cli.output import print_error
23
20
 
24
21
  SETUP_MODES = ("basic", "advanced")
@@ -145,6 +142,10 @@ def answers_to_env(answers: SetupAnswers) -> dict[str, str]:
145
142
  env[_PROVIDER_KEY_ENV[answers.provider]] = answers.api_key
146
143
  if answers.base_url:
147
144
  env["LLM_OPENAI_BASE_URL"] = answers.base_url
145
+ # Embeddings do not inherit this URL; write it so OpenRouter/vLLM
146
+ # keys are not sent to api.openai.com.
147
+ if (answers.embedding_transport or "openai") == "openai":
148
+ env["EMBEDDING_MODEL_CONFIG__OVERRIDES__BASE_URL"] = answers.base_url
148
149
 
149
150
  if answers.embedding_api_key and answers.embedding_key_transport:
150
151
  embed_key = (
@@ -188,9 +189,13 @@ def answers_to_env(answers: SetupAnswers) -> dict[str, str]:
188
189
 
189
190
  def answers_drop_keys(answers: SetupAnswers) -> tuple[str, ...]:
190
191
  """Keys to remove so a previous wizard run cannot leak into this one."""
191
- if answers.provider == "openai-compatible":
192
- return ()
193
- return ("LLM_OPENAI_BASE_URL",)
192
+ drop: list[str] = []
193
+ if answers.provider != "openai-compatible":
194
+ drop.append("LLM_OPENAI_BASE_URL")
195
+ embed_openai = (answers.embedding_transport or "openai") == "openai"
196
+ if answers.provider != "openai-compatible" or not embed_openai:
197
+ drop.append("EMBEDDING_MODEL_CONFIG__OVERRIDES__BASE_URL")
198
+ return tuple(drop)
194
199
 
195
200
 
196
201
  def run_setup(
@@ -441,13 +446,7 @@ def _prompt_secret(label: str, current: str | None) -> str:
441
446
  if choice != "2":
442
447
  return current
443
448
  _console.print(f" [dim]{label}[/dim]")
444
- raw = typer.prompt(
445
- f" {label}",
446
- default="",
447
- show_default=False,
448
- hide_input=True,
449
- prompt_suffix=": ",
450
- ).strip()
449
+ raw = _prompt_masked(f" {label}: ").strip()
451
450
  if not raw or is_placeholder_key(raw):
452
451
  print_error(
453
452
  "MISSING_LLM_KEY",
@@ -457,6 +456,69 @@ def _prompt_secret(label: str, current: str | None) -> str:
457
456
  return raw
458
457
 
459
458
 
459
+ def _prompt_masked(prompt: str) -> str:
460
+ """Read a secret, echoing ``*`` per character so paste is visibly received."""
461
+ stream = sys.stderr
462
+ stream.write(prompt)
463
+ stream.flush()
464
+ chars: list[str] = []
465
+
466
+ def _write(text: str) -> None:
467
+ stream.write(text)
468
+ stream.flush()
469
+
470
+ def _feed(ch: str) -> bool:
471
+ """Return True when input is complete."""
472
+ if not ch or ch in ("\n", "\r", "\x04"):
473
+ _write("\n")
474
+ return True
475
+ if ch in ("\x7f", "\x08"):
476
+ if chars:
477
+ chars.pop()
478
+ _write("\b \b")
479
+ return False
480
+ if ch == "\x1b":
481
+ return False
482
+ if ch.isprintable():
483
+ chars.append(ch)
484
+ _write("*")
485
+ return False
486
+
487
+ if sys.platform == "win32":
488
+ import msvcrt
489
+
490
+ while True:
491
+ ch = msvcrt.getwch()
492
+ if ch in ("\x00", "\xe0"):
493
+ msvcrt.getwch()
494
+ continue
495
+ if _feed(ch):
496
+ return "".join(chars)
497
+
498
+ import termios
499
+ import tty
500
+
501
+ fd = sys.stdin.fileno()
502
+ old = termios.tcgetattr(fd)
503
+ try:
504
+ tty.setcbreak(fd)
505
+ while True:
506
+ ch = sys.stdin.read(1)
507
+ if ch == "\x1b":
508
+ nxt = sys.stdin.read(1)
509
+ if nxt == "[":
510
+ while True:
511
+ seq = sys.stdin.read(1)
512
+ if not seq or "@" <= seq <= "~":
513
+ break
514
+ continue
515
+ if _feed(ch):
516
+ return "".join(chars)
517
+ finally:
518
+ termios.tcsetattr(fd, termios.TCSADRAIN, old)
519
+ return "".join(chars)
520
+
521
+
460
522
  def _redact(key: str) -> str:
461
523
  if len(key) <= 4:
462
524
  return "***"
@@ -15,6 +15,7 @@ from honcho_cli import __version__
15
15
  from honcho_cli._help import HonchoTyperGroup, print_welcome
16
16
  from honcho_cli.branding import BANNER
17
17
  from honcho_cli.output import set_json_mode
18
+ from honcho_cli.update_check import maybe_print_update_nag
18
19
 
19
20
 
20
21
  app = typer.Typer(
@@ -60,6 +61,7 @@ def main(
60
61
  if ctx.invoked_subcommand is None:
61
62
  print_welcome(Console())
62
63
  raise typer.Exit()
64
+ maybe_print_update_nag()
63
65
 
64
66
 
65
67
  # Register top-level commands
@@ -0,0 +1,67 @@
1
+ """Once-a-day stderr notice when a newer honcho-cli is on PyPI.
2
+
3
+ Fail-open: any error is swallowed. Cache is ``update-check.json`` beside
4
+ config, not ``config.json``.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import os
11
+ import sys
12
+ import time
13
+
14
+ import httpx
15
+
16
+ from honcho_cli import __version__
17
+ from honcho_cli.branding import ICON_RUN
18
+ from honcho_cli.config import _config_dir
19
+ from honcho_cli.output import console, use_json
20
+
21
+ _INTERVAL_S = 24 * 60 * 60
22
+ _PYPI_URL = "https://pypi.org/pypi/honcho-cli/json"
23
+
24
+
25
+ def maybe_print_update_nag() -> None:
26
+ if use_json() or "--json" in sys.argv:
27
+ return
28
+ if os.environ.get("HONCHO_NO_UPDATE_CHECK", "").lower() in ("1", "true"):
29
+ return
30
+ try:
31
+ path = _config_dir() / "update-check.json"
32
+ now = time.time()
33
+ try:
34
+ cache = json.loads(path.read_text(encoding="utf-8"))
35
+ except (OSError, json.JSONDecodeError, UnicodeDecodeError):
36
+ cache = {}
37
+ if isinstance(cache, dict) and now - float(cache.get("t") or 0) < _INTERVAL_S:
38
+ return
39
+ path.parent.mkdir(parents=True, exist_ok=True)
40
+ path.write_text(json.dumps({"t": now}), encoding="utf-8")
41
+ latest = httpx.get(_PYPI_URL, timeout=1.0).json()["info"]["version"]
42
+ if not isinstance(latest, str) or not _is_newer(latest, __version__):
43
+ return
44
+ console.print(f" {ICON_RUN} honcho-cli {latest} is available (you have {__version__})")
45
+ console.print(" [dim]uv tool upgrade honcho-cli[/dim]")
46
+ except Exception:
47
+ return
48
+
49
+
50
+ def _is_newer(latest: str, current: str) -> bool:
51
+ def parts(version: str) -> tuple[int, ...]:
52
+ out: list[int] = []
53
+ for segment in version.lstrip("v").split("."):
54
+ num = ""
55
+ for ch in segment:
56
+ if ch.isdigit():
57
+ num += ch
58
+ else:
59
+ break
60
+ if not num:
61
+ break
62
+ out.append(int(num))
63
+ return tuple(out) or (0,)
64
+
65
+ a, b = parts(latest), parts(current)
66
+ n = max(len(a), len(b))
67
+ return a + (0,) * (n - len(a)) > b + (0,) * (n - len(b))
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  from honcho_cli.local.setup import (
6
6
  DIALECTIC_LEVELS,
7
7
  SetupAnswers,
8
+ answers_drop_keys,
8
9
  answers_to_env,
9
10
  chat_model_default,
10
11
  load_toml_setup_defaults,
@@ -46,6 +47,33 @@ def test_basic_anthropic_keeps_openai_embeddings_default():
46
47
  assert "EMBEDDING_MODEL_CONFIG__TRANSPORT" not in env
47
48
 
48
49
 
50
+ def test_openai_compatible_copies_base_url_to_embeddings():
51
+ env = answers_to_env(
52
+ SetupAnswers(
53
+ mode="basic",
54
+ provider="openai-compatible",
55
+ api_key="sk-or-test",
56
+ chat_model="gpt-test",
57
+ base_url="https://openrouter.ai/api/v1",
58
+ )
59
+ )
60
+ assert env["LLM_OPENAI_BASE_URL"] == "https://openrouter.ai/api/v1"
61
+ assert (
62
+ env["EMBEDDING_MODEL_CONFIG__OVERRIDES__BASE_URL"]
63
+ == "https://openrouter.ai/api/v1"
64
+ )
65
+
66
+
67
+ def test_leaving_openai_compatible_drops_proxy_urls():
68
+ dropped = answers_drop_keys(
69
+ SetupAnswers(
70
+ mode="basic", provider="openai", api_key="sk", chat_model="gpt-test"
71
+ )
72
+ )
73
+ assert "LLM_OPENAI_BASE_URL" in dropped
74
+ assert "EMBEDDING_MODEL_CONFIG__OVERRIDES__BASE_URL" in dropped
75
+
76
+
49
77
  def test_chat_default_comes_from_image_toml(tmp_path):
50
78
  path = tmp_path / "config.toml"
51
79
  path.write_text(
File without changes
File without changes
File without changes
File without changes