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.
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/CHANGELOG.md +11 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/PKG-INFO +9 -9
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/README.md +8 -8
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/pyproject.toml +1 -1
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/__init__.py +1 -1
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/_help.py +17 -8
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/setup.py +77 -15
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/main.py +2 -0
- honcho_cli-0.1.4/src/honcho_cli/update_check.py +67 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_setup.py +28 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/.gitignore +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/scripts/generate_cli_docs.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/branding.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/__init__.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/conclusion.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/config_cmd.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/message.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/peer.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/session.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/setup.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/stack.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/commands/workspace.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/common.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/config.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/__init__.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/docker.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/env.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/health.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/profile.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/templates/__init__.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/templates/docker-compose.yml +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/local/templates/init.sql +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/oauth.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/output.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/src/honcho_cli/validation.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/__init__.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/conftest.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_commands.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_common.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_config.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_local.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_oauth.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_output.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_start.py +0 -0
- {honcho_cli-0.1.3 → honcho_cli-0.1.4}/tests/test_validation.py +0 -0
- {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
|
+
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
|
|
51
|
-
honcho start
|
|
52
|
-
honcho doctor
|
|
53
|
-
honcho
|
|
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`
|
|
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.
|
|
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
|
|
26
|
-
honcho start
|
|
27
|
-
honcho doctor
|
|
28
|
-
honcho
|
|
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`
|
|
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.
|
|
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
|
|
|
@@ -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
|
|
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",
|
|
70
|
-
("honcho start",
|
|
71
|
-
("honcho doctor",
|
|
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>",
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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 =
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|