langstage-cli 0.6.45__tar.gz → 0.6.46__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.
- {langstage_cli-0.6.45/langstage_cli.egg-info → langstage_cli-0.6.46}/PKG-INFO +1 -1
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/langstage_cli/cli.py +20 -3
- {langstage_cli-0.6.45 → langstage_cli-0.6.46/langstage_cli.egg-info}/PKG-INFO +1 -1
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/langstage_cli.egg-info/SOURCES.txt +1 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/pyproject.toml +1 -1
- langstage_cli-0.6.46/tests/test_usage_hint_186.py +52 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/LICENSE +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/README.md +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/deepagent_code/__init__.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/langstage_cli/__init__.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/langstage_cli/agui_stream.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/langstage_cli/config.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/langstage_cli/exit_codes.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/langstage_cli/sessions.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/langstage_cli.egg-info/dependency_links.txt +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/langstage_cli.egg-info/entry_points.txt +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/langstage_cli.egg-info/requires.txt +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/langstage_cli.egg-info/top_level.txt +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/setup.cfg +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_advertised_wave4.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_agui_stream.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_approval_menu_abort.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_async_mode.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_checkpointer.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_cli.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_cli_help.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_codeconfig.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_config.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_config_command.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_configurable.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_deferred_pass.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_dumb_term.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_exit_codes.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_generic_interrupt.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_help_render.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_history_persist.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_hitl_approval_preview.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_hitl_resume_warning.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_init.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_interrupt_non_tty.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_legacy_config_home.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_load_error.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_message_boundaries.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_no_color.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_no_interactive_approve.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_no_persist_resume.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_persist_env.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_quiet_output.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_reasoning_render.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_rename_shim.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_render_markdown.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_session_lifecycle.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_sessions.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_show_config.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_show_config_malformed.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_spec_loading_core.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_spec_resolution.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_spinner_reply_survives.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_stdin_interactive.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_stdin_single_shot.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_stream_mode.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_streaming_marker.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_terminal_width.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_toml_relative_paths.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_tool_card_args.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_tool_output_escape.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_turn_error_verbose.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_turn_exit_and_render.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_unicode_console.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_verbose_command.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_verify_flag.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_workspace.py +0 -0
- {langstage_cli-0.6.45 → langstage_cli-0.6.46}/tests/test_wrong_type_graph.py +0 -0
|
@@ -1001,6 +1001,15 @@ def _default_agent_path() -> Optional[Path]:
|
|
|
1001
1001
|
return path if path.exists() else None
|
|
1002
1002
|
|
|
1003
1003
|
|
|
1004
|
+
# A MESSAGE that is really an agent spec: ``my_agent.py:graph`` or ``pkg.module:agent``.
|
|
1005
|
+
_SPEC_LIKE = re.compile(r"^(?:(?:[A-Za-z]:)?[^\s:]+\.py|[A-Za-z_][\w.]*):[A-Za-z_]\w*$")
|
|
1006
|
+
|
|
1007
|
+
|
|
1008
|
+
def _looks_like_agent_spec(message: Optional[str]) -> bool:
|
|
1009
|
+
"""Whether a positional MESSAGE looks like an agent spec meant for ``-a`` (gh #186)."""
|
|
1010
|
+
return bool(message) and bool(_SPEC_LIKE.match(message.strip()))
|
|
1011
|
+
|
|
1012
|
+
|
|
1004
1013
|
def _no_agent_toml_hint(cfg: Any, toml_sources: list) -> List[str]:
|
|
1005
1014
|
"""Lines explaining why a ``langstage.toml`` that was read supplied no spec (gh #148).
|
|
1006
1015
|
|
|
@@ -3468,10 +3477,18 @@ def main(
|
|
|
3468
3477
|
# of only pointing at the env var (gh #148).
|
|
3469
3478
|
for line in _no_agent_toml_hint(cfg, toml_sources):
|
|
3470
3479
|
_status(f"{YELLOW}{line}{RESET}")
|
|
3480
|
+
# The spec went in as the MESSAGE: name the command that works (gh #186).
|
|
3481
|
+
if _looks_like_agent_spec(message):
|
|
3482
|
+
_status(
|
|
3483
|
+
f"{YELLOW}The agent spec goes after -a: langstage-cli -a {message.strip()}{RESET}"
|
|
3484
|
+
)
|
|
3471
3485
|
_status(f"\n{DIM}Usage:{RESET}")
|
|
3472
|
-
_status(" langstage-cli path/to/agent.py:graph")
|
|
3473
|
-
_status(" langstage-cli mypackage.module:agent")
|
|
3474
|
-
_status(
|
|
3486
|
+
_status(" langstage-cli -a path/to/agent.py:graph")
|
|
3487
|
+
_status(" langstage-cli -a mypackage.module:agent")
|
|
3488
|
+
_status(
|
|
3489
|
+
f"\n{DIM}Or set LANGSTAGE_AGENT_SPEC, put the spec under [agent] spec in "
|
|
3490
|
+
f"langstage.toml, or run `langstage-cli init` for a starter agent{RESET}"
|
|
3491
|
+
)
|
|
3475
3492
|
sys.exit(1)
|
|
3476
3493
|
|
|
3477
3494
|
# The BASE directory a relative spec resolves against depends on WHERE it came
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "langstage-cli"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.46"
|
|
8
8
|
description = "The terminal stage for your LangGraph agent — Claude Code-style CLI for any CompiledGraph"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""gh #186: the "No agent specified" hint shows commands that work (with -a)."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from click.testing import CliRunner
|
|
5
|
+
|
|
6
|
+
from langstage_cli import cli
|
|
7
|
+
from langstage_cli.cli import _looks_like_agent_spec, main
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def no_agent(tmp_path, monkeypatch):
|
|
12
|
+
monkeypatch.chdir(tmp_path)
|
|
13
|
+
for var in ("LANGSTAGE_AGENT_SPEC", "DEEPAGENT_AGENT_SPEC", "DEEPAGENT_SPEC"):
|
|
14
|
+
monkeypatch.delenv(var, raising=False)
|
|
15
|
+
monkeypatch.setattr(cli, "_default_agent_path", lambda: None)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_usage_hint_uses_dash_a(no_agent):
|
|
19
|
+
r = CliRunner().invoke(main, ["hi"])
|
|
20
|
+
assert r.exit_code == 1, r.output
|
|
21
|
+
assert "No agent specified" in r.output
|
|
22
|
+
assert "langstage-cli -a path/to/agent.py:graph" in r.output
|
|
23
|
+
assert "langstage-cli -a mypackage.module:agent" in r.output
|
|
24
|
+
assert "[agent] spec" in r.output and "langstage-cli init" in r.output
|
|
25
|
+
# Not a spec-looking message: no "goes after -a" line.
|
|
26
|
+
assert "goes after -a" not in r.output
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.parametrize("spec", ["my_agent.py:graph", "mypackage.module:agent"])
|
|
30
|
+
def test_spec_passed_as_message_names_the_working_command(no_agent, spec):
|
|
31
|
+
r = CliRunner().invoke(main, [spec])
|
|
32
|
+
assert r.exit_code == 1, r.output
|
|
33
|
+
assert f"The agent spec goes after -a: langstage-cli -a {spec}" in r.output
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.mark.parametrize(
|
|
37
|
+
"message,expected",
|
|
38
|
+
[
|
|
39
|
+
("my_agent.py:graph", True),
|
|
40
|
+
("./agents/a.py:graph", True),
|
|
41
|
+
(r"C:\work\agent.py:graph", True),
|
|
42
|
+
("pkg.module:agent", True),
|
|
43
|
+
("hello", False),
|
|
44
|
+
("what is 2:3", False),
|
|
45
|
+
("ratio 3:1 please", False),
|
|
46
|
+
("http://example.org:80", False),
|
|
47
|
+
("", False),
|
|
48
|
+
(None, False),
|
|
49
|
+
],
|
|
50
|
+
)
|
|
51
|
+
def test_looks_like_agent_spec(message, expected):
|
|
52
|
+
assert _looks_like_agent_spec(message) is expected
|
|
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
|
|
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
|