def accept_aggregate(file, net_file, clients_dir, since='',
                     filter_peer='', external_only='0'):
    """
    Aggregate accept events per peer — total bytes, packets, top destinations.
    Used by wgctl activity to show accepted traffic alongside drops.
 
    external_only='1': only show traffic to external IPs (non-private)
    external_only='0': only show traffic to internal IPs (default)
 
    Output:
      peer|peer_name|bytes_in|bytes_out|packets_in|packets_out|conn_count
      dest|peer_name|dst_ip|dst_port|proto|bytes_total|conn_count
    """
    from collections import defaultdict
    from itertools import groupby
 
    since_dt      = parse_since(since) if since else None
    show_external = str(external_only) == '1'
 
    peer_stats = defaultdict(lambda: {
        'bytes_in': 0, 'bytes_out': 0,
        'packets_in': 0, 'packets_out': 0,
        'conn_count': 0
    })
    # dest_stats = defaultdict(lambda: {'bytes': 0, 'count': 0})
    dest_stats = defaultdict(lambda: {'bytes_orig': 0, 'bytes_reply': 0, 'count': 0})
 
    try:
        with open(file) as f:
            for line in f:
                try:
                    e = json.loads(line.strip())
                    peer = e.get('peer', '')
                    if not peer:
                        continue
                    if filter_peer and peer != filter_peer:
                        continue
 
                    # Filter by external/internal
                    is_external = e.get('external', False)
                    if show_external and not is_external:
                        continue
                    if not show_external and is_external:
                        continue
 
                    if since_dt:
                        ts_str = e.get('ts', '')
                        try:
                            from datetime import timezone
                            ev_dt = datetime.fromisoformat(
                                ts_str.replace('Z', '+00:00'))
                            if ev_dt < since_dt:
                                continue
                        except Exception:
                            pass
 
                    dst_ip   = e.get('dst_ip', '')
                    dst_port = str(e.get('dst_port', ''))
                    proto    = e.get('proto', '')
                    b_orig   = e.get('bytes_orig', 0)
                    b_reply  = e.get('bytes_reply', 0)
                    p_orig   = e.get('packets_orig', 0)
                    p_reply  = e.get('packets_reply', 0)
 
                    ps = peer_stats[peer]
                    ps['bytes_out']   += b_orig
                    ps['bytes_in']    += b_reply
                    ps['packets_out'] += p_orig
                    ps['packets_in']  += p_reply
                    ps['conn_count']  += 1
 
                    dest_key = (peer, dst_ip, dst_port, proto)
                    dest_stats[dest_key]['bytes_orig']  += b_orig
                    dest_stats[dest_key]['bytes_reply'] += b_reply
                    dest_stats[dest_key]['count']       += 1
 
                except Exception:
                    pass
    except Exception:
        pass
 
    # Output peer summaries
    for peer, ps in sorted(peer_stats.items()):
        print(f"peer|{peer}|{ps['bytes_in']}|{ps['bytes_out']}|"
              f"{ps['packets_in']}|{ps['packets_out']}|{ps['conn_count']}")
 
    # Output top 5 destinations per peer sorted by byte count
    dest_items = sorted(
        dest_stats.items(),
        key=lambda x: (x[0][0], -x[1]['bytes'])
    )
    for peer, group in groupby(dest_items, key=lambda x: x[0][0]):
        top = list(group)[:20]
        for (p, dst_ip, dst_port, proto), stats in top:
            print(f"dest|{p}|{dst_ip}|{dst_port}|{proto}|"
                f"{stats['bytes_orig']}|{stats['bytes_reply']}|{stats['count']}")