Skip to content

CLI-tool helpers: mytk command, remote_cli, install_command_on_path, add_file_menu_command#98

Closed
dccote wants to merge 1 commit into
feature/remote-network-discoveryfrom
feature/cli-tools
Closed

CLI-tool helpers: mytk command, remote_cli, install_command_on_path, add_file_menu_command#98
dccote wants to merge 1 commit into
feature/remote-network-discoveryfrom
feature/cli-tools

Conversation

@dccote

@dccote dccote commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Stacked on #96 (base = feature/remote-network-discovery); review/merge #96 first, then this. GitHub will retarget it to main automatically once #96 merges.

Summary

Moves three pieces of generic framework plumbing into myTk so downstream apps stop reinventing them, and makes the remote CLI a first-class general tool.

What's new

mytk command (renamed from mytk-remote) — the one general tool to probe/--list/--browse/--discover and drive any RemoteControllable app. mytk-remote is kept as a compatibility alias (same entry point), so nothing breaks.

mytk.remote_cli(argv=None, *, host, port, app_name, prog) — an embeddable, branded command-line client. A downstream app's entry point does sys.exit(mytk.remote_cli(app_name="My App", prog="my-cli")) and gets a full CLI. It delegates to the same engine as the mytk command (run() gained default_host/default_port/default_app_name) — one syntax, one set of exit codes, one implementation — rather than a second parallel CLI. dict return values now print as an aligned key/value table.

mytk.install_command_on_path(name, *, arguments=(), directories=None) — writes a POSIX launcher for the current program onto PATH (first writable of /usr/local/bin, ~/.local/bin, ~/bin), so an app can distribute its branded CLI (install_command_on_path("my-cli", arguments=("ctl",))). Falls back across bin dirs, returns a PATH note, raises RuntimeError if none writable.

App.add_file_menu_command(label, command, *, before="Quit", separator=True) — insert into the File menu without the tkinter menu-bar plumbing.

The one line to scrutinize

install_command_on_path uses sys.executable verbatim — never .resolve(). A venv's python is a symlink to the base interpreter; resolving it follows the symlink and silently drops the venv's site-packages, so the installed command would lose the app's dependencies. This was a real bug hit downstream.

Design decisions (surfaced, not silently picked)

  1. remote_cli reconciled onto the existing CLI. The original spec proposed a standalone positional-arg client (my-cli add 2 3 + a custom coercer). Since myTk already ships remotecli (the mytk command) that introspects and dispatches, remote_cli now wraps it — same paren-call syntax ("add(2, 3)", ast-parsed literals) and exit codes. This matches the intended model ("a wrapper that calls the general tool") and avoids two CLI syntaxes in one library.
  2. Exit codes follow the mytk tool's convention, which differs from the spec: connection-refused → 1 (runtime), unknown command → 1 (server fault; not pre-validated, which would cost a round-trip per call), usage error → 2. remote_cli also catches argparse's SystemExit so it honors its documented "returns an int" contract.
  3. Windows: install_command_on_path raises NotImplementedError (POSIX /bin/sh wrapper). A .cmd shim + %LOCALAPPDATA% bin dir is the eventual story.
  4. Naming (install_command_on_path / remote_cli / add_file_menu_command) and the arguments tuple (vs a single subcommand= string) are the maintainer's call — happy to rename before this is depended on downstream. The rename to mytk and the arguments tuple were confirmed with the maintainer.

Usage

# app __main__: `myapp ctl <fn> …` runs the control CLI, in this app's venv
import sys, mytk
if sys.argv[1:2] == ["ctl"]:
    sys.exit(mytk.remote_cli(sys.argv[2:], app_name="Server", prog="myapp"))

mytk.install_command_on_path("myapp", arguments=("ctl",))   # one-time install
# then, with the app running:  myapp "turn_on()"  /  myapp --list

Tests

mytk/tests/testCliTools.py (17 tests): install_command_on_path (write/exec bits/verbatim executable, bin-dir fallback, RuntimeError, PATH note, overwrite — POSIX-only, skipped on Windows), remote_cli end-to-end against a live server (call, dict table, --list, unknown→1, usage→2, mismatch→2, refused→1), _print_result, and add_file_menu_command.

Verified locally: testCliTools 17/17, the 62-test remote suite, and testBaseWidgets all green. (The full local suite hangs on macOS at a pre-existing, unrelated test — testBaseWidgets.test_init_view — on main/the parent branch too; the CI matrix is the gate.)

Compatibility

Purely additive. No existing signature or behavior changes; mytk-remote still works.

🤖 Generated with Claude Code

…th, add_file_menu_command

Renames the remote CLI console script to `mytk` (keeps `mytk-remote` as a
compatibility alias) — one general tool to probe/list/browse/discover and drive
any RemoteControllable app.

Adds three reusable helpers so downstream apps stop reinventing them:

- mytk.remote_cli(argv=None, *, host, port, app_name, prog): an embeddable,
  branded command-line client. Delegates to the same engine as the `mytk`
  command (one syntax, one set of exit codes, one implementation) rather than a
  second parallel CLI. run()/build_parser gain default_host/port/app_name so a
  wrapper can bake in its target; dict results print as an aligned table.
- mytk.install_command_on_path(name, *, arguments=(), directories=None): write a
  POSIX launcher for this program onto PATH, using sys.executable VERBATIM (never
  resolve() — following a venv's python symlink drops the venv's site-packages).
  Falls back across bin dirs, reports a PATH note, raises on Windows.
- App.add_file_menu_command(label, command, *, before="Quit", separator=True):
  insert into the File menu without the tkinter plumbing.

Purely additive. New tests in testCliTools.py; docstring + CHANGELOG updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@dccote
dccote deleted the branch feature/remote-network-discovery July 7, 2026 17:56
@dccote dccote closed this Jul 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant