#!/usr/bin/perl use strict; use warnings; use Data::Dumper; use File::Tail; use JSON; use Digest::SHA qw(sha256_hex); use Net::Domain qw(hostfqdn); use Storable; our %DEFAULT_DATA = ( user => $ENV{'USER'}, hostname => hostfqdn(), shell => 'zsh', ); our %PROCESSED; sub read_processed { return unless -f "$ENV{'HOME'}/.cli-hive.processed"; %PROCESSED = %{retrieve "$ENV{'HOME'}/.cli-hive.processed"}; } sub store_processed { store \%PROCESSED, "$ENV{'HOME'}/.cli-hive.processed", } sub record_to_json { my $timestamp = shift; my $lines = shift; my %json = ( timestamp => $timestamp, command => join '\\n', @$lines, ); @json{keys %DEFAULT_DATA} = values %DEFAULT_DATA; #print encode_json \%json; print Dumper \%json; print "\n"; } sub zsh_extract_timestamp { my $line = shift; my ($timestamp, $command) = $line =~ /^: (\d+).*?;(.*)/; return ($timestamp, $command) if defined $command; return (undef, $line); } sub checksum { my $timestamp = shift; my @fields = @_; push @fields, $timestamp if defined $timestamp; sha256_hex(join '', @fields); } sub record_reader { my ($timestamp, @lines); return sub { my $line = shift; my ($timestamp_, $command) = zsh_extract_timestamp $line; $timestamp = $timestamp_ if defined $timestamp_; if ($command =~ /\\$/) { chomp $command; push @lines, $command; return; } if (defined $timestamp) { chomp $command; push @lines, $command; my $checksum = checksum $timestamp, @lines; unless (exists $PROCESSED{$checksum}) { record_to_json $timestamp, \@lines; $PROCESSED{$checksum} = { timestamp => time, }; store_processed; } } @lines = (); $timestamp = undef; }; } sub follow_history { my $file = File::Tail->new( name => "$ENV{'HOME'}/.zsh_history", interval => 0.1, maxinterval => 1, ); my $reader = record_reader; while (defined(my $line = $file->read)) { $reader->($line); } } read_processed; follow_history;