Move all three skills into skills/; add root README; fix paths
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
312742cfef
commit
d95e5d452d
84 files changed
+97
-31
No files matched your search
@@ -4,3 +4,4 @@ node_modules/
|
||||
.venv/
|
||||
.claude/worktrees/
|
||||
rpa-run-tab/logs/
|
||||
skills/rpa-run-tab/logs/
|
||||
@@ -1,6 +1,6 @@
|
||||
# RPA SKILLS
|
||||
|
||||
Shared Claude skills for building RPA dashboards: `rpa-dashboard-no-ai`, `rpa-dashboard-ai`, and `rpa-run-tab` (Tebra-style Run tab). See `PLAN.md`.
|
||||
Shared Claude skills for RPA bots, all in `skills/`: `rpa-run-tab` (Tebra-style Run tab), `rpa-dashboard-no-ai` and `rpa-dashboard-ai`. See `README.md` and `PLAN.md`.
|
||||
|
||||
## Agent skills
|
||||
|
||||
|
||||
@@ -73,12 +73,12 @@ Build both in `D:\RPA SKILLS\<skill-name>\`. The user installs them into Claude
|
||||
## Status: what already exists
|
||||
|
||||
```
|
||||
rpa-dashboard-no-ai/
|
||||
skills/rpa-dashboard-no-ai/
|
||||
templates/dashboard.html DONE: generic page, works in Qt (QWebChannel) or web (polling)
|
||||
templates/contract.md DONE: event/JSON contract + Tebra lessons
|
||||
templates/bridge.py DONE: minimal PyQt6 host + subprocess log tailing
|
||||
templates/reference/ DONE: tebra_dashboard.html, tebra_bridge_part1/2.py (raw extracts)
|
||||
rpa-dashboard-ai/
|
||||
skills/rpa-dashboard-ai/
|
||||
templates/reference/web/ DONE: Superbill index.css, DESIGN.md, bands.ts, theme.tsx, types.ts, api.ts,
|
||||
components (BrandHeader, Sidebar, StatsBar, Donut, SplitPane,
|
||||
RunPanel, ReviewPane, RoiTab, PdfPane, Tooltip, icons)
|
||||
@@ -86,24 +86,24 @@ rpa-dashboard-ai/
|
||||
```
|
||||
|
||||
## TODO for the implement skill
|
||||
1. **Fix the lint error in `rpa-dashboard-no-ai/templates/bridge.py:15`.** `STEPS = {{STEPS_PY}}` is not valid Python, so the lint hook flags it (F821). Change it to `STEPS = ["{{STEP_1}}", "{{STEP_2}}"]` or similar.
|
||||
2. **Write `rpa-dashboard-no-ai/SKILL.md`.**
|
||||
1. **Fix the lint error in `skills/rpa-dashboard-no-ai/templates/bridge.py:15`.** `STEPS = {{STEPS_PY}}` is not valid Python, so the lint hook flags it (F821). Change it to `STEPS = ["{{STEP_1}}", "{{STEP_2}}"]` or similar.
|
||||
2. **Write `skills/rpa-dashboard-no-ai/SKILL.md`.**
|
||||
- Frontmatter `name` and `description`. The trigger should cover "RPA dashboard", "automation dashboard", "bot dashboard", and "without AI".
|
||||
- The workflow above.
|
||||
- Point to `templates/*`.
|
||||
3. **Write `rpa-dashboard-ai/SKILL.md`.**
|
||||
3. **Write `skills/rpa-dashboard-ai/SKILL.md`.**
|
||||
- Trigger: "AI RPA dashboard", "extraction dashboard", "LLM/OCR review dashboard".
|
||||
- The workflow above.
|
||||
- Web path: React + Vite + Tailwind v4, adapted from `reference/web`.
|
||||
- Desktop path: copy the no-AI `dashboard.html`, then add the AI sections.
|
||||
4. **Write `templates/themes.md` in both skills.** Content is in the Theme presets section above.
|
||||
5. **Write `rpa-dashboard-ai/templates/contract.md`.** It extends the no-AI contract with:
|
||||
5. **Write `skills/rpa-dashboard-ai/templates/contract.md`.** It extends the no-AI contract with:
|
||||
- `Run{job_id,status:pending|queued|running|done|failed,model,pages_done,pages_total,summary:{green,amber,red,unscored}}`
|
||||
- `ReviewRow{row_id,field,value,band,score,reason,verified,corrected_value}`
|
||||
- `RoiRun{model,input_tokens,output_tokens,thinking_tokens,total_cost}`
|
||||
- Endpoints: `PATCH /review/{row_id}`, `POST /runs/{id}/approve` (server gate), `GET /roi`.
|
||||
- Polling: 1.5–2s only while a run is busy.
|
||||
6. **Write `rpa-dashboard-ai/templates/ai-sections.html`.** Plain HTML/CSS/JS versions of the band legend, the review split pane and the ROI tiles, for the desktop path.
|
||||
6. **Write `skills/rpa-dashboard-ai/templates/ai-sections.html`.** Plain HTML/CSS/JS versions of the band legend, the review split pane and the ROI tiles, for the desktop path.
|
||||
7. **Strip project-specific bits from the AI reference files.** Remove ECW/Tebra project names, SPB_* tables and patient/ticket wording, or mark them as such. **Never** ship real patient data (PHI), internal IPs or keys. The reference copies have already been checked for these, but check again after editing.
|
||||
8. **Verify.** Open `dashboard.html` in a browser using sample data. Check light, dark and narrow width. Load each SKILL.md and make sure every path it references exists.
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# RPA SKILLS
|
||||
|
||||
Claude skills for building the dashboard of an RPA bot. All three live in [`skills/`](skills/).
|
||||
|
||||
| Skill | Use it when | What it builds |
|
||||
|---|---|---|
|
||||
| [`rpa-run-tab`](skills/rpa-run-tab/README.md) | You have a PyQt6 desktop bot and only need a Run tab, in about 5 minutes. | The Tebra-style Run tab: trigger button, Excel input, progress, checkpoints, live log, dark mode. |
|
||||
| [`rpa-dashboard-no-ai`](skills/rpa-dashboard-no-ai/SKILL.md) | The bot has no AI step. | A full dashboard: run panel, overview, report, optional sections (checkpoints, admin, login). Desktop (PyQt) or web. |
|
||||
| [`rpa-dashboard-ai`](skills/rpa-dashboard-ai/SKILL.md) | The bot uses an LLM or OCR. | Everything in the no-AI skill, plus confidence bands, review-and-approve (the server refuses approval until fields are verified), and a cost/ROI page. |
|
||||
|
||||
## Install
|
||||
|
||||
Copy the skill folders you need from `skills/` into `~/.claude/skills/`.
|
||||
`rpa-dashboard-ai` reuses files from `rpa-dashboard-no-ai`, so install the two dashboard skills together, side by side.
|
||||
|
||||
## Use
|
||||
|
||||
In your bot's project, ask Claude for what you want, for example *"add the run tab"* or *"build an RPA dashboard"*.
|
||||
If the project already has a frontend, the dashboard skills report it and ask whether to keep its look or restyle it.
|
||||
|
||||
## Repo layout
|
||||
|
||||
| Path | What it is |
|
||||
|---|---|
|
||||
| `skills/` | The three skills |
|
||||
| `tests/` | Template tests, page checks (Playwright) and skill evals. See [`tests/README.md`](tests/README.md). |
|
||||
| `PLAN.md`, `GLOSSARY.md`, `docs/` | Background, domain terms and decisions |
|
||||
| `.scratch/` | Spec and tickets |
|
||||
|
||||
Run all tests: `uv run --with pytest --with playwright pytest tests`
|
||||
@@ -8,4 +8,4 @@ The bot runs as a **separate process** with no Qt. The Run tab starts it with `-
|
||||
|
||||
**Why:** this comes from Tebra Auto Post. UI automation inside a Qt worker thread was unreliable (bug-094). A windowed PyInstaller child's stdout pipe is block-buffered, so log lines arrived in late bursts; reading the log file fixed that. Tagging lines saves each project from writing a table of phrase→friendly-text rules.
|
||||
|
||||
**Conflict:** `rpa-dashboard-no-ai/templates/bot_runner.py` reads `stdout=PIPE`, which is the approach Tebra dropped. Fix it there, or point it at the Run tab skill's runner.
|
||||
**Conflict:** `skills/rpa-dashboard-no-ai/templates/bot_runner.py` reads `stdout=PIPE`, which is the approach Tebra dropped. Fix it there, or point it at the Run tab skill's runner.
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,35 @@
|
||||
# rpa-run-tab
|
||||
|
||||
A Claude skill that adds a **Tebra-style Run tab** to any desktop RPA bot (PyQt6) in about 5 minutes.
|
||||
|
||||
## What you get
|
||||
- **Trigger button** with three states: Upload a file / Trigger automation / Running...
|
||||
- **Excel input card:** drop zone, sheet tabs, a 200-row preview with passwords masked, and a Sample input download
|
||||
- **Progress bar** with an elapsed timer, a step list and a friendly live log
|
||||
- **Checkpoint boxes** with time limits. A stuck checkpoint is killed and shown in red.
|
||||
- **Endings:** green Completed, amber "N of M failed", red Failed or Stuck
|
||||
- **Dark mode** and a close-while-running guard. The bot runs in its own process.
|
||||
|
||||
## Install
|
||||
Copy this folder to `~/.claude/skills/rpa-run-tab/`.
|
||||
|
||||
## Use
|
||||
In your bot project, ask Claude: *"add the run tab"*. The skill:
|
||||
1. Scans your bot. It never runs it.
|
||||
2. Asks three things: **colour** (Tebra Sky, TVL Teal, or your hex), **checkpoints** (names and time limits), and **Excel input or none**.
|
||||
3. Builds `run_tab/` and adds a few `rt.checkpoint()` / `rt.ui()` lines to your bot.
|
||||
4. Checks the result with a fake bot (`python run_tab/run_tab.py --selfcheck`).
|
||||
|
||||
Start it with `python run_tab/run_tab.py`.
|
||||
|
||||
## Files
|
||||
| File | What it is |
|
||||
|---|---|
|
||||
| `SKILL.md` | Steps Claude follows |
|
||||
| `templates/run_tab.html` | The page (layout fixed, colours themable) |
|
||||
| `templates/run_tab.py` | PyQt6 window and bridge, plus the CONFIG block and `--selfcheck` |
|
||||
| `templates/runner.py` | Starts the bot, reads its log, handles stuck checkpoints and the ending |
|
||||
| `templates/rt.py` | Helpers the bot calls |
|
||||
| `templates/fake_bot.py` | Fake bot used by the self-check |
|
||||
| `templates/palette.py` | Colour presets or a custom hex, with a contrast check |
|
||||
| `docs/adr/` | Why it works this way |
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+1
-1
@@ -72,4 +72,4 @@ uv run --with pytest --with playwright pytest tests
|
||||
passwords, checkpoints driving steps/boxes/progress, green/amber/red endings, the no-input
|
||||
mode, the dark toggle and no horizontal scroll, each in light, dark and narrow mode. The
|
||||
Python side covers line parsing, endings, the sample workbook and `palette.py --write`.
|
||||
The end-to-end fake-bot check lives in the skill itself: `python rpa-run-tab/templates/run_tab.py --selfcheck`.
|
||||
The end-to-end fake-bot check lives in the skill itself: `python skills/rpa-run-tab/templates/run_tab.py --selfcheck`.
|
||||
@@ -202,7 +202,7 @@ def main(fixture):
|
||||
shutil.copy(pages[0], results / f"{fixture}.dashboard.html")
|
||||
checks["no placeholders left"] = "{{" not in html
|
||||
if cfg.get("brand"):
|
||||
spec = importlib.util.spec_from_file_location("palette", ROOT / "rpa-dashboard-no-ai/templates/palette.py")
|
||||
spec = importlib.util.spec_from_file_location("palette", ROOT / "skills/rpa-dashboard-no-ai/templates/palette.py")
|
||||
palette = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(palette)
|
||||
t = palette.derive(cfg["brand"])
|
||||
|
||||
@@ -16,7 +16,7 @@ import pytest
|
||||
from page_check_common import FAKE_BRIDGE, MODES, TEST_FILL, WIRED, open_page
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
TEMPLATE = ROOT / "rpa-dashboard-no-ai" / "templates" / "dashboard.html"
|
||||
TEMPLATE = ROOT / "skills" / "rpa-dashboard-no-ai" / "templates" / "dashboard.html"
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
|
||||
@@ -23,7 +23,7 @@ import pytest
|
||||
from page_check_common import MODES, TEST_FILL, load_stub, open_page
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
TEMPLATE = ROOT / "rpa-dashboard-ai" / "templates" / "web"
|
||||
TEMPLATE = ROOT / "skills" / "rpa-dashboard-ai" / "templates" / "web"
|
||||
TEST_FILL = {**TEST_FILL, "APP_NAME": "Test Extractor", "API_BASE": "/api"}
|
||||
RECORD_ACTIONS = """
|
||||
window.__calls = [];
|
||||
|
||||
@@ -22,8 +22,8 @@ import pytest
|
||||
from page_check_common import FAKE_BRIDGE, MODES, TEST_FILL, WIRED, load_stub, open_page
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
NO_AI = ROOT / "rpa-dashboard-no-ai" / "templates" / "dashboard.html"
|
||||
AI = ROOT / "rpa-dashboard-ai" / "templates"
|
||||
NO_AI = ROOT / "skills" / "rpa-dashboard-no-ai" / "templates" / "dashboard.html"
|
||||
AI = ROOT / "skills" / "rpa-dashboard-ai" / "templates"
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
|
||||
@@ -9,7 +9,7 @@ import pytest
|
||||
from page_check_common import FAKE_BRIDGE, MODES, open_page
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
TEMPLATE = ROOT / "rpa-run-tab" / "templates" / "run_tab.html"
|
||||
TEMPLATE = ROOT / "skills" / "rpa-run-tab" / "templates" / "run_tab.html"
|
||||
CFG = {"title": "Test Bot", "sub": "Page check", "input": True, "inputHint": "Test workbook",
|
||||
"sample": True, "darkKey": "test_dark",
|
||||
"checkpoints": [{"name": "Sign in", "max": 300}, {"name": "Process rows", "max": 0},
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import importlib.util
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
BANDS = Path(__file__).resolve().parents[1] / "rpa-dashboard-ai" / "templates" / "web" / "bands.py"
|
||||
BANDS = Path(__file__).resolve().parents[1] / "skills" / "rpa-dashboard-ai" / "templates" / "web" / "bands.py"
|
||||
spec = importlib.util.spec_from_file_location("bands", BANDS)
|
||||
bands = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(bands)
|
||||
|
||||
@@ -7,7 +7,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
RUNNER = Path(__file__).resolve().parents[1] / "rpa-dashboard-no-ai" / "templates" / "bot_runner.py"
|
||||
RUNNER = Path(__file__).resolve().parents[1] / "skills" / "rpa-dashboard-no-ai" / "templates" / "bot_runner.py"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -5,7 +5,7 @@ import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
DETECT = Path(__file__).resolve().parents[1] / "rpa-dashboard-no-ai" / "templates" / "detect_frontend.py"
|
||||
DETECT = Path(__file__).resolve().parents[1] / "skills" / "rpa-dashboard-no-ai" / "templates" / "detect_frontend.py"
|
||||
spec = importlib.util.spec_from_file_location("detect_frontend", DETECT)
|
||||
detect_frontend = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(detect_frontend)
|
||||
|
||||
@@ -4,7 +4,7 @@ import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
PALETTE = Path(__file__).resolve().parents[1] / "rpa-dashboard-no-ai" / "templates" / "palette.py"
|
||||
PALETTE = Path(__file__).resolve().parents[1] / "skills" / "rpa-dashboard-no-ai" / "templates" / "palette.py"
|
||||
spec = importlib.util.spec_from_file_location("palette", PALETTE)
|
||||
palette = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(palette)
|
||||
|
||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
SRC = Path(__file__).resolve().parents[1] / "rpa-dashboard-ai" / "templates" / "web" / "pricing.py"
|
||||
SRC = Path(__file__).resolve().parents[1] / "skills" / "rpa-dashboard-ai" / "templates" / "web" / "pricing.py"
|
||||
spec = importlib.util.spec_from_file_location("pricing", SRC)
|
||||
pricing = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(pricing)
|
||||
|
||||
@@ -6,8 +6,8 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SKILLS = [ROOT / "rpa-dashboard-ai", ROOT / "rpa-dashboard-no-ai"]
|
||||
AI_REFERENCE = ROOT / "rpa-dashboard-ai" / "templates" / "reference"
|
||||
SKILLS = [ROOT / "skills" / "rpa-dashboard-ai", ROOT / "skills" / "rpa-dashboard-no-ai"]
|
||||
AI_REFERENCE = ROOT / "skills" / "rpa-dashboard-ai" / "templates" / "reference"
|
||||
FORBIDDEN = {
|
||||
"internal IP": r"\b(10\.\d{1,3}|192\.168|172\.(1[6-9]|2\d|3[01]))\.\d{1,3}\.\d{1,3}\b",
|
||||
"Google API key": r"AIza[0-9A-Za-z_-]{30,}",
|
||||
|
||||
@@ -4,7 +4,7 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
T = ROOT / "rpa-run-tab" / "templates"
|
||||
T = ROOT / "skills" / "rpa-run-tab" / "templates"
|
||||
sys.path.insert(0, str(T))
|
||||
import runner # noqa: E402
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ from pathlib import Path
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
TEMPLATES = ROOT / "rpa-dashboard-no-ai" / "templates"
|
||||
TEMPLATES = ROOT / "skills" / "rpa-dashboard-no-ai" / "templates"
|
||||
spec = importlib.util.spec_from_file_location("sections", TEMPLATES / "sections.py")
|
||||
sections = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(sections)
|
||||
|
||||
@@ -10,9 +10,9 @@ ROOT = Path(__file__).resolve().parents[1]
|
||||
HOOK_RULES = "E9,F63,F7,F82,F811" # same rule set as the PostToolUse lint hook
|
||||
|
||||
|
||||
@pytest.mark.parametrize("template", ["rpa-dashboard-no-ai/templates/bridge.py",
|
||||
"rpa-dashboard-no-ai/templates/bot_runner.py",
|
||||
"rpa-dashboard-ai/templates/desktop/ai_bridge.py"])
|
||||
@pytest.mark.parametrize("template", ["skills/rpa-dashboard-no-ai/templates/bridge.py",
|
||||
"skills/rpa-dashboard-no-ai/templates/bot_runner.py",
|
||||
"skills/rpa-dashboard-ai/templates/desktop/ai_bridge.py"])
|
||||
def test_template_passes_lint_hook_unfilled(template):
|
||||
ruff = shutil.which("ruff") or pytest.skip("ruff not on PATH")
|
||||
r = subprocess.run([ruff, "check", "--select", HOOK_RULES, "--no-cache", str(ROOT / template)],
|
||||
@@ -21,7 +21,7 @@ def test_template_passes_lint_hook_unfilled(template):
|
||||
|
||||
|
||||
def test_tebra_sky_preset_is_the_template_default():
|
||||
templates = ROOT / "rpa-dashboard-no-ai" / "templates"
|
||||
templates = ROOT / "skills" / "rpa-dashboard-no-ai" / "templates"
|
||||
themes = (templates / "themes.md").read_text(encoding="utf-8")
|
||||
sky = themes.split("## 1. Tebra Sky")[1].split("```css\n")[1].split("```")[0]
|
||||
assert sky in (templates / "dashboard.html").read_text(encoding="utf-8")
|
||||
@@ -29,13 +29,13 @@ def test_tebra_sky_preset_is_the_template_default():
|
||||
|
||||
@pytest.mark.parametrize("skill", ["rpa-dashboard-no-ai", "rpa-dashboard-ai"])
|
||||
def test_skill_description_fits_the_1024_char_limit(skill):
|
||||
text = (ROOT / skill / "SKILL.md").read_text(encoding="utf-8")
|
||||
text = (ROOT / "skills" / skill / "SKILL.md").read_text(encoding="utf-8")
|
||||
description = re.search(r"^description: (.+)$", text, re.MULTILINE)[1]
|
||||
assert len(description) <= 1024, len(description)
|
||||
|
||||
|
||||
def test_ai_skill_references_files_that_exist():
|
||||
skill = ROOT / "rpa-dashboard-ai"
|
||||
skill = ROOT / "skills" / "rpa-dashboard-ai"
|
||||
text = (skill / "SKILL.md").read_text(encoding="utf-8")
|
||||
paths = re.findall(r"`((?:templates/|<no-ai>)[\w./-]+)`", text)
|
||||
assert len(paths) > 5
|
||||
|
||||
Reference in new issue
Block a user