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.
- asher_cli-0.1.0/.github/pull_request_template.md +24 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.github/workflows/ci.yml +2 -2
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.github/workflows/claude-code-review.yml +1 -1
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.github/workflows/claude.yml +1 -1
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.github/workflows/coverage.yml +1 -1
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.github/workflows/release.yml +2 -2
- {asher_cli-0.0.4 → asher_cli-0.1.0}/CLAUDE.md +20 -1
- asher_cli-0.1.0/CODEOWNERS +1 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/PKG-INFO +8 -4
- {asher_cli-0.0.4 → asher_cli-0.1.0}/README.md +4 -2
- {asher_cli-0.0.4 → asher_cli-0.1.0}/ROADMAP.md +363 -176
- asher_cli-0.1.0/asher/activity_labels.py +83 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/commands/__init__.py +460 -47
- asher_cli-0.1.0/asher/mcp_bridge.py +41 -0
- asher_cli-0.1.0/asher/mcp_config.py +107 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/robot_adapters.py +60 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/robot_protocol.py +17 -1
- {asher_cli-0.0.4 → asher_cli-0.1.0}/pyproject.toml +7 -3
- asher_cli-0.1.0/tests/test_activity_labels.py +133 -0
- asher_cli-0.1.0/tests/test_lr5_commands.py +264 -0
- asher_cli-0.1.0/tests/test_mcp_bridge.py +43 -0
- asher_cli-0.1.0/tests/test_mcp_command.py +117 -0
- asher_cli-0.1.0/tests/test_mcp_config.py +130 -0
- asher_cli-0.1.0/tests/test_missing_robot_commands.py +404 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_new_commands_pilot.py +12 -5
- {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_robot_adapters.py +188 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/uv.lock +471 -8
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.claude/hooks/block-env.ps1 +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.claude/settings.json +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.claude/skills/pylitterbot-ref/SKILL.md +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.claude/skills/release/SKILL.md +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.claude/skills/textual/SKILL.md +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.env.example +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.githooks/pre-push +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.gitignore +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.vscode/launch.json +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.vscode/settings.json +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/.vscode/tasks.json +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/LICENSE +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/app.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/__init__.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/__main__.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/app.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/auth.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/cats.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/commands/base.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/connection/__init__.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/constants.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/helpers.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/login_flow.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/monitoring/__init__.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/slash-commands/__init__.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/ui/__init__.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/asher/ui/style.tcss +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/renovate.json +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/requirements.txt +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/test.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/__init__.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/conftest.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_app_pilot.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_auth.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_auth_pilot.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_cats.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_commands_pilot.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_connection.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_connection_mixin.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_monitoring.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/test_ui.py +0 -0
- {asher_cli-0.0.4 → asher_cli-0.1.0}/tests/testhelpers.py +0 -0
- {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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
```
|