number_station 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.asciidoc +36 -1
- data/lib/number_station/cli.rb +214 -28
- data/lib/number_station/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d9d58534a90398f402d7fad891415ab81782d96aaa440964703c6d33a1e89f37
|
|
4
|
+
data.tar.gz: 7ca3efa06033aacc95db71c8bd9622f9869f82caa1e535a50f592d72b2f39a5e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 73577af7d0e0e2d6b383398dc3c49dee4d2cfe816d0a6e5d19da5947409108bb79343ddc9fade867209f4dc4973c33d4083ea8098bf691766a2edb81ebd1391b
|
|
7
|
+
data.tar.gz: fff53ecba16b4b3d77674148f2982aa2022cae2e8f5ad0d4995a103562663a4b3bd0aae3337a7be0089c1bfcb152814453f8692456c8ca2af5f932ab5d04198e
|
data/README.asciidoc
CHANGED
|
@@ -69,7 +69,7 @@ number_station pad create [--name AGENT] [--numpads NUM] [--length LENGTH]
|
|
|
69
69
|
----
|
|
70
70
|
|
|
71
71
|
If `--name` is provided, creates pad in `~/number_station/pads/AGENT/` directory.
|
|
72
|
-
Defaults: 500 pads, 500 characters
|
|
72
|
+
Defaults: 500 pads, 500 characters. Pad length is automatically rounded up to the nearest multiple of 5.
|
|
73
73
|
|
|
74
74
|
*View pad statistics:*
|
|
75
75
|
|
|
@@ -80,6 +80,41 @@ number_station pad stats [--path PATH]
|
|
|
80
80
|
|
|
81
81
|
Shows pad statistics grouped by agent, including unconsumed pad counts.
|
|
82
82
|
|
|
83
|
+
*Convert pad to AsciiDoc:*
|
|
84
|
+
|
|
85
|
+
[source,bash]
|
|
86
|
+
----
|
|
87
|
+
number_station convert_pad_to_asciidoc --padpath PAD_FILE
|
|
88
|
+
----
|
|
89
|
+
|
|
90
|
+
Converts a pad file to AsciiDoc format with each pad as a chapter. Output saved as `FILENAME.asciidoc`.
|
|
91
|
+
Pads are formatted in groups of 5 characters with page breaks after every 2nd pad.
|
|
92
|
+
|
|
93
|
+
*Convert pad to LaTeX:*
|
|
94
|
+
|
|
95
|
+
[source,bash]
|
|
96
|
+
----
|
|
97
|
+
number_station convert_pad_to_latex --padpath PAD_FILE
|
|
98
|
+
----
|
|
99
|
+
|
|
100
|
+
Converts a pad file to LaTeX format with landscape orientation. Output saved as `FILENAME.tex`.
|
|
101
|
+
Pads are formatted in groups of 5 characters, arranged in a 2x2 grid (4 pads per page), with each pad taking up one quadrant of the page.
|
|
102
|
+
|
|
103
|
+
*Convert to PDF:*
|
|
104
|
+
|
|
105
|
+
[source,bash]
|
|
106
|
+
----
|
|
107
|
+
number_station convert_to_pdf INPUT_FILE
|
|
108
|
+
----
|
|
109
|
+
|
|
110
|
+
Converts an AsciiDoc (`.asciidoc` or `.adoc`) or LaTeX (`.tex`) file to PDF.
|
|
111
|
+
+
|
|
112
|
+
For AsciiDoc files: Uses asciidoctor-pdf. Requires asciidoctor-pdf gem to be installed.
|
|
113
|
+
+
|
|
114
|
+
For LaTeX files: Uses pdflatex. Requires a LaTeX distribution (e.g., texlive) to be installed.
|
|
115
|
+
+
|
|
116
|
+
Output saved as `FILENAME.pdf` in the same directory.
|
|
117
|
+
|
|
83
118
|
==== Message Encryption/Decryption ====
|
|
84
119
|
|
|
85
120
|
*Encrypt:*
|
data/lib/number_station/cli.rb
CHANGED
|
@@ -39,13 +39,16 @@ module NumberStation
|
|
|
39
39
|
--name NAME - Agent name to associate with this pad. Creates subdirectory ~/number_station/pads/NAME/ if --path is not provided
|
|
40
40
|
--path PATH - Directory where the pad file will be created (defaults to current directory, or ~/number_station/pads/NAME/ if --name is provided)
|
|
41
41
|
--numpads NUM - Number of pads to generate (defaults to 500)
|
|
42
|
-
--length LENGTH - Length of each pad in characters (defaults to 500)
|
|
42
|
+
--length LENGTH - Length of each pad in characters (defaults to 500, rounded up to nearest multiple of 5)
|
|
43
43
|
|
|
44
44
|
If no parameters are passed it will generate 500 one time pads in the current
|
|
45
45
|
directory of size 500 characters.
|
|
46
46
|
|
|
47
47
|
If --name is provided without --path, the pad will be created in ~/number_station/pads/NAME/
|
|
48
48
|
|
|
49
|
+
Note: Pad length is automatically rounded up to the nearest multiple of 5 to ensure
|
|
50
|
+
proper formatting in 5-character groups.
|
|
51
|
+
|
|
49
52
|
Examples:
|
|
50
53
|
number_station pad create --name Shadow --numpads 1000 --length 1000
|
|
51
54
|
number_station pad create --name Shadow
|
|
@@ -420,7 +423,7 @@ module NumberStation
|
|
|
420
423
|
|
|
421
424
|
desc "activate NAME", "Activate an agent"
|
|
422
425
|
long_desc <<-ACTIVATE_AGENT_LONG_DESC
|
|
423
|
-
Activate an agent by name. Sets active status to true
|
|
426
|
+
Activate an agent by name. Sets active status to true, optionally sets start_date, and resets end_date to null.
|
|
424
427
|
|
|
425
428
|
Parameters:
|
|
426
429
|
NAME - The agent name (required)
|
|
@@ -428,6 +431,8 @@ module NumberStation
|
|
|
428
431
|
Options:
|
|
429
432
|
--start-date DATE - Set the start date (defaults to today if not specified)
|
|
430
433
|
|
|
434
|
+
When activating an agent, the end_date is automatically reset to null (clearing any previous end date).
|
|
435
|
+
|
|
431
436
|
Examples:
|
|
432
437
|
number_station agents activate Shadow
|
|
433
438
|
number_station agents activate Shadow --start-date "2024-01-15"
|
|
@@ -748,47 +753,227 @@ module NumberStation
|
|
|
748
753
|
puts "Generated AsciiDoc file: #{output_path}"
|
|
749
754
|
end
|
|
750
755
|
|
|
751
|
-
desc "
|
|
752
|
-
long_desc <<-
|
|
753
|
-
Convert
|
|
756
|
+
desc "convert_pad_to_latex --padpath PADPATH", "Convert a pad file to LaTeX format"
|
|
757
|
+
long_desc <<-CONVERT_PAD_TO_LATEX_LONG_DESC
|
|
758
|
+
Convert a one-time pad file to LaTeX format with landscape orientation.
|
|
754
759
|
|
|
755
760
|
Parameters:
|
|
756
|
-
|
|
761
|
+
--padpath PADPATH - Path to pad file (e.g., ~/number_station/pads/Shadow/Shadow-2026-01-12.json)
|
|
757
762
|
|
|
758
|
-
|
|
759
|
-
|
|
763
|
+
Each pad in the file is converted to groups of 5 characters, separated by spaces.
|
|
764
|
+
Pads are arranged in a 2x2 grid (4 pads per page), with each pad taking up one quadrant.
|
|
765
|
+
Output saved as FILENAME.tex in the same directory.
|
|
766
|
+
|
|
767
|
+
The LaTeX file uses landscape orientation and can be converted to PDF using convert_to_pdf.
|
|
760
768
|
|
|
761
769
|
Example:
|
|
762
|
-
number_station
|
|
763
|
-
|
|
764
|
-
|
|
770
|
+
number_station convert_pad_to_latex --padpath ~/number_station/pads/Shadow/Shadow-2026-01-12.json
|
|
771
|
+
CONVERT_PAD_TO_LATEX_LONG_DESC
|
|
772
|
+
option :padpath, type: :string, required: true
|
|
773
|
+
def convert_pad_to_latex
|
|
765
774
|
ensure_config_loaded
|
|
766
775
|
|
|
767
|
-
|
|
768
|
-
|
|
776
|
+
pad_path = options[:padpath]
|
|
777
|
+
unless File.exist?(pad_path)
|
|
778
|
+
raise Thor::Error, "Pad file not found: #{pad_path}"
|
|
769
779
|
end
|
|
770
780
|
|
|
771
|
-
#
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
781
|
+
# Read pad file
|
|
782
|
+
pad_data = JSON.parse(File.read(pad_path))
|
|
783
|
+
pad_id = pad_data["id"]
|
|
784
|
+
pads_hash = pad_data["pads"]
|
|
785
|
+
|
|
786
|
+
if pads_hash.nil? || pads_hash.empty?
|
|
787
|
+
raise Thor::Error, "No pads found in file: #{pad_path}"
|
|
775
788
|
end
|
|
776
789
|
|
|
777
|
-
# Generate output filename: replace .
|
|
778
|
-
input_basename = File.basename(
|
|
779
|
-
output_filename = "#{input_basename}.
|
|
780
|
-
output_path = File.join(File.dirname(
|
|
790
|
+
# Generate output filename: replace .json extension with .tex
|
|
791
|
+
input_basename = File.basename(pad_path, File.extname(pad_path))
|
|
792
|
+
output_filename = "#{input_basename}.tex"
|
|
793
|
+
output_path = File.join(File.dirname(pad_path), output_filename)
|
|
781
794
|
|
|
782
|
-
#
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
795
|
+
# Build LaTeX content
|
|
796
|
+
latex_content = []
|
|
797
|
+
latex_content << "\\documentclass[10pt]{article}"
|
|
798
|
+
latex_content << "\\usepackage[utf8]{inputenc}"
|
|
799
|
+
latex_content << "\\usepackage[landscape,margin=0.5in]{geometry}"
|
|
800
|
+
latex_content << "\\usepackage{fancyhdr}"
|
|
801
|
+
latex_content << "\\usepackage{listings}"
|
|
802
|
+
latex_content << "\\lstset{"
|
|
803
|
+
latex_content << " basicstyle=\\ttfamily\\small,"
|
|
804
|
+
latex_content << " breaklines=true,"
|
|
805
|
+
latex_content << " breakatwhitespace=false,"
|
|
806
|
+
latex_content << " columns=fullflexible,"
|
|
807
|
+
latex_content << " frame=none,"
|
|
808
|
+
latex_content << " showstringspaces=false"
|
|
809
|
+
latex_content << "}"
|
|
810
|
+
latex_content << "\\pagestyle{fancy}"
|
|
811
|
+
latex_content << "\\fancyhf{}"
|
|
812
|
+
latex_content << "\\fancyfoot[C]{One-Time Pad: #{input_basename}}"
|
|
813
|
+
latex_content << "\\renewcommand{\\headrulewidth}{0pt}"
|
|
814
|
+
latex_content << "\\renewcommand{\\footrulewidth}{0pt}"
|
|
815
|
+
latex_content << "\\begin{document}"
|
|
816
|
+
latex_content << ""
|
|
817
|
+
|
|
818
|
+
# Process pads in groups of 4 (one page = 4 quadrants)
|
|
819
|
+
pads_array = pads_hash.sort_by { |k, v| k.to_i }
|
|
820
|
+
pads_array.each_slice(4).with_index do |pad_group, index|
|
|
821
|
+
# Start a new page for each group of 4 pads (except the first)
|
|
822
|
+
if index > 0
|
|
823
|
+
latex_content << "\\newpage"
|
|
824
|
+
latex_content << ""
|
|
825
|
+
end
|
|
826
|
+
|
|
827
|
+
# Create 2x2 grid using minipage environments
|
|
828
|
+
latex_content << "\\noindent"
|
|
829
|
+
latex_content << "\\begin{minipage}[t]{0.48\\textwidth}"
|
|
830
|
+
latex_content << "\\vspace{0pt}"
|
|
831
|
+
|
|
832
|
+
# Top-left quadrant (pad 0)
|
|
833
|
+
if pad_group[0]
|
|
834
|
+
pad_num, pad_info = pad_group[0]
|
|
835
|
+
hex_key = pad_info["key"]
|
|
836
|
+
cleaned_hex = hex_key.gsub(/[\s\n\r]/, '')
|
|
837
|
+
grouped_hex = cleaned_hex.chars.each_slice(5).map(&:join).join(' ')
|
|
838
|
+
latex_content << "\\textbf{Pad #{pad_num}}\\\\"
|
|
839
|
+
latex_content << "\\begin{lstlisting}"
|
|
840
|
+
latex_content << grouped_hex
|
|
841
|
+
latex_content << "\\end{lstlisting}"
|
|
842
|
+
end
|
|
843
|
+
|
|
844
|
+
latex_content << "\\end{minipage}"
|
|
845
|
+
latex_content << "\\hfill"
|
|
846
|
+
latex_content << "\\begin{minipage}[t]{0.48\\textwidth}"
|
|
847
|
+
latex_content << "\\vspace{0pt}"
|
|
848
|
+
|
|
849
|
+
# Top-right quadrant (pad 1)
|
|
850
|
+
if pad_group[1]
|
|
851
|
+
pad_num, pad_info = pad_group[1]
|
|
852
|
+
hex_key = pad_info["key"]
|
|
853
|
+
cleaned_hex = hex_key.gsub(/[\s\n\r]/, '')
|
|
854
|
+
grouped_hex = cleaned_hex.chars.each_slice(5).map(&:join).join(' ')
|
|
855
|
+
latex_content << "\\textbf{Pad #{pad_num}}\\\\"
|
|
856
|
+
latex_content << "\\begin{lstlisting}"
|
|
857
|
+
latex_content << grouped_hex
|
|
858
|
+
latex_content << "\\end{lstlisting}"
|
|
859
|
+
end
|
|
860
|
+
|
|
861
|
+
latex_content << "\\end{minipage}"
|
|
862
|
+
latex_content << ""
|
|
863
|
+
latex_content << "\\\\[1cm]"
|
|
864
|
+
latex_content << ""
|
|
865
|
+
latex_content << "\\noindent"
|
|
866
|
+
latex_content << "\\begin{minipage}[t]{0.48\\textwidth}"
|
|
867
|
+
latex_content << "\\vspace{0pt}"
|
|
868
|
+
|
|
869
|
+
# Bottom-left quadrant (pad 2)
|
|
870
|
+
if pad_group[2]
|
|
871
|
+
pad_num, pad_info = pad_group[2]
|
|
872
|
+
hex_key = pad_info["key"]
|
|
873
|
+
cleaned_hex = hex_key.gsub(/[\s\n\r]/, '')
|
|
874
|
+
grouped_hex = cleaned_hex.chars.each_slice(5).map(&:join).join(' ')
|
|
875
|
+
latex_content << "\\textbf{Pad #{pad_num}}\\\\"
|
|
876
|
+
latex_content << "\\begin{lstlisting}"
|
|
877
|
+
latex_content << grouped_hex
|
|
878
|
+
latex_content << "\\end{lstlisting}"
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
latex_content << "\\end{minipage}"
|
|
882
|
+
latex_content << "\\hfill"
|
|
883
|
+
latex_content << "\\begin{minipage}[t]{0.48\\textwidth}"
|
|
884
|
+
latex_content << "\\vspace{0pt}"
|
|
885
|
+
|
|
886
|
+
# Bottom-right quadrant (pad 3)
|
|
887
|
+
if pad_group[3]
|
|
888
|
+
pad_num, pad_info = pad_group[3]
|
|
889
|
+
hex_key = pad_info["key"]
|
|
890
|
+
cleaned_hex = hex_key.gsub(/[\s\n\r]/, '')
|
|
891
|
+
grouped_hex = cleaned_hex.chars.each_slice(5).map(&:join).join(' ')
|
|
892
|
+
latex_content << "\\textbf{Pad #{pad_num}}\\\\"
|
|
893
|
+
latex_content << "\\begin{lstlisting}"
|
|
894
|
+
latex_content << grouped_hex
|
|
895
|
+
latex_content << "\\end{lstlisting}"
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
latex_content << "\\end{minipage}"
|
|
899
|
+
latex_content << ""
|
|
900
|
+
end
|
|
786
901
|
|
|
787
|
-
|
|
788
|
-
|
|
902
|
+
latex_content << "\\end{document}"
|
|
903
|
+
|
|
904
|
+
# Write LaTeX file
|
|
905
|
+
File.write(output_path, latex_content.join("\n"))
|
|
906
|
+
puts "Generated LaTeX file: #{output_path}"
|
|
907
|
+
end
|
|
908
|
+
|
|
909
|
+
desc "convert_to_pdf INPUT_FILE", "Convert an AsciiDoc or LaTeX file to PDF"
|
|
910
|
+
long_desc <<-CONVERT_TO_PDF_LONG_DESC
|
|
911
|
+
Convert an AsciiDoc or LaTeX file to PDF format.
|
|
912
|
+
|
|
913
|
+
Parameters:
|
|
914
|
+
INPUT_FILE - Path to AsciiDoc file (e.g., Shadow-2026-01-12.asciidoc) or LaTeX file (e.g., Shadow-2026-01-12.tex)
|
|
915
|
+
|
|
916
|
+
For AsciiDoc files:
|
|
917
|
+
Uses asciidoctor-pdf (checks availability before executing).
|
|
918
|
+
Output saved as FILENAME.pdf in the same directory.
|
|
919
|
+
|
|
920
|
+
For LaTeX files:
|
|
921
|
+
Uses pdflatex (checks availability before executing).
|
|
922
|
+
Output saved as FILENAME.pdf in the same directory.
|
|
923
|
+
|
|
924
|
+
Examples:
|
|
925
|
+
number_station convert_to_pdf Shadow-2026-01-12.asciidoc
|
|
926
|
+
number_station convert_to_pdf Shadow-2026-01-12.tex
|
|
927
|
+
CONVERT_TO_PDF_LONG_DESC
|
|
928
|
+
def convert_to_pdf(input_file)
|
|
929
|
+
ensure_config_loaded
|
|
930
|
+
|
|
931
|
+
unless File.exist?(input_file)
|
|
932
|
+
raise Thor::Error, "File not found: #{input_file}"
|
|
789
933
|
end
|
|
790
934
|
|
|
791
|
-
|
|
935
|
+
file_ext = File.extname(input_file).downcase
|
|
936
|
+
input_basename = File.basename(input_file, file_ext)
|
|
937
|
+
output_filename = "#{input_basename}.pdf"
|
|
938
|
+
output_path = File.join(File.dirname(input_file), output_filename)
|
|
939
|
+
|
|
940
|
+
if file_ext == '.tex'
|
|
941
|
+
# LaTeX file - use pdflatex
|
|
942
|
+
unless NumberStation.command?('pdflatex')
|
|
943
|
+
raise Thor::Error, "pdflatex is not available. Please install a LaTeX distribution (e.g., texlive)"
|
|
944
|
+
end
|
|
945
|
+
|
|
946
|
+
# Run pdflatex in the directory containing the .tex file
|
|
947
|
+
file_dir = File.dirname(input_file)
|
|
948
|
+
file_name = File.basename(input_file)
|
|
949
|
+
cmd = "cd #{file_dir} && pdflatex -interaction=nonstopmode #{file_name}"
|
|
950
|
+
NumberStation.log.info "Running: #{cmd}"
|
|
951
|
+
system(cmd)
|
|
952
|
+
|
|
953
|
+
unless $?.success?
|
|
954
|
+
raise Thor::Error, "PDF conversion failed with exit code #{$?.exitstatus}"
|
|
955
|
+
end
|
|
956
|
+
|
|
957
|
+
puts "Generated PDF file: #{output_path}"
|
|
958
|
+
elsif file_ext == '.asciidoc' || file_ext == '.adoc'
|
|
959
|
+
# AsciiDoc file - use asciidoctor-pdf
|
|
960
|
+
unless NumberStation.command?('asciidoctor-pdf')
|
|
961
|
+
raise Thor::Error, "asciidoctor-pdf is not available. Please install it with: gem install asciidoctor-pdf"
|
|
962
|
+
end
|
|
963
|
+
|
|
964
|
+
# Convert using asciidoctor-pdf command
|
|
965
|
+
cmd = "asciidoctor-pdf #{input_file} -o #{output_path}"
|
|
966
|
+
NumberStation.log.info "Running: #{cmd}"
|
|
967
|
+
system(cmd)
|
|
968
|
+
|
|
969
|
+
unless $?.success?
|
|
970
|
+
raise Thor::Error, "PDF conversion failed with exit code #{$?.exitstatus}"
|
|
971
|
+
end
|
|
972
|
+
|
|
973
|
+
puts "Generated PDF file: #{output_path}"
|
|
974
|
+
else
|
|
975
|
+
raise Thor::Error, "Unsupported file type: #{file_ext}. Supported types: .asciidoc, .adoc, .tex"
|
|
976
|
+
end
|
|
792
977
|
end
|
|
793
978
|
|
|
794
979
|
desc "espeak XML_FILE", "Use espeak to read an XML file"
|
|
@@ -887,6 +1072,7 @@ module NumberStation
|
|
|
887
1072
|
|
|
888
1073
|
If --agent is provided, the system will search for the oldest pad with unconsumed pads
|
|
889
1074
|
in the agent-specific directory (~/number_station/pads/AGENT/).
|
|
1075
|
+
Note: The agent must be active. Encryption will fail if the agent is inactive.
|
|
890
1076
|
|
|
891
1077
|
If --agent is not provided and --padpath/--numpad are not specified, the system will
|
|
892
1078
|
search for the oldest pad with unconsumed pads in ~/number_station/pads/.
|