DevPlace CI / test (push) Failing after 44m45s
Continuous Delivery could never be turned off once a crop was planted. Add a one-shot per-plot "stop after harvest" flag (POST /game/plot/stop, Devii tool game_plot_stop, game_plots.stop_after_harvest), cleared by the next plant, autoreplant claim or Refactor. Autoreplant now also respects crop locks through the shared _crop_lock predicate. Plan and measurements in happyplanting.md. Also includes the pending community sync, node network and related work in the tree; the full suite passed (4999) with all of it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
161 lines
7.1 KiB
Python
161 lines
7.1 KiB
Python
# retoor <retoor@molodetz.nl>
|
|
|
|
import asyncio
|
|
import os
|
|
import sys
|
|
|
|
from devplacepy.cli._shared import _audit_cli
|
|
|
|
|
|
def _prepare():
|
|
from devplacepy.database import init_db
|
|
|
|
init_db()
|
|
|
|
|
|
def _credentials(args) -> dict:
|
|
return {
|
|
"username": args.login_user or "",
|
|
"email": args.login_email or "",
|
|
"password": args.login_password or os.environ.get("COMMUNITY_PASSWORD", ""),
|
|
"totp": args.login_totp or os.environ.get("COMMUNITY_TOTP_SECRET", ""),
|
|
}
|
|
|
|
|
|
def _print_report(report):
|
|
mode = "DRY RUN" if report.dry_run else "LIVE"
|
|
print(f"\n[{mode}] status: {report.status}")
|
|
print(f"collected: {report.collected}")
|
|
for kind, counts in report.pushed.items():
|
|
print(f" {kind}: {counts or 'nothing'}")
|
|
for error in report.errors[:10]:
|
|
print(f" problem: {error}")
|
|
|
|
|
|
def cmd_community_discover(args):
|
|
from devplacepy.services.community.logbook import LogBook
|
|
from devplacepy.services.community.runner import TRIGGER_CLI, CommunityRunner
|
|
from devplacepy.services.community.spec import STATUS_READY
|
|
from devplacepy.services.community.store import store
|
|
|
|
from devplacepy.services.community.discoverer import slug_for_query
|
|
|
|
_prepare()
|
|
runner = CommunityRunner()
|
|
existing = store.get(slug_for_query(args.query))
|
|
if existing and not args.relearn and not args.dry_run:
|
|
print(f"Community '{existing['slug']}' already exists ({existing['status']}); pass --relearn to learn it again")
|
|
if args.run and existing["status"] == STATUS_READY:
|
|
with LogBook(echo=True):
|
|
_print_report(asyncio.run(runner.run(existing, dry_run=False, trigger=TRIGGER_CLI)))
|
|
return
|
|
with LogBook(echo=True):
|
|
result, row, _ = asyncio.run(
|
|
runner.discover(
|
|
args.query,
|
|
"cli",
|
|
dry_run=args.dry_run,
|
|
relearn_slug=existing["slug"] if existing else None,
|
|
credentials=_credentials(args),
|
|
)
|
|
)
|
|
print(f"\n{result.slug or '-'}: {result.status}" + (f" ({result.error})" if result.error else ""))
|
|
if result.status == STATUS_READY:
|
|
for feed in result.schema["feeds"]:
|
|
print(f" feed {feed['id']}: {feed['type']} {feed['url']} comments={feed['comments']['mode']} pagination={feed['pagination']['style']}")
|
|
print(f" sample: { {kind: len(rows) for kind, rows in result.sample['records'].items()} }")
|
|
if args.dry_run:
|
|
print("dry run: nothing was saved")
|
|
else:
|
|
_audit_cli("cli.community.discover", f"CLI discovered community {result.slug}: {result.status}", target_label=result.slug, metadata={"status": result.status})
|
|
if result.status != STATUS_READY and not args.dry_run and result.status != "login_required":
|
|
sys.exit(1)
|
|
if args.run and row and result.status == STATUS_READY and not args.dry_run:
|
|
with LogBook(echo=True):
|
|
_print_report(asyncio.run(runner.run(store.get(row["uid"]), dry_run=False, trigger=TRIGGER_CLI)))
|
|
|
|
|
|
def cmd_community_run(args):
|
|
from devplacepy.services.community.logbook import LogBook
|
|
from devplacepy.services.community.runner import TRIGGER_CLI, CommunityRunner
|
|
from devplacepy.services.community.store import store
|
|
|
|
_prepare()
|
|
row = store.get(args.slug)
|
|
if row is None:
|
|
print(f"Unknown community '{args.slug}'")
|
|
sys.exit(1)
|
|
with LogBook(echo=True):
|
|
report = asyncio.run(CommunityRunner().run(row, dry_run=args.dry_run, trigger=TRIGGER_CLI))
|
|
_print_report(report)
|
|
if not args.dry_run:
|
|
_audit_cli("cli.community.run", f"CLI ran community {row['slug']}: {report.status}", target_label=row["slug"], metadata={"status": report.status, "collected": report.collected})
|
|
if report.status == "failed":
|
|
sys.exit(1)
|
|
|
|
|
|
def cmd_community_login(args):
|
|
from devplacepy.services.community.store import store
|
|
|
|
_prepare()
|
|
row = store.get(args.slug)
|
|
if row is None:
|
|
print(f"Unknown community '{args.slug}'")
|
|
sys.exit(1)
|
|
if args.clear:
|
|
store.update(row["uid"], login_username="", login_email="", login_password="", login_totp_secret="")
|
|
print(f"Removed the stored account of {row['slug']}")
|
|
else:
|
|
secrets = _credentials(args)
|
|
if not secrets["password"] or not (secrets["username"] or secrets["email"]):
|
|
print("Give --login-user or --login-email, and --login-password (or COMMUNITY_PASSWORD)")
|
|
sys.exit(1)
|
|
store.update(row["uid"], login_username=secrets["username"], login_email=secrets["email"], login_password=secrets["password"], login_totp_secret=secrets["totp"])
|
|
print(f"Stored the account for {row['slug']}; run 'community discover \"{row['query']}\" --relearn' to learn behind the login")
|
|
_audit_cli("cli.community.login", f"CLI changed the stored account of community {row['slug']}", target_label=row["slug"])
|
|
|
|
|
|
def cmd_community_list(args):
|
|
from devplacepy.services.community.store import store
|
|
|
|
_prepare()
|
|
rows = store.list()
|
|
if not rows:
|
|
print("No communities")
|
|
return
|
|
for row in rows:
|
|
print(
|
|
f"{row['slug']:<28} {row['status']:<15} runs={row.get('run_count') or 0:<4} "
|
|
f"last={row.get('last_run_at') or '-'} next={row.get('next_run_at') or '-'}"
|
|
)
|
|
|
|
|
|
def add_login_arguments(parser):
|
|
parser.add_argument("--login-user", help="username of an account on the community")
|
|
parser.add_argument("--login-email", help="email of an account on the community")
|
|
parser.add_argument("--login-password", help="password (or set COMMUNITY_PASSWORD)")
|
|
parser.add_argument("--login-totp", help="authenticator secret of the account, for a login with two-factor codes (or set COMMUNITY_TOTP_SECRET)")
|
|
|
|
|
|
def register_community(subparsers):
|
|
community = subparsers.add_parser("community", help="External community discovery and sync")
|
|
sub = community.add_subparsers(title="action", dest="action")
|
|
discover = sub.add_parser("discover", help="Discover, learn and generate collectors for a community")
|
|
discover.add_argument("query", help="community name, address or search phrase")
|
|
discover.add_argument("--run", action="store_true", help="also run the collectors once, live")
|
|
discover.add_argument("--relearn", action="store_true", help="learn again even if the community exists")
|
|
discover.add_argument("--dry-run", action="store_true", help="learn and report, save nothing")
|
|
add_login_arguments(discover)
|
|
discover.set_defaults(func=cmd_community_discover)
|
|
run = sub.add_parser("run", help="Run the generated collectors of a community")
|
|
run.add_argument("slug")
|
|
run.add_argument("--dry-run", action="store_true", help="collect and validate, write nothing, keep the watermark")
|
|
run.set_defaults(func=cmd_community_run)
|
|
login = sub.add_parser("login", help="Store or remove the account used to log in to a community")
|
|
login.add_argument("slug")
|
|
login.add_argument("--clear", action="store_true", help="remove the stored account")
|
|
add_login_arguments(login)
|
|
login.set_defaults(func=cmd_community_login)
|
|
listing = sub.add_parser("list", help="List communities")
|
|
listing.set_defaults(func=cmd_community_list)
|