SPB Git

spb/ka4 Public

ka4 — explorateur structuré du web québécois (édition agressive, Groupe KA). Crawling concurrent, Claude Sonnet+Haiku, Firecrawl/Scrapfly, graphe de connaissances.

Python 97.8% Shell 2.2%
5.3 KB · 167 lines python
Raw Blame History
1#!/usr/bin/env python32# Author: Simon-Pierre Boucher3# Contact: contact@spboucher.ai4"""CLI de ka-bot — cartographe du web québécois (Claude Haiku 4.5 + Firecrawl).56Exemples :7    python main.py agent --goal "Cartographier les PME technos de Québec" \\8        --seed "https://www.quebecinternational.ca" --max-steps 209    python main.py pipeline --query "agences marketing Montréal" --limit 1010    python main.py drain --max-pages 3011    python main.py stats12    python main.py export --out exports/entites.json13"""1415from __future__ import annotations1617import argparse18import json19import os20import sys2122from src.config import config23from src.orchestrator import Orchestrator24from src.scheduler import LongRunner252627def _print(obj) -> None:28    print(json.dumps(obj, ensure_ascii=False, indent=2))293031def cmd_agent(args) -> None:32    orch = Orchestrator(config)33    try:34        summary = orch.run_agent(args.goal, args.seed, args.max_steps)35        print("\n=== Résumé de mission ===")36        print(summary)37        print("\n=== Statistiques ===")38        _print(orch.store.stats())39    finally:40        orch.close()414243def cmd_pipeline(args) -> None:44    orch = Orchestrator(config)45    try:46        _print(orch.run_pipeline(args.query, args.limit, args.per_site_pages))47    finally:48        orch.close()495051def cmd_drain(args) -> None:52    orch = Orchestrator(config)53    try:54        _print(orch.drain_queue(args.max_pages))55    finally:56        orch.close()575859def cmd_run(args) -> None:60    runner = LongRunner(61        config,62        cycle_delay=args.cycle_delay,63        queue_batch=args.queue_batch,64        daily_page_budget=args.daily_budget,65        revisit_hours=args.revisit_hours,66        session_minutes=args.session_minutes,67    )68    print("ka4 démarré (Ctrl+C pour arrêter proprement).")69    runner.run(max_cycles=args.max_cycles)707172def cmd_missions(args) -> None:73    orch = Orchestrator(config)74    try:75        if args.add:76            mid = orch.store.add_mission(goal=args.add, seed=args.seed or "", priority=args.priority)77            print(f"Mission #{mid} ajoutée." if mid else "Mission déjà existante.")78        _print(orch.store.list_missions())79    finally:80        orch.close()818283def cmd_stats(_args) -> None:84    orch = Orchestrator(config)85    try:86        _print(orch.store.stats())87    finally:88        orch.close()899091def cmd_export(args) -> None:92    orch = Orchestrator(config)93    try:94        data = orch.store.export()95        if args.out:96            os.makedirs(os.path.dirname(args.out) or ".", exist_ok=True)97            with open(args.out, "w", encoding="utf-8") as f:98                json.dump(data, f, ensure_ascii=False, indent=2)99            print(f"{len(data)} entités exportées vers {args.out}")100        else:101            _print(data)102    finally:103        orch.close()104105106def build_parser() -> argparse.ArgumentParser:107    p = argparse.ArgumentParser(prog="ka-bot", description="Cartographe du web québécois.")108    sub = p.add_subparsers(dest="cmd", required=True)109110    a = sub.add_parser("agent", help="Mode agentique piloté par Claude Haiku")111    a.add_argument("--goal", required=True, help="Objectif de mission")112    a.add_argument("--seed", help="URL de départ optionnelle")113    a.add_argument("--max-steps", type=int, default=20)114    a.set_defaults(func=cmd_agent)115116    pl = sub.add_parser("pipeline", help="Pipeline déterministe search->scrape->extract")117    pl.add_argument("--query", required=True)118    pl.add_argument("--limit", type=int, default=10)119    pl.add_argument("--per-site-pages", type=int, default=3)120    pl.set_defaults(func=cmd_pipeline)121122    d = sub.add_parser("drain", help="Traiter la file d'URLs en attente")123    d.add_argument("--max-pages", type=int, default=None)124    d.set_defaults(func=cmd_drain)125126    r = sub.add_parser("run", help="Bot LONG TERME : boucle perpétuelle de cartographie")127    r.add_argument("--cycle-delay", type=float, default=20.0)128    r.add_argument("--queue-batch", type=int, default=8)129    r.add_argument("--daily-budget", type=int, default=800, help="Pages max par jour")130    r.add_argument("--revisit-hours", type=float, default=72.0)131    r.add_argument("--session-minutes", type=float, default=15.0,132                   help="Durée de session avant d'attendre « Continuer »")133    r.add_argument("--max-cycles", type=int, default=None, help="Limite de cycles (test)")134    r.set_defaults(func=cmd_run)135136    m = sub.add_parser("missions", help="Lister / ajouter des missions")137    m.add_argument("--add", help="Ajouter une mission (objectif)")138    m.add_argument("--seed", help="URL de départ pour la mission")139    m.add_argument("--priority", type=int, default=5)140    m.set_defaults(func=cmd_missions)141142    s = sub.add_parser("stats", help="Statistiques de la base")143    s.set_defaults(func=cmd_stats)144145    e = sub.add_parser("export", help="Exporter les entités en JSON")146    e.add_argument("--out", help="Fichier de sortie (sinon stdout)")147    e.set_defaults(func=cmd_export)148149    return p150151152def main() -> int:153    args = build_parser().parse_args()154    try:155        args.func(args)156        return 0157    except RuntimeError as e:158        print(f"Erreur : {e}", file=sys.stderr)159        return 1160    except KeyboardInterrupt:161        print("\nInterrompu.", file=sys.stderr)162        return 130163164165if __name__ == "__main__":166    raise SystemExit(main())167