asher-cli 0.0.4__tar.gz → 0.1.0__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 (70) hide show
  1. asher_cli-0.1.0/.github/pull_request_template.md +24 -0
  2. {asher_cli-0.0.4 → asher_cli-0.1.0}/.github/workflows/ci.yml +2 -2
  3. {asher_cli-0.0.4 → asher_cli-0.1.0}/.github/workflows/claude-code-review.yml +1 -1
  4. {asher_cli-0.0.4 → asher_cli-0.1.0}/.github/workflows/claude.yml +1 -1
  5. {asher_cli-0.0.4 → asher_cli-0.1.0}/.github/workflows/coverage.yml +1 -1
  6. {asher_cli-0.0.4 → asher_cli-0.1.0}/.github/workflows/release.yml +2 -2
  7. {asher_cli-0.0.4 → asher_cli-0.1.0}/CLAUDE.md +20 -1
  8. asher_cli-0.1.0/CODEOWNERS +1 -0
  9. {asher_cli-0.0.4 → asher_cli-0.1.0}/PKG-INFO +8 -4
  10. {asher_cli-0.0.4 → asher_cli-0.1.0}/README.md +4 -2
  11. {asher_cli-0.0.4 → asher_cli-0.1.0}/ROADMAP.md +363 -176
  12. asher_cli-0.1.0/asher/activity_labels.py +83 -0
  13. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/commands/__init__.py +460 -47
  14. asher_cli-0.1.0/asher/mcp_bridge.py +41 -0
  15. asher_cli-0.1.0/asher/mcp_config.py +107 -0
  16. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/robot_adapters.py +60 -0
  17. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/robot_protocol.py +17 -1
  18. {asher_cli-0.0.4 → asher_cli-0.1.0}/pyproject.toml +7 -3
  19. asher_cli-0.1.0/tests/test_activity_labels.py +133 -0
  20. asher_cli-0.1.0/tests/test_lr5_commands.py +264 -0
  21. asher_cli-0.1.0/tests/test_mcp_bridge.py +43 -0
  22. asher_cli-0.1.0/tests/test_mcp_command.py +117 -0
  23. asher_cli-0.1.0/tests/test_mcp_config.py +130 -0
  24. asher_cli-0.1.0/tests/test_missing_robot_commands.py +404 -0
  25. {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_new_commands_pilot.py +12 -5
  26. {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_robot_adapters.py +188 -0
  27. {asher_cli-0.0.4 → asher_cli-0.1.0}/uv.lock +471 -8
  28. {asher_cli-0.0.4 → asher_cli-0.1.0}/.claude/hooks/block-env.ps1 +0 -0
  29. {asher_cli-0.0.4 → asher_cli-0.1.0}/.claude/settings.json +0 -0
  30. {asher_cli-0.0.4 → asher_cli-0.1.0}/.claude/skills/pylitterbot-ref/SKILL.md +0 -0
  31. {asher_cli-0.0.4 → asher_cli-0.1.0}/.claude/skills/release/SKILL.md +0 -0
  32. {asher_cli-0.0.4 → asher_cli-0.1.0}/.claude/skills/textual/SKILL.md +0 -0
  33. {asher_cli-0.0.4 → asher_cli-0.1.0}/.env.example +0 -0
  34. {asher_cli-0.0.4 → asher_cli-0.1.0}/.githooks/pre-push +0 -0
  35. {asher_cli-0.0.4 → asher_cli-0.1.0}/.gitignore +0 -0
  36. {asher_cli-0.0.4 → asher_cli-0.1.0}/.vscode/launch.json +0 -0
  37. {asher_cli-0.0.4 → asher_cli-0.1.0}/.vscode/settings.json +0 -0
  38. {asher_cli-0.0.4 → asher_cli-0.1.0}/.vscode/tasks.json +0 -0
  39. {asher_cli-0.0.4 → asher_cli-0.1.0}/LICENSE +0 -0
  40. {asher_cli-0.0.4 → asher_cli-0.1.0}/app.py +0 -0
  41. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/__init__.py +0 -0
  42. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/__main__.py +0 -0
  43. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/app.py +0 -0
  44. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/auth.py +0 -0
  45. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/cats.py +0 -0
  46. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/commands/base.py +0 -0
  47. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/connection/__init__.py +0 -0
  48. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/constants.py +0 -0
  49. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/helpers.py +0 -0
  50. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/login_flow.py +0 -0
  51. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/monitoring/__init__.py +0 -0
  52. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/slash-commands/__init__.py +0 -0
  53. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/ui/__init__.py +0 -0
  54. {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/ui/style.tcss +0 -0
  55. {asher_cli-0.0.4 → asher_cli-0.1.0}/renovate.json +0 -0
  56. {asher_cli-0.0.4 → asher_cli-0.1.0}/requirements.txt +0 -0
  57. {asher_cli-0.0.4 → asher_cli-0.1.0}/test.py +0 -0
  58. {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/__init__.py +0 -0
  59. {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/conftest.py +0 -0
  60. {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_app_pilot.py +0 -0
  61. {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_auth.py +0 -0
  62. {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_auth_pilot.py +0 -0
  63. {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_cats.py +0 -0
  64. {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_commands_pilot.py +0 -0
  65. {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_connection.py +0 -0
  66. {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_connection_mixin.py +0 -0
  67. {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_monitoring.py +0 -0
  68. {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_ui.py +0 -0
  69. {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/testhelpers.py +0 -0
  70. {asher_cli-0.0.4 → asher_cli-0.1.0}/watchrun.py +0 -0
@@ -0,0 +1,24 @@
1
+ ## Summary
2
+
3
+ <!-- What does this PR change, and why? -->
4
+
5
+ ## Related issue
6
+
7
+ Closes #
8
+
9
+ ## Changes
10
+
11
+ -
12
+
13
+ ## Testing
14
+
15
+ - [ ] `pytest` passes locally
16
+ - [ ] Added/updated tests for new or changed behavior
17
+ - [ ] Manually ran the CLI to verify the change (`asher ...`)
18
+
19
+ ## Checklist
20
+
21
+ - [ ] Code is linted/formatted per project conventions
22
+ - [ ] README / CLI help text updated if flags or commands changed
23
+ - [ ] No secrets, tokens, or local paths committed
24
+ - [ ] Version bump considered if this affects the published PyPI package
@@ -13,7 +13,7 @@ jobs:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
15
  - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
16
- - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
16
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
17
17
  - run: uv sync --dev
18
18
  - run: uv run ruff check .
19
19
  - run: uv run ruff format --check .
@@ -29,6 +29,6 @@ jobs:
29
29
  runs-on: ${{ matrix.os }}
30
30
  steps:
31
31
  - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
32
- - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
32
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
33
33
  - run: uv sync --dev
34
34
  - run: uv run pytest tests/ -v --tb=short
@@ -33,7 +33,7 @@ jobs:
33
33
 
34
34
  - name: Run Claude Code Review
35
35
  id: claude-review
36
- uses: anthropics/claude-code-action@428971d2ecd6e3a7cb0ee0da2a3a8b33fdb3678d # v1
36
+ uses: anthropics/claude-code-action@af0559ee4f514d1ef21826982bed13f7edc3c35e # v1
37
37
  with:
38
38
  claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
39
39
  plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
@@ -32,7 +32,7 @@ jobs:
32
32
 
33
33
  - name: Run Claude Code
34
34
  id: claude
35
- uses: anthropics/claude-code-action@428971d2ecd6e3a7cb0ee0da2a3a8b33fdb3678d # v1
35
+ uses: anthropics/claude-code-action@af0559ee4f514d1ef21826982bed13f7edc3c35e # v1
36
36
  with:
37
37
  claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
38
38
 
@@ -13,7 +13,7 @@ jobs:
13
13
  runs-on: ubuntu-latest
14
14
  steps:
15
15
  - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
16
- - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
16
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
17
17
  - run: uv sync --dev
18
18
  - name: Run tests with coverage
19
19
  run: uv run pytest tests/ --cov=asher --cov-report=lcov --cov-report=term-missing
@@ -20,7 +20,7 @@ jobs:
20
20
  - name: Extract version from branch name
21
21
  id: version
22
22
  run: echo "version=${GITHUB_REF_NAME#release/}" >> "$GITHUB_OUTPUT"
23
- - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
23
+ - uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
24
24
  - run: uv build
25
25
  - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
26
26
  with:
@@ -37,7 +37,7 @@ jobs:
37
37
  with:
38
38
  name: dist
39
39
  path: dist/
40
- - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
40
+ - uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # release/v1
41
41
 
42
42
  github-release:
43
43
  name: Create GitHub Release
@@ -41,6 +41,8 @@ asher/
41
41
  login_flow.py LoginFlow state machine — inline email/password prompt in command bar
42
42
  robot_protocol.py RobotProtocol structural Protocol for pylitterbot robot objects
43
43
  robot_adapters.py RobotAdapter ABC + LR3/LR4/LR5 subclasses + make_adapter() factory
44
+ mcp_config.py Claude Desktop config read/write for the /mcp slash command
45
+ mcp_bridge.py asher-mcp-launch console script — keyring-backed pylitterbot MCP launcher
44
46
  __main__.py main() entry point
45
47
  commands/
46
48
  base.py Command ABC, SlashCommand, CommandRegistry
@@ -61,6 +63,9 @@ tests/
61
63
  test_connection_mixin.py ConnectionMixin structure
62
64
  test_monitoring.py MonitoringMixin async methods
63
65
  test_ui.py UIMixin constants, CSS, helper existence
66
+ test_mcp_config.py Claude Desktop config read/write
67
+ test_mcp_bridge.py mcp_bridge launcher credential/subprocess handling
68
+ test_mcp_command.py /mcp slash command dispatch
64
69
 
65
70
  .github/workflows/
66
71
  ci.yml ruff + mypy + pytest on every push/PR
@@ -83,13 +88,23 @@ LITTER_ROBOT_PASSWORD=...
83
88
  Keyring service name: `asher-cli`, keys `email` and `password`.
84
89
  Helper functions in `asher/connection/__init__.py`: `_keyring_load()`, `_keyring_save()`, `_keyring_delete()`.
85
90
 
91
+ ## MCP bridge (`/mcp`)
92
+
93
+ pylitterbot ships an optional MCP server (`pip install pylitterbot[mcp]`, run via `python -m pylitterbot.mcp`) that lets an MCP client like Claude Desktop monitor/control the robot directly. Its own docs configure it with plaintext credentials in the client's JSON config — asher-cli avoids that:
94
+
95
+ - `/mcp on|off|status` (in `asher/commands/__init__.py`, logic in `asher/mcp_config.py`) adds/removes an entry (named by `mcp_config._SERVER_NAME`) in every `claude_desktop_config.json` this OS's Claude Desktop might read (`mcp_config.config_paths()` — on Windows this includes both the standard installer path and any MSIX/Microsoft Store virtualized path). The entry's `command` is `sys.executable -m asher.mcp_bridge` — never the credentials themselves.
96
+ - `/mcp on` also auto-installs pylitterbot's `mcp` extra via `sys.executable -m pip install "pylitterbot[mcp]==<installed version>"` if the `mcp` package isn't importable yet.
97
+ - `asher/mcp_bridge.py` (console script `asher-mcp-launch`) is what Claude Desktop actually spawns. It reads email/password from the OS keyring at process start, sets them as `LITTER_ROBOT_USERNAME`/`LITTER_ROBOT_PASSWORD` (pylitterbot's expected names — note these differ from asher-cli's own `.env` var `LITTER_ROBOT_USER`) in that process's environment only, then execs `python -m pylitterbot.mcp`. No credentials ever touch the on-disk MCP config.
98
+ - `/mcp on` requires keyring credentials. If none are found but `.env` fallback credentials are set, it copies them into the keyring automatically (since the bridge process can't reliably discover a project-relative `.env` — Claude Desktop controls its working directory, not asher-cli).
99
+ - Requires the `mcp` extra: `uv sync --extra mcp` / `pip install asher-cli[mcp]`. Restart Claude Desktop after toggling for the change to take effect.
100
+
86
101
  ## Command convention
87
102
 
88
103
  **Normal commands** (no prefix) — robot actions only:
89
104
  `clean`, `status`, `lock`, `unlock`, `sleep`, `wake`, `night-light on|off|auto`, `night-light-brightness <level>`, `history`, `clear`, `help`
90
105
 
91
106
  **Slash commands** (`/` prefix) — app management only:
92
- `/login`, `/logout`, `/exit`, `/robots`, `/robot <index|name>`
107
+ `/login`, `/logout`, `/exit`, `/robots`, `/robot <index|name>`, `/pets`, `/pet <index|name>`, `/cat on|off|color <hex>`, `/refresh <seconds|off>`, `/config`, `/mcp on|off|status`
93
108
 
94
109
  **Special cases** (accepted both with and without `/`):
95
110
  `exit`, `quit`, `q` — exit the app
@@ -144,6 +159,10 @@ pylitterbot auto-detects robot type. Commands that differ per model are handled
144
159
  - `robot.set_night_light_brightness(int)` or `robot.set_night_light_mode(NightLightMode)`
145
160
  - `robot.get_activity_history(limit=int)` → list of `Activity` objects with `.timestamp` and `.action` (`LitterBoxStatus` enum)
146
161
 
162
+ ## Code comments
163
+
164
+ Don't add comments above functions or inline unless the WHY is genuinely non-obvious (a hidden constraint, a subtle invariant, a workaround for a specific bug). Well-named identifiers should make the WHAT self-evident. Before reaching for a comment, check whether the explanation can instead be expressed through abstraction or encapsulation — e.g. domain logic buried in a mixin or command handler should move to a self-commenting, domain-named method rather than being explained in a comment. Favor human-readable, domain-driven names and logical flow over prose explanations, while keeping code legible to agents working in this repo.
165
+
147
166
  ## Development notes
148
167
 
149
168
  - Textual and pylitterbot are both asyncio-native — compose cleanly with `@work` tasks
@@ -0,0 +1 @@
1
+ * @karanshukla
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: asher-cli
3
- Version: 0.0.4
3
+ Version: 0.1.0
4
4
  Summary: Terminal dashboard for Litter Robot (LR3/LR4/LR5) via the Whisker cloud API
5
5
  License: MIT License
6
6
 
@@ -35,11 +35,13 @@ Classifier: Programming Language :: Python :: 3.12
35
35
  Classifier: Topic :: Home Automation
36
36
  Requires-Python: >=3.10
37
37
  Requires-Dist: keyring>=24.0.0
38
- Requires-Dist: pylitterbot==2025.5.0
38
+ Requires-Dist: pylitterbot==2025.6.2
39
39
  Requires-Dist: python-dotenv>=1.0.0
40
40
  Requires-Dist: rich>=13.0.0
41
41
  Requires-Dist: textual>=0.47.0
42
42
  Requires-Dist: tzlocal>=5.4
43
+ Provides-Extra: mcp
44
+ Requires-Dist: pylitterbot[mcp]==2025.6.2; extra == 'mcp'
43
45
  Description-Content-Type: text/markdown
44
46
 
45
47
  # Asher CLI
@@ -154,12 +156,14 @@ LITTER_ROBOT_PASSWORD=yourpassword
154
156
  ## Releasing
155
157
 
156
158
  ```bash
157
- # bump version, commit, and tag in one step
159
+ # bump version, commit, and tag in one step, then push with tags
158
160
  uv run bump-my-version bump patch # 0.0.1 → 0.0.2
159
161
  uv run bump-my-version bump minor # 0.0.2 → 0.1.0
160
162
  uv run bump-my-version bump major # 0.1.0 → 1.0.0
161
163
 
162
- # then push the release branch to trigger PyPI publish
164
+ git push && git push --tags
165
+
166
+ # then push the release branch to trigger PyPI publish with the new semver
163
167
  git checkout -b release/0.0.2
164
168
  git push origin release/0.0.2
165
169
  ```
@@ -110,12 +110,14 @@ LITTER_ROBOT_PASSWORD=yourpassword
110
110
  ## Releasing
111
111
 
112
112
  ```bash
113
- # bump version, commit, and tag in one step
113
+ # bump version, commit, and tag in one step, then push with tags
114
114
  uv run bump-my-version bump patch # 0.0.1 → 0.0.2
115
115
  uv run bump-my-version bump minor # 0.0.2 → 0.1.0
116
116
  uv run bump-my-version bump major # 0.1.0 → 1.0.0
117
117
 
118
- # then push the release branch to trigger PyPI publish
118
+ git push && git push --tags
119
+
120
+ # then push the release branch to trigger PyPI publish with the new semver
119
121
  git checkout -b release/0.0.2
120
122
  git push origin release/0.0.2
121
123
  ```