CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/hubitat-dev

Context for developing and debugging Hubitat Elevation apps, drivers, and hub environment — sandbox constraints, lifecycle idioms, capability contracts, plus grounded deploy/log-tail/lint mechanisms.

74

Quality

93%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

Overview
Quality
Evals
Security
Files

hub_deploy.pyskills/_scripts/

#!/usr/bin/env python3
"""Deploy an app or driver's Groovy source to a Hubitat hub (create or update).

Thin CLI over hubclient.HubClient. If --id is given (or --name matches an existing entry),
it updates that entry with optimistic concurrency — the hub's current version is fetched
and sent, and a stale-version rejection surfaces as a conflict to reconcile, never a blind
retry. Otherwise it creates a new entry and reports the assigned id.

Usage:
    hub_deploy.py --kind driver --source d.groovy --hub main --hubs hubs.json
    hub_deploy.py --kind app --source a.groovy --id 143 --ip 192.0.2.10
    hub_deploy.py --kind driver --source d.groovy --name "My Driver" --ip 192.0.2.10

A save rejection — most often a Groovy compile error — surfaces the hub's errorMessage
verbatim and exits 1, distinct from the version-conflict path so its "re-pull and reconcile"
advice never fires on a typo in your own source.

Exit: 0 on success, 2 on a version conflict (re-pull and reconcile), 1 on other errors.
"""
import argparse
import json
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))
# E402: this import must follow the sys.path insert above so hubclient resolves when run as a script.
from hubclient import HubClient, HubError, DeployConflict, KINDS, resolve_base_from_args  # noqa: E402


def main(argv=None, client_factory=None) -> int:
    p = argparse.ArgumentParser(description="Deploy Hubitat code to a hub.")
    p.add_argument("--kind", required=True, choices=KINDS)
    p.add_argument("--source", required=True, help="path to the .groovy source file")
    p.add_argument("--id", type=int, help="update this existing code id")
    p.add_argument("--name", help="update the existing entry with this name, if present")
    p.add_argument("--ip")
    p.add_argument("--port", type=int, default=8080)
    p.add_argument("--hub")
    p.add_argument("--hubs")
    args = p.parse_args(argv)

    try:
        source = Path(args.source).read_text()
    except OSError as e:
        print(f"cannot read {args.source}: {e}", file=sys.stderr)
        return 1

    try:
        base = resolve_base_from_args(args.ip, args.port, args.hub, args.hubs)
        client = (client_factory or HubClient)(base)
        code_id = args.id
        if code_id is None and args.name:
            code_id = client.find_id(args.kind, args.name)  # None => create
        result = client.deploy(args.kind, source, code_id=code_id)
    except DeployConflict as e:
        print(str(e), file=sys.stderr)
        return 2
    except HubError as e:
        print(str(e), file=sys.stderr)
        return 1

    json.dump({"kind": args.kind, **result}, sys.stdout, indent=2)
    sys.stdout.write("\n")
    return 0


if __name__ == "__main__":
    sys.exit(main())

README.md

tile.json