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:
VasanthandClaude Sonnet 5.5 committed 2026-10-16 17:04:38 +05:30
1 parent 312742cfef
commit d95e5d452d
84 files changed
+97 -31

No files matched your search

+1
View File
@@ -4,3 +4,4 @@ node_modules/
.venv/
.claude/worktrees/
rpa-run-tab/logs/
skills/rpa-run-tab/logs/
+1 -1
View File
@@ -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
+7 -7
View File
@@ -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.
+30
View File
@@ -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.
+35
View File
@@ -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
View File
@@ -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`.
+1 -1
View File
@@ -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"])
+1 -1
View File
@@ -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")
+1 -1
View File
@@ -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 = [];
+2 -2
View File
@@ -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")
+1 -1
View File
@@ -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
View File
@@ -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)
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+2 -2
View File
@@ -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,}",
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+6 -6
View File
@@ -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