asher-cli 0.0.2__tar.gz → 0.0.3__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.3/.env.example +3 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/.githooks/pre-push +0 -3
- {asher_cli-0.0.2 → asher_cli-0.0.3}/.github/workflows/ci.yml +1 -0
- asher_cli-0.0.3/.github/workflows/claude-code-review.yml +44 -0
- asher_cli-0.0.3/.github/workflows/claude.yml +50 -0
- asher_cli-0.0.3/.github/workflows/coverage.yml +24 -0
- asher_cli-0.0.3/.github/workflows/dependency-audit.yml +37 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/CLAUDE.md +44 -7
- {asher_cli-0.0.2 → asher_cli-0.0.3}/PKG-INFO +75 -18
- {asher_cli-0.0.2 → asher_cli-0.0.3}/README.md +74 -17
- {asher_cli-0.0.2 → asher_cli-0.0.3}/ROADMAP.md +2 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/asher/app.py +7 -4
- asher_cli-0.0.3/asher/cats.py +93 -0
- asher_cli-0.0.3/asher/commands/__init__.py +442 -0
- asher_cli-0.0.3/asher/commands/base.py +81 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/asher/connection/__init__.py +39 -5
- asher_cli-0.0.3/asher/login_flow.py +58 -0
- asher_cli-0.0.3/asher/slash-commands/__init__.py +19 -0
- asher_cli-0.0.3/asher/ui/__init__.py +253 -0
- asher_cli-0.0.3/asher/ui/style.tcss +134 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/pyproject.toml +18 -3
- asher_cli-0.0.3/tests/test_app_pilot.py +211 -0
- asher_cli-0.0.3/tests/test_auth.py +61 -0
- asher_cli-0.0.3/tests/test_auth_pilot.py +95 -0
- asher_cli-0.0.3/tests/test_cats.py +64 -0
- asher_cli-0.0.3/tests/test_commands_pilot.py +290 -0
- asher_cli-0.0.3/tests/test_connection.py +95 -0
- asher_cli-0.0.3/tests/test_connection_mixin.py +19 -0
- asher_cli-0.0.3/tests/test_monitoring.py +125 -0
- asher_cli-0.0.3/tests/test_ui.py +146 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/tests/testhelpers.py +29 -1
- {asher_cli-0.0.2 → asher_cli-0.0.3}/uv.lock +472 -1
- asher_cli-0.0.2/asher/cats.py +0 -40
- asher_cli-0.0.2/asher/commands/__init__.py +0 -350
- asher_cli-0.0.2/asher/slash-commands/__init__.py +0 -26
- asher_cli-0.0.2/asher/ui/__init__.py +0 -279
- {asher_cli-0.0.2 → asher_cli-0.0.3}/.claude/skills/textual/SKILL.md +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/.github/workflows/release.yml +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/.gitignore +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/.vscode/launch.json +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/.vscode/settings.json +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/.vscode/tasks.json +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/LICENSE +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/app.py +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/asher/__init__.py +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/asher/__main__.py +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/asher/auth.py +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/asher/helpers.py +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/asher/monitoring/__init__.py +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/requirements.txt +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/test.py +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/tests/__init__.py +0 -0
- {asher_cli-0.0.2 → asher_cli-0.0.3}/tests/conftest.py +0 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: Claude Code Review
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [opened, synchronize, ready_for_review, reopened]
|
|
6
|
+
# Optional: Only run on specific file changes
|
|
7
|
+
# paths:
|
|
8
|
+
# - "src/**/*.ts"
|
|
9
|
+
# - "src/**/*.tsx"
|
|
10
|
+
# - "src/**/*.js"
|
|
11
|
+
# - "src/**/*.jsx"
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
claude-review:
|
|
15
|
+
# Optional: Filter by PR author
|
|
16
|
+
if: |
|
|
17
|
+
github.event.pull_request.user.login == 'external-contributor' ||
|
|
18
|
+
github.event.pull_request.user.login == 'new-developer' ||
|
|
19
|
+
github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
|
20
|
+
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
pull-requests: read
|
|
25
|
+
issues: read
|
|
26
|
+
id-token: write
|
|
27
|
+
|
|
28
|
+
steps:
|
|
29
|
+
- name: Checkout repository
|
|
30
|
+
uses: actions/checkout@v4
|
|
31
|
+
with:
|
|
32
|
+
fetch-depth: 1
|
|
33
|
+
|
|
34
|
+
- name: Run Claude Code Review
|
|
35
|
+
id: claude-review
|
|
36
|
+
uses: anthropics/claude-code-action@v1
|
|
37
|
+
with:
|
|
38
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
39
|
+
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
|
|
40
|
+
plugins: 'code-review@claude-code-plugins'
|
|
41
|
+
prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
|
|
42
|
+
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
|
43
|
+
# or https://code.claude.com/docs/en/cli-reference for available options
|
|
44
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
name: Claude Code
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issue_comment:
|
|
5
|
+
types: [created]
|
|
6
|
+
pull_request_review_comment:
|
|
7
|
+
types: [created]
|
|
8
|
+
issues:
|
|
9
|
+
types: [opened, assigned]
|
|
10
|
+
pull_request_review:
|
|
11
|
+
types: [submitted]
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
claude:
|
|
15
|
+
if: |
|
|
16
|
+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
17
|
+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
|
|
18
|
+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
|
|
19
|
+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
pull-requests: read
|
|
24
|
+
issues: read
|
|
25
|
+
id-token: write
|
|
26
|
+
actions: read # Required for Claude to read CI results on PRs
|
|
27
|
+
steps:
|
|
28
|
+
- name: Checkout repository
|
|
29
|
+
uses: actions/checkout@v4
|
|
30
|
+
with:
|
|
31
|
+
fetch-depth: 1
|
|
32
|
+
|
|
33
|
+
- name: Run Claude Code
|
|
34
|
+
id: claude
|
|
35
|
+
uses: anthropics/claude-code-action@v1
|
|
36
|
+
with:
|
|
37
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
38
|
+
|
|
39
|
+
# This is an optional setting that allows Claude to read CI results on PRs
|
|
40
|
+
additional_permissions: |
|
|
41
|
+
actions: read
|
|
42
|
+
|
|
43
|
+
# Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
|
|
44
|
+
# prompt: 'Update the pull request description to include a summary of changes.'
|
|
45
|
+
|
|
46
|
+
# Optional: Add claude_args to customize behavior and configuration
|
|
47
|
+
# See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
|
|
48
|
+
# or https://code.claude.com/docs/en/cli-reference for available options
|
|
49
|
+
# claude_args: '--allowed-tools Bash(gh pr *)'
|
|
50
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
name: Coverage
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: "0 6 * * *" # daily at 06:00 UTC
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
coverage:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v3
|
|
17
|
+
- run: uv sync --dev
|
|
18
|
+
- name: Run tests with coverage
|
|
19
|
+
run: uv run pytest tests/ --cov=asher --cov-report=lcov --cov-report=term-missing
|
|
20
|
+
- name: Upload to Coveralls
|
|
21
|
+
uses: coverallsapp/github-action@v2
|
|
22
|
+
with:
|
|
23
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
24
|
+
path-to-lcov: coverage.lcov
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: Dependency Audit
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
schedule:
|
|
5
|
+
- cron: "0 0 * * 1" # every Monday at midnight UTC
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
pull-requests: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
dependency-audit:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: astral-sh/setup-uv@v3
|
|
18
|
+
- run: uv export --format requirements-txt > /tmp/requirements.txt
|
|
19
|
+
- name: Audit Python dependencies
|
|
20
|
+
uses: pypa/gh-action-pip-audit@v1.0.8
|
|
21
|
+
with:
|
|
22
|
+
inputs: /tmp/requirements.txt
|
|
23
|
+
- name: Upgrade lock file
|
|
24
|
+
run: uv lock --upgrade
|
|
25
|
+
- name: Open upgrade PR
|
|
26
|
+
uses: peter-evans/create-pull-request@v7
|
|
27
|
+
with:
|
|
28
|
+
commit-message: "chore: weekly dependency upgrades"
|
|
29
|
+
title: "chore: weekly dependency upgrades"
|
|
30
|
+
body: |
|
|
31
|
+
Automated weekly dependency upgrade via `uv lock --upgrade`.
|
|
32
|
+
|
|
33
|
+
Upgrades packages to their latest versions within the constraints
|
|
34
|
+
defined in `pyproject.toml`. Review the lock file diff before merging.
|
|
35
|
+
branch: chore/weekly-dep-upgrades
|
|
36
|
+
delete-branch: true
|
|
37
|
+
add-paths: uv.lock
|
|
@@ -10,12 +10,22 @@ Terminal dashboard for Litter Robot (LR3/LR4/LR5) via the Whisker cloud API.
|
|
|
10
10
|
- **python-dotenv** — credential loading (`.env` fallback)
|
|
11
11
|
- **keyring>=24** — OS credential store (Windows Credential Manager / macOS Keychain / Linux Secret Service)
|
|
12
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
|
+
|
|
13
23
|
## Entry points
|
|
14
24
|
|
|
15
25
|
```
|
|
16
26
|
python app.py # compatibility shim (calls asher/__main__.py)
|
|
17
27
|
python -m asher # run as module
|
|
18
|
-
asher # after: pip install -e .
|
|
28
|
+
asher # after: uv sync && uv run asher OR pip install -e .
|
|
19
29
|
```
|
|
20
30
|
|
|
21
31
|
## Package structure
|
|
@@ -35,8 +45,16 @@ asher/
|
|
|
35
45
|
slash-commands/ Convention doc + future slash-command registry
|
|
36
46
|
|
|
37
47
|
tests/
|
|
38
|
-
|
|
39
|
-
|
|
48
|
+
testhelpers.py unit tests for helpers.py
|
|
49
|
+
test_cats.py CATS dict structure
|
|
50
|
+
test_auth.py LoginScreen CSS / structure
|
|
51
|
+
test_auth_pilot.py Textual Pilot integration tests for LoginScreen
|
|
52
|
+
test_app_pilot.py Textual Pilot integration tests for AsherApp
|
|
53
|
+
test_commands_pilot.py Textual Pilot integration tests for command dispatch
|
|
54
|
+
test_connection.py keyring helper functions
|
|
55
|
+
test_connection_mixin.py ConnectionMixin structure
|
|
56
|
+
test_monitoring.py MonitoringMixin async methods
|
|
57
|
+
test_ui.py UIMixin constants, CSS, helper existence
|
|
40
58
|
|
|
41
59
|
.github/workflows/
|
|
42
60
|
ci.yml ruff + mypy + pytest on every push/PR
|
|
@@ -52,8 +70,8 @@ Priority order on startup:
|
|
|
52
70
|
|
|
53
71
|
`.env` variable names (for fallback):
|
|
54
72
|
```
|
|
55
|
-
LITTER_ROBOT_USER=...
|
|
56
|
-
LITTER_ROBOT_PASSWORD=...
|
|
73
|
+
LITTER_ROBOT_USER=...
|
|
74
|
+
LITTER_ROBOT_PASSWORD=...
|
|
57
75
|
```
|
|
58
76
|
|
|
59
77
|
Keyring service name: `asher-cli`, keys `email` and `password`.
|
|
@@ -132,6 +150,25 @@ pylitterbot auto-detects robot type. Any attribute/method missing on a given mod
|
|
|
132
150
|
|
|
133
151
|
**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")`.
|
|
134
152
|
|
|
135
|
-
**Run tests:** `pytest tests/` (or `uv run pytest` if using uv).
|
|
136
|
-
|
|
137
153
|
**File naming convention:** no underscores in filenames (except Python-required `__init__.py` and `__main__.py`).
|
|
154
|
+
|
|
155
|
+
## Dev workflow
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
uv sync # install all deps (including dev group)
|
|
159
|
+
uv run poe dev # run with CSS hot reload (textual --dev)
|
|
160
|
+
uv run poe watch # run with Python auto-restart on file change (watchfiles)
|
|
161
|
+
uv run poe test # run test suite
|
|
162
|
+
uv run poe check # ruff + mypy + pytest (same as CI)
|
|
163
|
+
uv run poe fix # auto-fix ruff issues
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Pre-push hook (`.githooks/pre-push`) runs: ruff check → ruff format --check → mypy. Tests are not in the hook — run them manually.
|
|
167
|
+
|
|
168
|
+
## Testing notes
|
|
169
|
+
|
|
170
|
+
- Pilot-based integration tests use `app.run_test()` with `await pilot.pause()` before querying widgets
|
|
171
|
+
- Helper app wrappers for screens must **not** start with `Test` (pytest will try to collect them); use e.g. `LoginTestApp`
|
|
172
|
+
- Mock external deps with `unittest.mock.AsyncMock` for async robot/account methods
|
|
173
|
+
- `from pylitterbot import Account` is a local import inside `_connect_worker` — patch it at `pylitterbot.Account`, not `asher.connection.Account`
|
|
174
|
+
- 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.3
|
|
4
4
|
Summary: Terminal dashboard for Litter Robot (LR3/LR4/LR5) via the Whisker cloud API
|
|
5
5
|
License: MIT License
|
|
6
6
|
|
|
@@ -43,6 +43,14 @@ Description-Content-Type: text/markdown
|
|
|
43
43
|
|
|
44
44
|
# Asher CLI
|
|
45
45
|
|
|
46
|
+
[](https://pypi.org/project/asher-cli/)
|
|
47
|
+

|
|
48
|
+
[](https://pypi.org/project/asher-cli/)
|
|
49
|
+
[](LICENSE)
|
|
50
|
+
[](https://github.com/karanshukla/asher-cli/actions/workflows/ci.yml)
|
|
51
|
+
[](https://coveralls.io/github/karanshukla/asher-cli?branch=main)
|
|
52
|
+
[](https://github.com/astral-sh/ruff)
|
|
53
|
+
|
|
46
54
|
A Claude Code-style terminal dashboard for monitoring and controlling Litter Robot via the Whisker cloud API.
|
|
47
55
|
|
|
48
56
|
<img width="808" height="351" alt="image" src="https://github.com/user-attachments/assets/6599966f-837c-419c-8692-bfda3533e730" />
|
|
@@ -60,6 +68,8 @@ A Claude Code-style terminal dashboard for monitoring and controlling Litter Rob
|
|
|
60
68
|
|
|
61
69
|
## Install
|
|
62
70
|
|
|
71
|
+
### With pipx (recommended)
|
|
72
|
+
|
|
63
73
|
```bash
|
|
64
74
|
pipx install asher-cli
|
|
65
75
|
asher
|
|
@@ -67,34 +77,27 @@ asher
|
|
|
67
77
|
|
|
68
78
|
`pipx` installs the CLI into an isolated environment and puts `asher` on your PATH automatically. Install `pipx` with `pip install pipx` if you don't have it.
|
|
69
79
|
|
|
70
|
-
|
|
80
|
+
### With pip
|
|
71
81
|
|
|
72
82
|
```bash
|
|
73
83
|
pip install asher-cli
|
|
74
84
|
asher
|
|
75
85
|
```
|
|
76
86
|
|
|
77
|
-
|
|
87
|
+
### With uv
|
|
78
88
|
|
|
79
89
|
```bash
|
|
80
|
-
|
|
81
|
-
cd asher-cli
|
|
82
|
-
pip install -e .
|
|
90
|
+
uv tool install asher-cli
|
|
83
91
|
asher
|
|
84
92
|
```
|
|
85
93
|
|
|
86
|
-
###
|
|
94
|
+
### Run from source
|
|
87
95
|
|
|
88
96
|
```bash
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
uv run poe check # ruff + mypy + pytest (same as CI)
|
|
95
|
-
uv run poe fix # auto-fix ruff issues
|
|
96
|
-
uv run poe test # tests only
|
|
97
|
-
uv run poe types # mypy only
|
|
97
|
+
git clone https://github.com/karanshukla/asher-cli
|
|
98
|
+
cd asher-cli
|
|
99
|
+
uv sync
|
|
100
|
+
uv run asher
|
|
98
101
|
```
|
|
99
102
|
|
|
100
103
|
## Credentials
|
|
@@ -179,10 +182,64 @@ python -m pip install pipx
|
|
|
179
182
|
python -m pipx install asher-cli
|
|
180
183
|
```
|
|
181
184
|
|
|
182
|
-
##
|
|
185
|
+
## Dev Setup
|
|
186
|
+
|
|
187
|
+
### 1. Clone and install
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
git clone https://github.com/karanshukla/asher-cli
|
|
191
|
+
cd asher-cli
|
|
192
|
+
uv sync # installs all deps including the dev group
|
|
193
|
+
git config core.hooksPath .githooks # lint + type checks run before every push
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 2. Configure environment (optional)
|
|
197
|
+
|
|
198
|
+
Copy `.env.example` to `.env` and fill in your credentials:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
cp .env.example .env
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
```env
|
|
205
|
+
LITTER_ROBOT_USER=your@email.com
|
|
206
|
+
LITTER_ROBOT_PASSWORD=yourpassword
|
|
207
|
+
ASHER_CLI_DEV_MODE=true # sets version to "dev" instead of the installed package version
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### 3. Run with hot reload
|
|
211
|
+
|
|
212
|
+
**CSS hot reload** — Textual's devtools watch inline `CSS` strings and `.tcss` files and reload them in-place without restarting:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
uv run poe dev
|
|
216
|
+
# equivalent to: textual run --dev asher/__main__.py
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Python auto-restart** — true in-process reload isn't possible with Textual's event loop, but `watchfiles` will kill and relaunch the app whenever a `.py` file changes in `asher/`:
|
|
183
220
|
|
|
184
221
|
```bash
|
|
185
|
-
uv run
|
|
222
|
+
uv run poe watch
|
|
223
|
+
# equivalent to: watchfiles --filter python 'python -m asher' asher
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
You can combine both — run `poe watch` for Python changes and it will naturally pick up CSS changes too on restart.
|
|
227
|
+
|
|
228
|
+
### 4. Run tests
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
uv run poe test # run all tests
|
|
232
|
+
uv run pytest tests/ --cov=asher # with coverage report
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 5. Lint, format, type-check
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
uv run poe fix # auto-fix ruff issues
|
|
239
|
+
uv run poe lint # check only (no fixes)
|
|
240
|
+
uv run poe fmt # check formatting
|
|
241
|
+
uv run poe types # mypy
|
|
242
|
+
uv run poe check # run all of the above + tests (same as CI)
|
|
186
243
|
```
|
|
187
244
|
|
|
188
245
|
CI runs on Python 3.10 / 3.11 / 3.12 across Ubuntu, Windows, and macOS on every push.
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Asher CLI
|
|
2
2
|
|
|
3
|
+
[](https://pypi.org/project/asher-cli/)
|
|
4
|
+

|
|
5
|
+
[](https://pypi.org/project/asher-cli/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://github.com/karanshukla/asher-cli/actions/workflows/ci.yml)
|
|
8
|
+
[](https://coveralls.io/github/karanshukla/asher-cli?branch=main)
|
|
9
|
+
[](https://github.com/astral-sh/ruff)
|
|
10
|
+
|
|
3
11
|
A Claude Code-style terminal dashboard for monitoring and controlling Litter Robot via the Whisker cloud API.
|
|
4
12
|
|
|
5
13
|
<img width="808" height="351" alt="image" src="https://github.com/user-attachments/assets/6599966f-837c-419c-8692-bfda3533e730" />
|
|
@@ -17,6 +25,8 @@ A Claude Code-style terminal dashboard for monitoring and controlling Litter Rob
|
|
|
17
25
|
|
|
18
26
|
## Install
|
|
19
27
|
|
|
28
|
+
### With pipx (recommended)
|
|
29
|
+
|
|
20
30
|
```bash
|
|
21
31
|
pipx install asher-cli
|
|
22
32
|
asher
|
|
@@ -24,34 +34,27 @@ asher
|
|
|
24
34
|
|
|
25
35
|
`pipx` installs the CLI into an isolated environment and puts `asher` on your PATH automatically. Install `pipx` with `pip install pipx` if you don't have it.
|
|
26
36
|
|
|
27
|
-
|
|
37
|
+
### With pip
|
|
28
38
|
|
|
29
39
|
```bash
|
|
30
40
|
pip install asher-cli
|
|
31
41
|
asher
|
|
32
42
|
```
|
|
33
43
|
|
|
34
|
-
|
|
44
|
+
### With uv
|
|
35
45
|
|
|
36
46
|
```bash
|
|
37
|
-
|
|
38
|
-
cd asher-cli
|
|
39
|
-
pip install -e .
|
|
47
|
+
uv tool install asher-cli
|
|
40
48
|
asher
|
|
41
49
|
```
|
|
42
50
|
|
|
43
|
-
###
|
|
51
|
+
### Run from source
|
|
44
52
|
|
|
45
53
|
```bash
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
uv run poe check # ruff + mypy + pytest (same as CI)
|
|
52
|
-
uv run poe fix # auto-fix ruff issues
|
|
53
|
-
uv run poe test # tests only
|
|
54
|
-
uv run poe types # mypy only
|
|
54
|
+
git clone https://github.com/karanshukla/asher-cli
|
|
55
|
+
cd asher-cli
|
|
56
|
+
uv sync
|
|
57
|
+
uv run asher
|
|
55
58
|
```
|
|
56
59
|
|
|
57
60
|
## Credentials
|
|
@@ -136,10 +139,64 @@ python -m pip install pipx
|
|
|
136
139
|
python -m pipx install asher-cli
|
|
137
140
|
```
|
|
138
141
|
|
|
139
|
-
##
|
|
142
|
+
## Dev Setup
|
|
143
|
+
|
|
144
|
+
### 1. Clone and install
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
git clone https://github.com/karanshukla/asher-cli
|
|
148
|
+
cd asher-cli
|
|
149
|
+
uv sync # installs all deps including the dev group
|
|
150
|
+
git config core.hooksPath .githooks # lint + type checks run before every push
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### 2. Configure environment (optional)
|
|
154
|
+
|
|
155
|
+
Copy `.env.example` to `.env` and fill in your credentials:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
cp .env.example .env
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```env
|
|
162
|
+
LITTER_ROBOT_USER=your@email.com
|
|
163
|
+
LITTER_ROBOT_PASSWORD=yourpassword
|
|
164
|
+
ASHER_CLI_DEV_MODE=true # sets version to "dev" instead of the installed package version
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 3. Run with hot reload
|
|
168
|
+
|
|
169
|
+
**CSS hot reload** — Textual's devtools watch inline `CSS` strings and `.tcss` files and reload them in-place without restarting:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
uv run poe dev
|
|
173
|
+
# equivalent to: textual run --dev asher/__main__.py
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Python auto-restart** — true in-process reload isn't possible with Textual's event loop, but `watchfiles` will kill and relaunch the app whenever a `.py` file changes in `asher/`:
|
|
140
177
|
|
|
141
178
|
```bash
|
|
142
|
-
uv run
|
|
179
|
+
uv run poe watch
|
|
180
|
+
# equivalent to: watchfiles --filter python 'python -m asher' asher
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
You can combine both — run `poe watch` for Python changes and it will naturally pick up CSS changes too on restart.
|
|
184
|
+
|
|
185
|
+
### 4. Run tests
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
uv run poe test # run all tests
|
|
189
|
+
uv run pytest tests/ --cov=asher # with coverage report
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 5. Lint, format, type-check
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
uv run poe fix # auto-fix ruff issues
|
|
196
|
+
uv run poe lint # check only (no fixes)
|
|
197
|
+
uv run poe fmt # check formatting
|
|
198
|
+
uv run poe types # mypy
|
|
199
|
+
uv run poe check # run all of the above + tests (same as CI)
|
|
143
200
|
```
|
|
144
201
|
|
|
145
202
|
CI runs on Python 3.10 / 3.11 / 3.12 across Ubuntu, Windows, and macOS on every push.
|
|
@@ -1891,5 +1891,7 @@ Ranked by user-visible impact vs. implementation effort:
|
|
|
1891
1891
|
22. **Desktop notifications** (§20) — `plyer` toasts + `winsound` bell on fault/cat-detected; `/notify on|off` command
|
|
1892
1892
|
23. **Dark/light theme toggle** (§11) — CSS variable swap; nice-to-have but not critical
|
|
1893
1893
|
24. **Startup animation** (§11) — cute but adds friction to quick status checks; could be opt-in
|
|
1894
|
+
25. **E2E test harness** (§15) — Textual Pilot tests for critical user flows; good for preventing regressions but requires maintenance
|
|
1895
|
+
26. **Refactor to be more clean code** - example: have a base command class, and have a property to determine whether its a slash command or not, instead of having two separate methods for slash and non-slash commands. This would reduce code duplication and make it easier to add new commands in the future.
|
|
1894
1896
|
|
|
1895
1897
|
|
|
@@ -17,7 +17,7 @@ from .ui import UIMixin
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class AsherApp(UIMixin, ConnectionMixin, MonitoringMixin, CommandsMixin, App): # type: ignore[type-arg]
|
|
20
|
-
|
|
20
|
+
CSS_PATH = "ui/style.tcss"
|
|
21
21
|
BINDINGS = [
|
|
22
22
|
Binding("ctrl+c", "quit", "Quit", priority=True),
|
|
23
23
|
Binding("ctrl+l", "clear_log", "Clear log"),
|
|
@@ -31,10 +31,12 @@ class AsherApp(UIMixin, ConnectionMixin, MonitoringMixin, CommandsMixin, App):
|
|
|
31
31
|
self._pets: list = []
|
|
32
32
|
self._cat_mode: str = "idle"
|
|
33
33
|
self._cat_frame: int = 0
|
|
34
|
+
self._cat_fx_idx: int = 0
|
|
34
35
|
self._cmd_history: list[str] = []
|
|
35
36
|
self._hist_idx: int = -1
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
from .login_flow import LoginFlow
|
|
38
|
+
|
|
39
|
+
self._login = LoginFlow()
|
|
38
40
|
self._last_cat_seen: Any = None
|
|
39
41
|
self._is_loading: bool = True
|
|
40
42
|
self._spinner_idx: int = 0
|
|
@@ -47,7 +49,7 @@ class AsherApp(UIMixin, ConnectionMixin, MonitoringMixin, CommandsMixin, App):
|
|
|
47
49
|
self._show_loading_state()
|
|
48
50
|
self._connect_worker()
|
|
49
51
|
self.set_interval(30, self._poll_status_interval)
|
|
50
|
-
self.set_interval(0.
|
|
52
|
+
self.set_interval(0.4, self._tick_cat)
|
|
51
53
|
# this doesn't work for some reason :(
|
|
52
54
|
inp = self.query_one("#cmd-input", Input)
|
|
53
55
|
inp.set_styles(self._INPUT_STYLES)
|
|
@@ -62,3 +64,4 @@ class AsherApp(UIMixin, ConnectionMixin, MonitoringMixin, CommandsMixin, App):
|
|
|
62
64
|
if self._account:
|
|
63
65
|
with contextlib.suppress(Exception):
|
|
64
66
|
await self._account.disconnect()
|
|
67
|
+
print("meow")
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""ASCII cat art definitions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _frame(eyes: str, extra: str = "") -> str:
|
|
7
|
+
"""Build a single cat frame with the given eye expression and tail extra."""
|
|
8
|
+
e1, e2 = eyes[0], eyes[2]
|
|
9
|
+
line3 = " \\" + e1 + " " + e2 + "/"
|
|
10
|
+
last = "\\_____/" + extra
|
|
11
|
+
return "\n".join(
|
|
12
|
+
[
|
|
13
|
+
" /\\___/\\",
|
|
14
|
+
" \\/ \\/",
|
|
15
|
+
line3,
|
|
16
|
+
" ==`^ ==",
|
|
17
|
+
" / \\",
|
|
18
|
+
" /| |",
|
|
19
|
+
" || - |",
|
|
20
|
+
" || |",
|
|
21
|
+
" ||| ||_",
|
|
22
|
+
"/\\||_|//",
|
|
23
|
+
last,
|
|
24
|
+
]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# idle: slow blink cycle (6 frames)
|
|
29
|
+
IDLE = [
|
|
30
|
+
_frame("o o"), # 0 eyes open
|
|
31
|
+
_frame("o o"), # 1
|
|
32
|
+
_frame("- -"), # 2 closing
|
|
33
|
+
_frame("- -"), # 3 closed
|
|
34
|
+
_frame("o o"), # 4 opening
|
|
35
|
+
_frame("o o"), # 5
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
# happy: eye squint cycle (6 frames)
|
|
39
|
+
HAPPY = [
|
|
40
|
+
_frame("^ ^"), # 0
|
|
41
|
+
_frame("^ ^"), # 1
|
|
42
|
+
_frame("- -"), # 2 squint
|
|
43
|
+
_frame("^ ^"), # 3
|
|
44
|
+
_frame("^ ^"), # 4
|
|
45
|
+
_frame("- -"), # 5 squint
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
# sleeping: floating zZ (8 frames)
|
|
49
|
+
SLEEPING = [
|
|
50
|
+
_frame("- -", " z"), # 0
|
|
51
|
+
_frame("- -", "zZ"), # 1
|
|
52
|
+
_frame("- -", " Z"), # 2
|
|
53
|
+
_frame("- -", "zZ"), # 3
|
|
54
|
+
_frame("- -", " z"), # 4
|
|
55
|
+
_frame("- -", "zZ"), # 5
|
|
56
|
+
_frame("- -", " Z"), # 6
|
|
57
|
+
_frame("- -", "zZ"), # 7
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
# cleaning: active cycle (6 frames)
|
|
61
|
+
CLEANING = [
|
|
62
|
+
_frame("@ o"), # 0
|
|
63
|
+
_frame("o @"), # 1
|
|
64
|
+
_frame("* *"), # 2
|
|
65
|
+
_frame("@ o"), # 3
|
|
66
|
+
_frame("o @"), # 4
|
|
67
|
+
_frame("* *"), # 5
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
# error: alarm flash (4 frames)
|
|
71
|
+
ERROR = [
|
|
72
|
+
_frame("x x"), # 0
|
|
73
|
+
_frame("x x", "!"), # 1 flash
|
|
74
|
+
_frame("x x"), # 2
|
|
75
|
+
_frame("x x", "!"), # 3 flash
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
# full: agitated eye flash (4 frames)
|
|
79
|
+
FULL = [
|
|
80
|
+
_frame("! !"), # 0
|
|
81
|
+
_frame("x !"), # 1 agitated
|
|
82
|
+
_frame("! !"), # 2
|
|
83
|
+
_frame("! x"), # 3 agitated
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
CATS: dict[str, list[str]] = {
|
|
87
|
+
"idle": IDLE,
|
|
88
|
+
"happy": HAPPY,
|
|
89
|
+
"sleeping": SLEEPING,
|
|
90
|
+
"cleaning": CLEANING,
|
|
91
|
+
"error": ERROR,
|
|
92
|
+
"full": FULL,
|
|
93
|
+
}
|