asher-cli 0.0.3__tar.gz → 0.0.5__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.0.5/.claude/hooks/block-env.ps1 +9 -0
- asher_cli-0.0.5/.claude/settings.json +40 -0
- asher_cli-0.0.5/.claude/skills/pylitterbot-ref/SKILL.md +73 -0
- asher_cli-0.0.5/.claude/skills/release/SKILL.md +43 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/.github/workflows/ci.yml +4 -4
- {asher_cli-0.0.3 → asher_cli-0.0.5}/.github/workflows/claude-code-review.yml +2 -2
- {asher_cli-0.0.3 → asher_cli-0.0.5}/.github/workflows/claude.yml +2 -2
- {asher_cli-0.0.3 → asher_cli-0.0.5}/.github/workflows/coverage.yml +3 -3
- {asher_cli-0.0.3 → asher_cli-0.0.5}/.github/workflows/release.yml +7 -7
- asher_cli-0.0.5/CLAUDE.md +208 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/PKG-INFO +24 -9
- {asher_cli-0.0.3 → asher_cli-0.0.5}/README.md +19 -7
- {asher_cli-0.0.3 → asher_cli-0.0.5}/ROADMAP.md +440 -100
- {asher_cli-0.0.3 → asher_cli-0.0.5}/asher/app.py +19 -15
- asher_cli-0.0.5/asher/commands/__init__.py +1045 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/asher/commands/base.py +5 -2
- {asher_cli-0.0.3 → asher_cli-0.0.5}/asher/connection/__init__.py +35 -9
- asher_cli-0.0.5/asher/constants.py +19 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/asher/helpers.py +5 -8
- asher_cli-0.0.5/asher/mcp_bridge.py +41 -0
- asher_cli-0.0.5/asher/mcp_config.py +107 -0
- asher_cli-0.0.5/asher/monitoring/__init__.py +185 -0
- asher_cli-0.0.5/asher/robot_adapters.py +152 -0
- asher_cli-0.0.5/asher/robot_protocol.py +58 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/asher/ui/__init__.py +65 -18
- {asher_cli-0.0.3 → asher_cli-0.0.5}/asher/ui/style.tcss +0 -10
- {asher_cli-0.0.3 → asher_cli-0.0.5}/pyproject.toml +10 -4
- asher_cli-0.0.5/renovate.json +22 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/tests/conftest.py +2 -2
- {asher_cli-0.0.3 → asher_cli-0.0.5}/tests/test_app_pilot.py +6 -3
- {asher_cli-0.0.3 → asher_cli-0.0.5}/tests/test_commands_pilot.py +6 -3
- {asher_cli-0.0.3 → asher_cli-0.0.5}/tests/test_connection.py +3 -2
- asher_cli-0.0.5/tests/test_mcp_bridge.py +43 -0
- asher_cli-0.0.5/tests/test_mcp_command.py +117 -0
- asher_cli-0.0.5/tests/test_mcp_config.py +130 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/tests/test_monitoring.py +39 -12
- asher_cli-0.0.5/tests/test_new_commands_pilot.py +392 -0
- asher_cli-0.0.5/tests/test_robot_adapters.py +393 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/uv.lock +568 -82
- asher_cli-0.0.5/watchrun.py +56 -0
- asher_cli-0.0.3/.github/workflows/dependency-audit.yml +0 -37
- asher_cli-0.0.3/CLAUDE.md +0 -174
- asher_cli-0.0.3/asher/commands/__init__.py +0 -442
- asher_cli-0.0.3/asher/monitoring/__init__.py +0 -111
- {asher_cli-0.0.3 → asher_cli-0.0.5}/.claude/skills/textual/SKILL.md +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/.env.example +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/.githooks/pre-push +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/.gitignore +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/.vscode/launch.json +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/.vscode/settings.json +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/.vscode/tasks.json +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/LICENSE +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/app.py +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/asher/__init__.py +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/asher/__main__.py +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/asher/auth.py +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/asher/cats.py +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/asher/login_flow.py +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/asher/slash-commands/__init__.py +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/requirements.txt +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/test.py +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/tests/__init__.py +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/tests/test_auth.py +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/tests/test_auth_pilot.py +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/tests/test_cats.py +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/tests/test_connection_mixin.py +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/tests/test_ui.py +0 -0
- {asher_cli-0.0.3 → asher_cli-0.0.5}/tests/testhelpers.py +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(uv run *)",
|
|
5
|
+
"Bash(git *)",
|
|
6
|
+
"Bash(python -c ' *)",
|
|
7
|
+
"Bash(python *)",
|
|
8
|
+
"mcp__plugin_context7_context7__resolve-library-id",
|
|
9
|
+
"mcp__plugin_context7_context7__query-docs",
|
|
10
|
+
"mcp__plugin_github_github__*",
|
|
11
|
+
"WebSearch",
|
|
12
|
+
"WebFetch",
|
|
13
|
+
"Bash(gh api *)"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"hooks": {
|
|
17
|
+
"PostToolUse": [
|
|
18
|
+
{
|
|
19
|
+
"matcher": "Edit|Write",
|
|
20
|
+
"hooks": [
|
|
21
|
+
{
|
|
22
|
+
"type": "command",
|
|
23
|
+
"command": "uv run ruff check --fix . --quiet; uv run ruff format . --quiet"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"PreToolUse": [
|
|
29
|
+
{
|
|
30
|
+
"matcher": "Edit|Write",
|
|
31
|
+
"hooks": [
|
|
32
|
+
{
|
|
33
|
+
"type": "command",
|
|
34
|
+
"command": "powershell -NonInteractive -ExecutionPolicy Bypass -File .claude/hooks/block-env.ps1"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pylitterbot-ref
|
|
3
|
+
description: Reference for the confirmed pylitterbot API surface as used in asher-cli. Load before making changes to robot command handling, monitoring, or adapter code. Prevents attribute-name guessing and eliminates runtime inspection commands.
|
|
4
|
+
user-invocable: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# pylitterbot Confirmed API Surface
|
|
8
|
+
|
|
9
|
+
Load this skill whenever working on `asher/commands/`, `asher/monitoring/`, `asher/robot_adapters.py`, or anything that touches `robot.*`.
|
|
10
|
+
|
|
11
|
+
## Robot attributes (LR4 confirmed)
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
robot.name # str
|
|
15
|
+
robot.serial # str
|
|
16
|
+
robot.is_online # bool
|
|
17
|
+
robot.status # LitterBoxStatus enum
|
|
18
|
+
robot.waste_drawer_level # int, 0–100
|
|
19
|
+
robot.sleep_mode_enabled # bool — NOT "sleeping"
|
|
20
|
+
robot.panel_lock_enabled # bool — NOT "panel_lockout"
|
|
21
|
+
robot.night_light_mode_enabled # bool
|
|
22
|
+
robot.last_seen # datetime | None
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Robot methods (all async, await required)
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
await robot.refresh()
|
|
29
|
+
await robot.start_cleaning()
|
|
30
|
+
await robot.set_sleep_mode(enabled: bool)
|
|
31
|
+
await robot.set_panel_lockout(enabled: bool)
|
|
32
|
+
await robot.set_night_light_brightness(brightness: int) # or:
|
|
33
|
+
await robot.set_night_light_mode(mode: NightLightMode)
|
|
34
|
+
await robot.get_activity_history(limit: int) # -> list[Activity]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Activity objects
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
activity.timestamp # datetime
|
|
41
|
+
activity.action # LitterBoxStatus enum (same as robot.status values)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Model detection
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
model_name = type(robot).__name__ # "LitterRobot4", "LitterRobot3", etc.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Model-specific behaviour
|
|
51
|
+
|
|
52
|
+
| Feature | LR3 | LR4 | LR5 |
|
|
53
|
+
|---|---|---|---|
|
|
54
|
+
| `set_sleep_mode()` | Simple on/off | Per-weekday schedule | TBD |
|
|
55
|
+
| Night light | `set_night_light_brightness(int)` | `set_night_light_mode(NightLightMode)` | TBD |
|
|
56
|
+
|
|
57
|
+
Use `asher/robot_adapters.py` for all model-specific dispatch — do not branch on `type(robot).__name__` inline in command handlers.
|
|
58
|
+
|
|
59
|
+
## Timing / cloud queuing gotcha
|
|
60
|
+
|
|
61
|
+
`sendLitterRobot4Command` returns when the cloud **queues** the command, not when the robot applies it.
|
|
62
|
+
- Toggle commands (lock, unlock, night-light): use optimistic UI updates, not `refresh()` after the call.
|
|
63
|
+
- State-unknown commands (sleep, wake): use `asyncio.sleep(2)` + `refresh()` + `_refresh_status()` as best-effort.
|
|
64
|
+
|
|
65
|
+
## Safe attribute access pattern
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
level = getattr(robot, "waste_drawer_level", None)
|
|
69
|
+
if level is not None:
|
|
70
|
+
...
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Use `getattr(..., default)` or `try/except AttributeError` for any attribute not guaranteed on all models.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: release
|
|
3
|
+
description: Cut a new asher-cli release. Runs preflight checks, bumps the version with bump-my-version, pushes the tag, and confirms the GitHub Actions release workflow is triggered. Invoke when user says "release", "cut a release", "bump version", "publish", or "ship".
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Release Skill
|
|
8
|
+
|
|
9
|
+
Walk through the release process in order. Stop and report if any step fails.
|
|
10
|
+
|
|
11
|
+
## Steps
|
|
12
|
+
|
|
13
|
+
1. **Check working tree is clean**
|
|
14
|
+
Run `git status --porcelain`. If there are uncommitted changes, stop and tell the user to commit or stash them first.
|
|
15
|
+
|
|
16
|
+
2. **Run full CI checks locally**
|
|
17
|
+
Run `uv run poe check` (lint + format + types + tests). All must pass before bumping.
|
|
18
|
+
|
|
19
|
+
3. **Confirm the bump level**
|
|
20
|
+
Ask the user: patch / minor / major? Default to `patch` for bug fixes, `minor` for new features.
|
|
21
|
+
|
|
22
|
+
4. **Bump the version**
|
|
23
|
+
Run `uv run bump-my-version bump <level>`.
|
|
24
|
+
This automatically:
|
|
25
|
+
- Updates `version` in `pyproject.toml`
|
|
26
|
+
- Runs `uv lock --no-sync` (pre-commit hook)
|
|
27
|
+
- Stages `uv.lock` and commits with a version bump message
|
|
28
|
+
- Creates a git tag `v<new_version>`
|
|
29
|
+
|
|
30
|
+
5. **Push the commit and tag**
|
|
31
|
+
Run `git push origin main --tags`.
|
|
32
|
+
The `release.yml` GitHub Actions workflow triggers on new tags matching `v*`.
|
|
33
|
+
|
|
34
|
+
6. **Confirm the workflow triggered**
|
|
35
|
+
Run `gh run list --workflow=release.yml --limit=3` and show the user the latest run status.
|
|
36
|
+
|
|
37
|
+
7. **Report done**
|
|
38
|
+
Show the new version number and the expected PyPI URL once the workflow completes.
|
|
39
|
+
|
|
40
|
+
## Notes
|
|
41
|
+
- The pre-push hook (`.githooks/pre-push`) runs ruff + mypy — it must be active: `git config core.hooksPath .githooks`
|
|
42
|
+
- If bump-my-version fails due to a dirty tree (e.g. uv.lock not staged), check that the `pre_commit_hooks` in `pyproject.toml` ran correctly
|
|
43
|
+
- Never `--skip` the pre-push hook
|
|
@@ -12,8 +12,8 @@ jobs:
|
|
|
12
12
|
lint:
|
|
13
13
|
runs-on: ubuntu-latest
|
|
14
14
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
16
|
-
- uses: astral-sh/setup-uv@
|
|
15
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
16
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
17
17
|
- run: uv sync --dev
|
|
18
18
|
- run: uv run ruff check .
|
|
19
19
|
- run: uv run ruff format --check .
|
|
@@ -28,7 +28,7 @@ jobs:
|
|
|
28
28
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
29
29
|
runs-on: ${{ matrix.os }}
|
|
30
30
|
steps:
|
|
31
|
-
- uses: actions/checkout@
|
|
32
|
-
- uses: astral-sh/setup-uv@
|
|
31
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
32
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
33
33
|
- run: uv sync --dev
|
|
34
34
|
- run: uv run pytest tests/ -v --tb=short
|
|
@@ -27,13 +27,13 @@ jobs:
|
|
|
27
27
|
|
|
28
28
|
steps:
|
|
29
29
|
- name: Checkout repository
|
|
30
|
-
uses: actions/checkout@
|
|
30
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
31
31
|
with:
|
|
32
32
|
fetch-depth: 1
|
|
33
33
|
|
|
34
34
|
- name: Run Claude Code Review
|
|
35
35
|
id: claude-review
|
|
36
|
-
uses: anthropics/claude-code-action@v1
|
|
36
|
+
uses: anthropics/claude-code-action@428971d2ecd6e3a7cb0ee0da2a3a8b33fdb3678d # 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'
|
|
@@ -26,13 +26,13 @@ jobs:
|
|
|
26
26
|
actions: read # Required for Claude to read CI results on PRs
|
|
27
27
|
steps:
|
|
28
28
|
- name: Checkout repository
|
|
29
|
-
uses: actions/checkout@
|
|
29
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
30
30
|
with:
|
|
31
31
|
fetch-depth: 1
|
|
32
32
|
|
|
33
33
|
- name: Run Claude Code
|
|
34
34
|
id: claude
|
|
35
|
-
uses: anthropics/claude-code-action@v1
|
|
35
|
+
uses: anthropics/claude-code-action@428971d2ecd6e3a7cb0ee0da2a3a8b33fdb3678d # v1
|
|
36
36
|
with:
|
|
37
37
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
38
38
|
|
|
@@ -12,13 +12,13 @@ jobs:
|
|
|
12
12
|
coverage:
|
|
13
13
|
runs-on: ubuntu-latest
|
|
14
14
|
steps:
|
|
15
|
-
- uses: actions/checkout@
|
|
16
|
-
- uses: astral-sh/setup-uv@
|
|
15
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
16
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
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
20
|
- name: Upload to Coveralls
|
|
21
|
-
uses: coverallsapp/github-action@v2
|
|
21
|
+
uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2
|
|
22
22
|
with:
|
|
23
23
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
24
24
|
path-to-lcov: coverage.lcov
|
|
@@ -16,13 +16,13 @@ jobs:
|
|
|
16
16
|
outputs:
|
|
17
17
|
version: ${{ steps.version.outputs.version }}
|
|
18
18
|
steps:
|
|
19
|
-
- uses: actions/checkout@
|
|
19
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
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@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
24
24
|
- run: uv build
|
|
25
|
-
- uses: actions/upload-artifact@
|
|
25
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
|
26
26
|
with:
|
|
27
27
|
name: dist
|
|
28
28
|
path: dist/
|
|
@@ -33,19 +33,19 @@ jobs:
|
|
|
33
33
|
runs-on: ubuntu-latest
|
|
34
34
|
environment: pypi
|
|
35
35
|
steps:
|
|
36
|
-
- uses: actions/download-artifact@
|
|
36
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
37
37
|
with:
|
|
38
38
|
name: dist
|
|
39
39
|
path: dist/
|
|
40
|
-
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
40
|
+
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # release/v1
|
|
41
41
|
|
|
42
42
|
github-release:
|
|
43
43
|
name: Create GitHub Release
|
|
44
44
|
needs: [build, publish]
|
|
45
45
|
runs-on: ubuntu-latest
|
|
46
46
|
steps:
|
|
47
|
-
- uses: actions/checkout@
|
|
48
|
-
- uses: actions/download-artifact@
|
|
47
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
48
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
|
49
49
|
with:
|
|
50
50
|
name: dist
|
|
51
51
|
path: dist/
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Asher CLI — CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Terminal dashboard for Litter Robot (LR3/LR4/LR5) via the Whisker cloud API.
|
|
4
|
+
|
|
5
|
+
## Stack
|
|
6
|
+
|
|
7
|
+
- **Python 3.10+**
|
|
8
|
+
- **Textual** — async TUI framework (`textual>=0.47`)
|
|
9
|
+
- **pylitterbot** — unofficial Whisker API wrapper (`pylitterbot>=3.0`)
|
|
10
|
+
- **python-dotenv** — credential loading (`.env` fallback)
|
|
11
|
+
- **keyring>=24** — OS credential store (Windows Credential Manager / macOS Keychain / Linux Secret Service)
|
|
12
|
+
|
|
13
|
+
## Tooling
|
|
14
|
+
|
|
15
|
+
- **uv** — dependency management and task runner (`uv sync`, `uv run`)
|
|
16
|
+
- **poethepoet** — task aliases via `uv run poe <task>`
|
|
17
|
+
- **ruff** — linter and formatter
|
|
18
|
+
- **mypy** — static type checking
|
|
19
|
+
- **pytest + pytest-asyncio + pytest-cov** — tests
|
|
20
|
+
- **textual-dev** — CSS hot reload devtools
|
|
21
|
+
- **watchfiles** — Python auto-restart on file change
|
|
22
|
+
|
|
23
|
+
## Entry points
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
python app.py # compatibility shim (calls asher/__main__.py)
|
|
27
|
+
python -m asher # run as module
|
|
28
|
+
asher # after: uv sync && uv run asher OR pip install -e .
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Package structure
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
asher/
|
|
35
|
+
__init__.py
|
|
36
|
+
app.py AsherApp class (thin orchestrator — composes mixins)
|
|
37
|
+
auth.py LoginScreen modal (ModalScreen[tuple[str,str]]) — available, not primary flow
|
|
38
|
+
helpers.py fmt_ago(), drawer_bar(), ts(), robot_model() (pure, testable)
|
|
39
|
+
constants.py STATUS_COLORS, ROBOT_MODELS
|
|
40
|
+
cats.py CATS dict (ASCII art)
|
|
41
|
+
login_flow.py LoginFlow state machine — inline email/password prompt in command bar
|
|
42
|
+
robot_protocol.py RobotProtocol structural Protocol for pylitterbot robot objects
|
|
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
|
|
46
|
+
__main__.py main() entry point
|
|
47
|
+
commands/
|
|
48
|
+
base.py Command ABC, SlashCommand, CommandRegistry
|
|
49
|
+
__init__.py CommandsMixin — all command classes + registry + dispatch
|
|
50
|
+
connection/ ConnectionMixin — keyring auth, _connect_worker, keyring helpers
|
|
51
|
+
monitoring/ MonitoringMixin — _poll_status_interval, _refresh_status
|
|
52
|
+
ui/ UIMixin — CSS, compose(), log helpers, cat helpers
|
|
53
|
+
slash-commands/ Convention doc
|
|
54
|
+
|
|
55
|
+
tests/
|
|
56
|
+
testhelpers.py unit tests for helpers.py
|
|
57
|
+
test_cats.py CATS dict structure
|
|
58
|
+
test_auth.py LoginScreen CSS / structure
|
|
59
|
+
test_auth_pilot.py Textual Pilot integration tests for LoginScreen
|
|
60
|
+
test_app_pilot.py Textual Pilot integration tests for AsherApp
|
|
61
|
+
test_commands_pilot.py Textual Pilot integration tests for command dispatch
|
|
62
|
+
test_connection.py keyring helper functions
|
|
63
|
+
test_connection_mixin.py ConnectionMixin structure
|
|
64
|
+
test_monitoring.py MonitoringMixin async methods
|
|
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
|
|
69
|
+
|
|
70
|
+
.github/workflows/
|
|
71
|
+
ci.yml ruff + mypy + pytest on every push/PR
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Credentials
|
|
75
|
+
|
|
76
|
+
Priority order on startup:
|
|
77
|
+
|
|
78
|
+
1. **OS keyring** — set automatically after first interactive login
|
|
79
|
+
2. **`.env` file** — fallback for existing users / CI
|
|
80
|
+
3. **Inline login flow** — shown when no credentials found anywhere (email → password prompt in command bar)
|
|
81
|
+
|
|
82
|
+
`.env` variable names (for fallback):
|
|
83
|
+
```
|
|
84
|
+
LITTER_ROBOT_USER=...
|
|
85
|
+
LITTER_ROBOT_PASSWORD=...
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Keyring service name: `asher-cli`, keys `email` and `password`.
|
|
89
|
+
Helper functions in `asher/connection/__init__.py`: `_keyring_load()`, `_keyring_save()`, `_keyring_delete()`.
|
|
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
|
+
|
|
101
|
+
## Command convention
|
|
102
|
+
|
|
103
|
+
**Normal commands** (no prefix) — robot actions only:
|
|
104
|
+
`clean`, `status`, `lock`, `unlock`, `sleep`, `wake`, `night-light on|off|auto`, `night-light-brightness <level>`, `history`, `clear`, `help`
|
|
105
|
+
|
|
106
|
+
**Slash commands** (`/` prefix) — app management only:
|
|
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`
|
|
108
|
+
|
|
109
|
+
**Special cases** (accepted both with and without `/`):
|
|
110
|
+
`exit`, `quit`, `q` — exit the app
|
|
111
|
+
|
|
112
|
+
Do not add robot-control commands as slash commands, and do not add app-management commands as bare commands.
|
|
113
|
+
|
|
114
|
+
## Architecture
|
|
115
|
+
|
|
116
|
+
**Status bar philosophy:**
|
|
117
|
+
- **Top row** — ambient/settings info (robot name, online badge, night light mode + brightness). Not time-critical.
|
|
118
|
+
- **Second row** — important operational state and cat data (drawer %, litter %, cat weight, last visit). Could be subject to change
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
AsherApp (textual.App)
|
|
122
|
+
├── #status-bar top dock — two rows (top: name/online/night-light/lock; bottom: drawer/litter/weight/visit)
|
|
123
|
+
├── #main-area
|
|
124
|
+
│ ├── #log RichLog — scrollable event/command output
|
|
125
|
+
│ └── #cat-panel animated ASCII cat sidebar
|
|
126
|
+
└── #bottom-dock bottom dock
|
|
127
|
+
├── #input-bar / #input-row command prompt ("> " label + CmdInput)
|
|
128
|
+
└── #hint-bar shortcut hints / login flow prompts
|
|
129
|
+
|
|
130
|
+
LoginScreen (ModalScreen) — available in auth.py but not the primary auth path
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Key methods
|
|
134
|
+
|
|
135
|
+
| Method | Purpose |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `_connect_worker()` | `@work` — resolve credentials (keyring → .env → inline login), authenticate |
|
|
138
|
+
| `_refresh_status()` | update all header widgets from robot state |
|
|
139
|
+
| `_poll_status_interval()` | `@work` — poll fallback every 300s (5 min); WebSocket is primary |
|
|
140
|
+
| `_tick_cat()` | advances multi-frame cat animation every 0.4s |
|
|
141
|
+
| `_dispatch_command(command, args)` | `@work` — calls `command.run(app, args)` from the registry |
|
|
142
|
+
| `on_input_submitted()` | routes input to login flow or `_dispatch_command` via `CommandRegistry` |
|
|
143
|
+
| `_start_login_flow()` | begin inline email/password prompt in command bar |
|
|
144
|
+
| `_cmd_logout()` | delete creds from keyring, disconnect |
|
|
145
|
+
| `make_adapter(robot)` | factory in `robot_adapters.py` — returns correct `RobotAdapter` subclass |
|
|
146
|
+
| `_log_ok/err/warn/info()` | timestamped log helpers |
|
|
147
|
+
|
|
148
|
+
## Robot compatibility
|
|
149
|
+
|
|
150
|
+
pylitterbot auto-detects robot type. Commands that differ per model are handled by `RobotAdapter` subclasses in `robot_adapters.py` — `make_adapter(robot)` returns the right one based on `type(robot).__name__`. Status-bar reads use `getattr(..., default)` for graceful degradation on older models. Tested API surface:
|
|
151
|
+
|
|
152
|
+
- `robot.name`, `robot.serial`, `robot.is_online`
|
|
153
|
+
- `robot.status` (LitterBoxStatus enum)
|
|
154
|
+
- `robot.waste_drawer_level` (0–100)
|
|
155
|
+
- `robot.sleep_mode_enabled`, `robot.panel_lock_enabled`, `robot.night_light_mode_enabled`
|
|
156
|
+
- `robot.last_seen` (datetime)
|
|
157
|
+
- `robot.refresh()`, `robot.start_cleaning()`
|
|
158
|
+
- `robot.set_sleep_mode(bool)`, `robot.set_panel_lockout(bool)`
|
|
159
|
+
- `robot.set_night_light_brightness(int)` or `robot.set_night_light_mode(NightLightMode)`
|
|
160
|
+
- `robot.get_activity_history(limit=int)` → list of `Activity` objects with `.timestamp` and `.action` (`LitterBoxStatus` enum)
|
|
161
|
+
|
|
162
|
+
## Development notes
|
|
163
|
+
|
|
164
|
+
- Textual and pylitterbot are both asyncio-native — compose cleanly with `@work` tasks
|
|
165
|
+
- All command execution runs in `@work` async workers to keep the UI responsive
|
|
166
|
+
- Cat modes: `idle`, `happy`, `cleaning` (animated), `sleeping`, `error`, `full`
|
|
167
|
+
- `VERSION` is read from `importlib.metadata.version("asher-cli")` — falls back to `"dev"` when running from source
|
|
168
|
+
- The primary login path is the inline flow in `login_flow.py` (`LoginFlow` state machine: `IDLE` → `AWAITING_EMAIL` → `AWAITING_PASSWORD`). `LoginScreen` (`auth.py`) still exists as a modal but is not used in the current main flow.
|
|
169
|
+
- `LoginScreen` uses `event.stop()` on `Input.Submitted` and `Button.Pressed` to prevent bubbling to the App's `on_input_submitted` (relevant if re-activating the modal path)
|
|
170
|
+
|
|
171
|
+
### IoT command timing — optimistic UI updates
|
|
172
|
+
|
|
173
|
+
`sendLitterRobot4Command` (and equivalents) return as soon as the cloud **queues** the command, not when the robot applies it. Calling `robot.refresh()` immediately after gets stale data. The fix for toggle/mode commands (lock, unlock, night-light on/off/auto) is to **update the status bar widget directly** after a successful API call, without waiting for a refresh — the WebSocket subscription will confirm the final state later. Do **not** add `asyncio.sleep(N)` + `refresh()` + `_refresh_status()` for these commands.
|
|
174
|
+
|
|
175
|
+
Commands that need a confirmed cloud state before showing a result (e.g. sleep/wake, where the state isn't known from the command arg alone) use `asyncio.sleep(2)` + `refresh()` + `_refresh_status()` as a best-effort workaround, accepting the risk of briefly stale display.
|
|
176
|
+
|
|
177
|
+
## Common tasks
|
|
178
|
+
|
|
179
|
+
**Add a robot command:** create a class inheriting `Command` in `asher/commands/__init__.py`, implement `async def run(self, app, args)`, and call `_registry.register(MyCommand())`.
|
|
180
|
+
|
|
181
|
+
**Add a slash command:** create a class inheriting `SlashCommand` (sets `prefix = "/"`), implement `async def run(self, app, args)`, register it, and document in `asher/slash-commands/__init__.py`.
|
|
182
|
+
|
|
183
|
+
**Change poll interval:** `self.set_interval(300, ...)` in `on_mount`.
|
|
184
|
+
|
|
185
|
+
**Add a new cat state:** add entry to `CATS` dict in `asher/cats.py` (str for static, list[str] for animated), then call `_set_cat("name", "label")`.
|
|
186
|
+
|
|
187
|
+
**File naming convention:** no underscores in filenames (except Python-required `__init__.py` and `__main__.py`).
|
|
188
|
+
|
|
189
|
+
## Dev workflow
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
uv sync # install all deps (including dev group)
|
|
193
|
+
uv run poe dev # run with CSS hot reload (textual --dev)
|
|
194
|
+
uv run poe watch # run with Python auto-restart on file change (watchfiles)
|
|
195
|
+
uv run poe test # run test suite
|
|
196
|
+
uv run poe check # ruff + mypy + pytest (same as CI)
|
|
197
|
+
uv run poe fix # auto-fix ruff issues
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Pre-push hook (`.githooks/pre-push`) runs: ruff check → ruff format --check → mypy. Tests are not in the hook — run them manually.
|
|
201
|
+
|
|
202
|
+
## Testing notes
|
|
203
|
+
|
|
204
|
+
- Pilot-based integration tests use `app.run_test()` with `await pilot.pause()` before querying widgets
|
|
205
|
+
- Helper app wrappers for screens must **not** start with `Test` (pytest will try to collect them); use e.g. `LoginTestApp`
|
|
206
|
+
- Mock external deps with `unittest.mock.AsyncMock` for async robot/account methods
|
|
207
|
+
- `from pylitterbot import Account` is a local import inside `_connect_worker` — patch it at `pylitterbot.Account`, not `asher.connection.Account`
|
|
208
|
+
- Coverage: ~76% overall; main gaps are async exception paths and `_connect_worker` auth flow
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: asher-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5
|
|
4
4
|
Summary: Terminal dashboard for Litter Robot (LR3/LR4/LR5) via the Whisker cloud API
|
|
5
5
|
License: MIT License
|
|
6
6
|
|
|
@@ -35,16 +35,19 @@ 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
|
|
38
|
+
Requires-Dist: pylitterbot==2025.5.0
|
|
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
|
+
Requires-Dist: tzlocal>=5.4
|
|
43
|
+
Provides-Extra: mcp
|
|
44
|
+
Requires-Dist: pylitterbot[mcp]==2025.5.0; extra == 'mcp'
|
|
42
45
|
Description-Content-Type: text/markdown
|
|
43
46
|
|
|
44
47
|
# Asher CLI
|
|
45
48
|
|
|
46
49
|
[](https://pypi.org/project/asher-cli/)
|
|
47
|
-

|
|
48
51
|
[](https://pypi.org/project/asher-cli/)
|
|
49
52
|
[](LICENSE)
|
|
50
53
|
[](https://github.com/karanshukla/asher-cli/actions/workflows/ci.yml)
|
|
@@ -60,11 +63,11 @@ A Claude Code-style terminal dashboard for monitoring and controlling Litter Rob
|
|
|
60
63
|
- Live status bar — unit name, online/offline, drawer fill level, last activity, cat weight
|
|
61
64
|
- Human-readable robot status — translates raw API states into plain English (`Ready`, `Cleaning`, `Cat Detected`, `Drawer Full`, etc.)
|
|
62
65
|
- Scrollable activity log with timestamps
|
|
63
|
-
- Commands: `clean`, `status`, `lock`, `unlock`, `sleep`, `wake`, `night-light`, `history`, `help`, `quit`
|
|
64
|
-
- Slash commands for app management: `/login`, `/logout`, `/exit`
|
|
66
|
+
- Commands: `clean`, `status`, `lock`, `unlock`, `sleep`, `wake`, `night-light on|off|auto`, `night-light-brightness`, `history`, `export [days|month]`, `help`, `quit`
|
|
67
|
+
- Slash commands for app management: `/login`, `/logout`, `/robots`, `/robot <index|name>`, `/pets`, `/pet <index|name>`, `/cat on|off|color <hex>`, `/refresh [seconds|off]`, `/config`, `/exit`
|
|
65
68
|
- Cat animation panel that reacts to robot state
|
|
66
69
|
- Command history (↑/↓ arrows)
|
|
67
|
-
-
|
|
70
|
+
- Real-time updates via WebSocket; 5-minute poll fallback
|
|
68
71
|
|
|
69
72
|
## Install
|
|
70
73
|
|
|
@@ -124,8 +127,10 @@ LITTER_ROBOT_PASSWORD=yourpassword
|
|
|
124
127
|
| `info` | Full dump of all robot properties (serial, firmware, all settings) |
|
|
125
128
|
| `lock` / `unlock` | Toggle panel lockout |
|
|
126
129
|
| `sleep` / `wake` | Toggle sleep mode |
|
|
127
|
-
| `night-light on\|off` |
|
|
130
|
+
| `night-light on\|off\|auto` | Set night light mode |
|
|
131
|
+
| `night-light-brightness <level>` | Set brightness (LR5: 0-100; LR4: 25/50/100) |
|
|
128
132
|
| `history` | Show last 25 activity events |
|
|
133
|
+
| `export [days\|month]` | Export activity history to CSV in `~/Downloads` (default: 30 days) |
|
|
129
134
|
| `clear` | Clear the log |
|
|
130
135
|
| `help` | Show command list |
|
|
131
136
|
| `quit` | Exit |
|
|
@@ -136,6 +141,14 @@ LITTER_ROBOT_PASSWORD=yourpassword
|
|
|
136
141
|
|---|---|
|
|
137
142
|
| `/login` | Sign in or switch accounts |
|
|
138
143
|
| `/logout` | Sign out and clear saved credentials |
|
|
144
|
+
| `/robots` | List all robots on the account |
|
|
145
|
+
| `/robot <index\|name>` | Switch active robot (selection persists to keyring) |
|
|
146
|
+
| `/pets` | List all pets on the account |
|
|
147
|
+
| `/pet <index\|name>` | Switch which pet's name/weight shows in the status bar |
|
|
148
|
+
| `/cat on\|off` | Show or hide the cat animation panel |
|
|
149
|
+
| `/cat color <hex>` | Change the cat art colour (e.g. `/cat color #ff79c6`); `/cat reset` to revert |
|
|
150
|
+
| `/refresh [seconds\|off]` | Change the auto-poll interval or disable it (`/refresh 60`, `/refresh off`) |
|
|
151
|
+
| `/config` | Show current runtime settings (robot, refresh rate, cat panel, active pet) |
|
|
139
152
|
| `/exit` | Exit Asher CLI |
|
|
140
153
|
|
|
141
154
|
**Keyboard shortcuts:** `Ctrl+L` clears the log, `Ctrl+C` quits.
|
|
@@ -143,12 +156,14 @@ LITTER_ROBOT_PASSWORD=yourpassword
|
|
|
143
156
|
## Releasing
|
|
144
157
|
|
|
145
158
|
```bash
|
|
146
|
-
# bump version, commit, and tag in one step
|
|
159
|
+
# bump version, commit, and tag in one step, then push with tags
|
|
147
160
|
uv run bump-my-version bump patch # 0.0.1 → 0.0.2
|
|
148
161
|
uv run bump-my-version bump minor # 0.0.2 → 0.1.0
|
|
149
162
|
uv run bump-my-version bump major # 0.1.0 → 1.0.0
|
|
150
163
|
|
|
151
|
-
|
|
164
|
+
git push && git push --tags
|
|
165
|
+
|
|
166
|
+
# then push the release branch to trigger PyPI publish with the new semver
|
|
152
167
|
git checkout -b release/0.0.2
|
|
153
168
|
git push origin release/0.0.2
|
|
154
169
|
```
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Asher CLI
|
|
2
2
|
|
|
3
3
|
[](https://pypi.org/project/asher-cli/)
|
|
4
|
-

|
|
5
5
|
[](https://pypi.org/project/asher-cli/)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://github.com/karanshukla/asher-cli/actions/workflows/ci.yml)
|
|
@@ -17,11 +17,11 @@ A Claude Code-style terminal dashboard for monitoring and controlling Litter Rob
|
|
|
17
17
|
- Live status bar — unit name, online/offline, drawer fill level, last activity, cat weight
|
|
18
18
|
- Human-readable robot status — translates raw API states into plain English (`Ready`, `Cleaning`, `Cat Detected`, `Drawer Full`, etc.)
|
|
19
19
|
- Scrollable activity log with timestamps
|
|
20
|
-
- Commands: `clean`, `status`, `lock`, `unlock`, `sleep`, `wake`, `night-light`, `history`, `help`, `quit`
|
|
21
|
-
- Slash commands for app management: `/login`, `/logout`, `/exit`
|
|
20
|
+
- Commands: `clean`, `status`, `lock`, `unlock`, `sleep`, `wake`, `night-light on|off|auto`, `night-light-brightness`, `history`, `export [days|month]`, `help`, `quit`
|
|
21
|
+
- Slash commands for app management: `/login`, `/logout`, `/robots`, `/robot <index|name>`, `/pets`, `/pet <index|name>`, `/cat on|off|color <hex>`, `/refresh [seconds|off]`, `/config`, `/exit`
|
|
22
22
|
- Cat animation panel that reacts to robot state
|
|
23
23
|
- Command history (↑/↓ arrows)
|
|
24
|
-
-
|
|
24
|
+
- Real-time updates via WebSocket; 5-minute poll fallback
|
|
25
25
|
|
|
26
26
|
## Install
|
|
27
27
|
|
|
@@ -81,8 +81,10 @@ LITTER_ROBOT_PASSWORD=yourpassword
|
|
|
81
81
|
| `info` | Full dump of all robot properties (serial, firmware, all settings) |
|
|
82
82
|
| `lock` / `unlock` | Toggle panel lockout |
|
|
83
83
|
| `sleep` / `wake` | Toggle sleep mode |
|
|
84
|
-
| `night-light on\|off` |
|
|
84
|
+
| `night-light on\|off\|auto` | Set night light mode |
|
|
85
|
+
| `night-light-brightness <level>` | Set brightness (LR5: 0-100; LR4: 25/50/100) |
|
|
85
86
|
| `history` | Show last 25 activity events |
|
|
87
|
+
| `export [days\|month]` | Export activity history to CSV in `~/Downloads` (default: 30 days) |
|
|
86
88
|
| `clear` | Clear the log |
|
|
87
89
|
| `help` | Show command list |
|
|
88
90
|
| `quit` | Exit |
|
|
@@ -93,6 +95,14 @@ LITTER_ROBOT_PASSWORD=yourpassword
|
|
|
93
95
|
|---|---|
|
|
94
96
|
| `/login` | Sign in or switch accounts |
|
|
95
97
|
| `/logout` | Sign out and clear saved credentials |
|
|
98
|
+
| `/robots` | List all robots on the account |
|
|
99
|
+
| `/robot <index\|name>` | Switch active robot (selection persists to keyring) |
|
|
100
|
+
| `/pets` | List all pets on the account |
|
|
101
|
+
| `/pet <index\|name>` | Switch which pet's name/weight shows in the status bar |
|
|
102
|
+
| `/cat on\|off` | Show or hide the cat animation panel |
|
|
103
|
+
| `/cat color <hex>` | Change the cat art colour (e.g. `/cat color #ff79c6`); `/cat reset` to revert |
|
|
104
|
+
| `/refresh [seconds\|off]` | Change the auto-poll interval or disable it (`/refresh 60`, `/refresh off`) |
|
|
105
|
+
| `/config` | Show current runtime settings (robot, refresh rate, cat panel, active pet) |
|
|
96
106
|
| `/exit` | Exit Asher CLI |
|
|
97
107
|
|
|
98
108
|
**Keyboard shortcuts:** `Ctrl+L` clears the log, `Ctrl+C` quits.
|
|
@@ -100,12 +110,14 @@ LITTER_ROBOT_PASSWORD=yourpassword
|
|
|
100
110
|
## Releasing
|
|
101
111
|
|
|
102
112
|
```bash
|
|
103
|
-
# bump version, commit, and tag in one step
|
|
113
|
+
# bump version, commit, and tag in one step, then push with tags
|
|
104
114
|
uv run bump-my-version bump patch # 0.0.1 → 0.0.2
|
|
105
115
|
uv run bump-my-version bump minor # 0.0.2 → 0.1.0
|
|
106
116
|
uv run bump-my-version bump major # 0.1.0 → 1.0.0
|
|
107
117
|
|
|
108
|
-
|
|
118
|
+
git push && git push --tags
|
|
119
|
+
|
|
120
|
+
# then push the release branch to trigger PyPI publish with the new semver
|
|
109
121
|
git checkout -b release/0.0.2
|
|
110
122
|
git push origin release/0.0.2
|
|
111
123
|
```
|