cafeznik 0.5.5

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2d8f332ebaae2c888624a9d63af1df086999f2eadd7bceb11153e31dd5a5ac0c
4
+ data.tar.gz: 872a3e16fe91edf8062bd755e1a0746b7d4663dfd6f77e7e6e35a7f7631b25b4
5
+ SHA512:
6
+ metadata.gz: cfaeda87b0b4aed7de8a7ea77314e455b82df913675807978bad611c0139b712246038686953bc1f38d3a04d41b5bd12f62979926c1445f46a33fa2c842ff3cb
7
+ data.tar.gz: 4b7fa4394308fe7d61fd05392c73c1683db062f9a103f9150e725dfd198a012a4a4ee558849113dd8889d4bfb7cb79c237ae29e232c19d78099754c3b71f6b08
data/README.md ADDED
@@ -0,0 +1,6 @@
1
+ # Cafeznik
2
+
3
+ Not a fez wearing cat in a track suit
4
+
5
+
6
+ There are [many](https://github.com/Dicklesworthstone/your-source-to-prompt.html?tab=readme-ov-file) a code2prompt tools about, but this one is mine 🪖
data/bin/cafeznik ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "../lib/cafeznik"
4
+
5
+ Cafeznik::CLI.start(ARGV)
@@ -0,0 +1,49 @@
1
+ require "thor"
2
+
3
+ module Cafeznik
4
+ class CLI < Thor
5
+ def self.exit_on_failure? = true
6
+ def self.user_agrees? = $stdin.gets.strip.casecmp("y").zero?
7
+
8
+ class_option :verbose, type: :boolean, aliases: "-v", default: false, desc: "Run in verbose mode"
9
+ class_option :no_header, type: :boolean, default: false, desc: "Exclude headers"
10
+ class_option :with_tree, type: :boolean, aliases: "-t", default: false, desc: "Include file tree"
11
+ class_option :grep, type: :string, aliases: "-g", desc: "Filter files containing the specified content"
12
+ class_option :exclude, type: :array, aliases: "-e", desc: "Exclude files/folders matching patterns"
13
+
14
+ desc "default", "Select files, copy to clipboard; use --repo/-r for GitHub repository"
15
+ method_option :repo, type: :string, aliases: "-r", desc: "GitHub repository (owner/repo format)"
16
+
17
+ default_task :default
18
+
19
+ def default
20
+ Log.verbose = options[:verbose]
21
+ Log.info "Running in #{repo ? 'GitHub' : 'local'} mode"
22
+
23
+ source = determine_source
24
+
25
+ selector = Selector.new(source)
26
+ file_paths = selector.select
27
+
28
+ Content.new( # TODO: find better name than Content, perhaps Clipboard?
29
+ source:, file_paths:,
30
+ include_headers: !options[:no_header],
31
+ include_tree: options[:with_tree]
32
+ ).copy_to_clipboard
33
+ end
34
+
35
+ private
36
+
37
+ def determine_source
38
+ if repo
39
+ Source::GitHub.new(repo:, grep:, exclude:)
40
+ else
41
+ Source::Local.new(grep:, exclude:)
42
+ end
43
+ end
44
+
45
+ def repo = options[:repo]
46
+ def grep = options[:grep]
47
+ def exclude = options[:exclude] || []
48
+ end
49
+ end
@@ -0,0 +1,73 @@
1
+ require "clipboard"
2
+ require "memery"
3
+
4
+ module Cafeznik
5
+ class Content
6
+ include Memery
7
+ MAX_LINES = 10_000
8
+
9
+ def initialize(source:, file_paths:, include_headers:, include_tree:)
10
+ Log.debug "Initializing Content" do
11
+ <<~LOG
12
+ Source: #{source.class} file_paths: #{file_paths.size}
13
+ include_headers: #{include_headers} include_tree: #{include_tree}
14
+ LOG
15
+ end
16
+ @source = source
17
+ @file_paths = file_paths
18
+ @include_headers = include_headers
19
+ @include_tree = include_tree
20
+ end
21
+
22
+ def copy_to_clipboard
23
+ Log.debug "Copying content to clipboard"
24
+ @content = build_content
25
+
26
+ return Log.info("Copy operation cancelled by user") unless confirm_size!
27
+
28
+ ::Clipboard.copy(@content)
29
+
30
+ skipped_files = @file_paths.size - files_contents.size
31
+
32
+ log_message = "Copied #{@content.lines.size} lines across #{files_contents.size} files"
33
+ log_message << " (skipped #{skipped_files} empty)" if skipped_files.positive?
34
+
35
+ Log.info("#{log_message} to clipboard")
36
+ end
37
+
38
+ private
39
+
40
+ memoize def build_content = [tree_section, files_contents.join("\n\n")].flatten.compact.join("\n\n")
41
+
42
+ memoize def files_contents
43
+ Log.debug "Processing #{@file_paths.size} files"
44
+ @file_paths.each_with_object([]) do |file, memo|
45
+ content = @source.content(file)
46
+ memo << (@include_headers ? with_header(content, file) : content) unless content.empty?
47
+ rescue StandardError => e
48
+ Log.error("Error fetching content for #{file}: #{e.message}")
49
+ nil
50
+ end
51
+ end
52
+
53
+ memoize def tree_section = @include_tree ? with_header(@source.tree.drop(1).join("\n"), "Tree") : nil
54
+ def with_header(content, title) = "==> #{title} <==\n#{content}"
55
+
56
+ def confirm_size!
57
+ line_count = @content.lines.size
58
+ return true if line_count <= MAX_LINES
59
+
60
+ if @include_tree && suggest_tree_removal?
61
+ Log.warn "Content exceeds #{MAX_LINES} lines (#{line_count}). Try cutting out the tree? (y/N)"
62
+ @include_tree = false
63
+ @content = build_content
64
+ return confirm_size! if CLI.user_agrees?
65
+ end
66
+
67
+ Log.warn "Content exceeds #{MAX_LINES} lines (#{line_count}). Proceed? (y/N)"
68
+ CLI.user_agrees?
69
+ end
70
+
71
+ def suggest_tree_removal? = @content.lines.size <= MAX_LINES + @source.tree.size - 1
72
+ end
73
+ end
@@ -0,0 +1,105 @@
1
+ require "logger"
2
+ require "digest"
3
+
4
+ module Cafeznik
5
+ module Log
6
+ module_function
7
+
8
+ def verbose=(value)
9
+ @verbose = value
10
+ logger.level = value ? Logger::DEBUG : Logger::INFO
11
+ logger.formatter.verbose = value
12
+ end
13
+
14
+ def verbose? = @verbose || false
15
+
16
+ def logger
17
+ @_logger ||= Logger.new($stdout).tap do |log|
18
+ log.level = verbose? ? Logger::DEBUG : Logger::INFO
19
+ log.formatter = CompactFormatter.new
20
+ log.debug "Verbose mode enabled" if verbose?
21
+ end
22
+ end
23
+
24
+ %i[info debug warn error fatal].each do |level|
25
+ define_method(level) do |msg = nil, &block|
26
+ return unless logger.send(:"#{level}?")
27
+
28
+ caller_context = caller_locations(1, 1).first
29
+ component = caller_context.path[%r{/([^/]+)\.rb$}, 1]&.capitalize || "Unknown"
30
+ method = caller_context.label.split(/[#.]/).last
31
+
32
+ source_prefix = "[#{component}::#{method}]"
33
+
34
+ message = block ? "#{msg}:\n#{block.call}" : msg
35
+ formatted_message = "#{source_prefix} #{message}"
36
+
37
+ logger.send(level, formatted_message)
38
+ return unless level == :fatal
39
+
40
+ exit(1)
41
+ end
42
+ end
43
+ end
44
+
45
+ class CompactFormatter < Logger::Formatter
46
+ COLOR_MAP = {
47
+ colors: (30..37).to_a + (90..97).to_a # ANSI text colors
48
+ }.freeze
49
+
50
+ COLORS = {
51
+ severity: {
52
+ "DEBUG" => ["\e[44m", "\e[37m"], # Blue bg, white fg
53
+ "INFO" => ["\e[42m", "\e[30m"], # Green bg, black fg
54
+ "WARN" => ["\e[43m", "\e[30m"], # Yellow bg, black fg
55
+ "ERROR" => ["\e[41m", "\e[37m"], # Red bg, white fg
56
+ "FATAL" => ["\e[45m", "\e[30m"] # Magenta bg, black fg
57
+ },
58
+ reset: "\e[0m"
59
+ }.freeze
60
+
61
+ attr_accessor :verbose
62
+
63
+ def initialize
64
+ @component_colors = {}
65
+ @verbose = false
66
+ super
67
+ end
68
+
69
+ def call(severity, _time, _progname, message)
70
+ component, method, content = parse_message(message)
71
+ severity_bg, severity_fg = COLORS[:severity][severity] || COLORS[:severity]["DEBUG"]
72
+ severity_prefix = "#{severity_bg}#{severity_fg}#{severity[0]}#{COLORS[:reset]}"
73
+ source_prefix = format_source(component, method)
74
+
75
+ formatted_content = content.gsub("\n", "\n" + (" " * (severity_prefix.size + source_prefix.size + 1)))
76
+ if @verbose
77
+ "#{severity_prefix} #{source_prefix} #{formatted_content}\n"
78
+ else
79
+ "#{formatted_content}\n"
80
+ end
81
+ end
82
+
83
+ private
84
+
85
+ def parse_message(message)
86
+ if message =~ /\[([^:]+)::([^\]]+)\]\s+(.+)/m
87
+ [::Regexp.last_match(1), ::Regexp.last_match(2), ::Regexp.last_match(3)]
88
+ else
89
+ ["Unknown", "unknown", message]
90
+ end
91
+ end
92
+
93
+ def format_source(component, method)
94
+ component_color = color_for_string(component)
95
+ method_color = color_for_string(method)
96
+
97
+ "[#{component_color}#{component}#{COLORS[:reset]}::#{method_color}#{method}#{COLORS[:reset]}]"
98
+ end
99
+
100
+ def color_for_string(str)
101
+ index = Digest::MD5.hexdigest(str).to_i(16) % COLOR_MAP[:colors].size
102
+ "\e[#{COLOR_MAP[:colors][index]}m"
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,78 @@
1
+ require "tty-command"
2
+
3
+ module Cafeznik
4
+ class Selector
5
+ MAX_FILES = 20
6
+
7
+ def initialize(source)
8
+ Log.fatal "fzf is kinda the centerpiece of this little tool here. Go install, deal. I'll be here when you're done" unless ToolChecker.fzf_available?
9
+ @source = source
10
+ end
11
+
12
+ def select
13
+ skip_selection if @source.tree.empty?
14
+ select_paths_with_fzf
15
+ .tap { log_selection(it) if Log.verbose? }
16
+ .then { |paths| expand_paths(paths) }
17
+ .tap { |expanded| confirm_count!(expanded) }
18
+ end
19
+
20
+ private
21
+
22
+ def skip_selection
23
+ Log.info("No matching files found; skipping file selection.")
24
+ exit(1)
25
+ end
26
+
27
+ def select_paths_with_fzf
28
+ Log.debug "Running fzf"
29
+ run_fzf_command.then { |selected| selected.include?("./") ? [:all_files] : selected }
30
+ rescue TTY::Command::ExitError => e
31
+ handle_fzf_error(e)
32
+ end
33
+
34
+ def log_selection(paths)
35
+ Log.debug("#{paths.size} paths selected:") do
36
+ paths.map.with_index(1) { |p, i| "#{i}. #{p}" }.join("\n")
37
+ end
38
+ end
39
+
40
+ def run_fzf_command = TTY::Command.new(printer: Log.verbose? ? :pretty : :null)
41
+ .run("fzf --multi", stdin: @source.tree.join("\n"))
42
+ .out.split("\n")
43
+
44
+ def handle_fzf_error(error)
45
+ exit_code = error.message.match(/exit status: (\d+)/)[1].to_i
46
+ if exit_code == 130
47
+ Log.info("No files selected. Exiting..")
48
+ exit(0)
49
+ else
50
+ Log.error("Error running fzf: #{error.message}")
51
+ exit(1)
52
+ end
53
+ end
54
+
55
+ def expand_paths(paths)
56
+ # TODO: I think I can remove the reject here, as it's already done in the source
57
+ return @source.all_files.reject { |path| @source.exclude?(path) } if paths == [:all_files]
58
+
59
+ paths.flat_map do |path|
60
+ dir?(path) ? @source.expand_dir(path) : path
61
+ end.uniq
62
+ end
63
+
64
+ def confirm_count!(paths)
65
+ Log.info "Selected #{paths.size} files"
66
+ return paths if paths.size <= MAX_FILES
67
+
68
+ Log.warn "Selected more than #{MAX_FILES} files. Continue? (y/N)"
69
+ unless CLI.user_agrees?
70
+ Log.info "Copy operation cancelled by user"
71
+ exit 0
72
+ end
73
+ paths
74
+ end
75
+
76
+ def dir?(path) = @source.dir?(path)
77
+ end
78
+ end
@@ -0,0 +1,33 @@
1
+ module Cafeznik
2
+ module Source
3
+ class Base
4
+ # TODO: change to `root: nil, repo: nil`
5
+ def initialize(repo: nil, grep: nil, exclude: [])
6
+ @repo = repo
7
+ @grep = grep
8
+ @exclude = exclude
9
+ end
10
+
11
+ def tree = raise NotImplementedError
12
+ def expand_dir(_) = raise NotImplementedError
13
+ def content(_) = raise NotImplementedError
14
+ def dir?(_) = raise NotImplementedError
15
+ def full_tree = raise NotImplementedError
16
+
17
+ def exclude?(path)
18
+ Log.debug "Checking exclusion for #{path} against #{@exclude}"
19
+ excluded = @exclude.any? do |pattern|
20
+ if pattern.include?(File::SEPARATOR) || pattern.include?("/")
21
+ File.fnmatch?(pattern, path, File::FNM_PATHNAME)
22
+ else
23
+ File.fnmatch?(pattern, File.basename(path))
24
+ end
25
+ end
26
+ Log.debug "Exclusion result: #{excluded}"
27
+ excluded
28
+ end
29
+
30
+ def all_files = tree.reject(&method(:dir?))
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,86 @@
1
+ require_relative "base"
2
+ require "octokit"
3
+ require "base64"
4
+ require "resolv" # TODO: why is this here?
5
+
6
+ module Cafeznik
7
+ module Source
8
+ class GitHub < Base
9
+ def initialize(repo:, grep: nil, exclude: [])
10
+ super
11
+ @client = Octokit::Client.new(access_token:, auto_paginate: true)
12
+ verify_connection!
13
+ normalize_repo_name
14
+ end
15
+
16
+ def tree
17
+ @_tree ||= begin
18
+ all_paths = @grep ? grep_files(@grep) : full_tree
19
+ all_paths.reject { |path| exclude?(path) }
20
+ end
21
+ rescue Octokit::Error => e
22
+ Log.error "Error fetching GitHub tree: #{e.message}"
23
+ nil
24
+ end
25
+
26
+ def content(path)
27
+ Base64.decode64 @client.contents(@repo, path:)[:content]
28
+ rescue Octokit::Error => e
29
+ Log.error "Error fetching GitHub content: #{e.message}"
30
+ nil
31
+ end
32
+
33
+ def expand_dir(path) = tree.select { _1.start_with?(path) && !_1.end_with?("/") }
34
+ def dir?(path) = path.end_with?("/")
35
+
36
+ private
37
+
38
+ def verify_connection!
39
+ @client.repository(@repo)
40
+ rescue Octokit::Error, Faraday::Error => e
41
+ error_messages = {
42
+ Faraday::ConnectionFailed => "You might be offline, or something is keeping you from connecting 🛜",
43
+ Octokit::Unauthorized => "Unable to connect to GitHub. Please check your token / gh cli 🐙",
44
+ Octokit::NotFound => "Repo not found. Can't help you 🪬"
45
+ }
46
+ Log.fatal error_messages[e.class] || e.message
47
+ end
48
+
49
+ def normalize_repo_name
50
+ @repo = @repo[%r{github\.com[:/](.+?)(/?$)}, 1] || @repo.delete_prefix("/").delete_suffix("/")
51
+ end
52
+
53
+ def access_token = @_access_token ||=
54
+ ENV["GITHUB_TOKEN"] ||
55
+ fetch_token_via_gh ||
56
+ (Log.error("GitHub token not found. Please configure `gh` or set GITHUB_TOKEN in your environment.")
57
+ exit 1)
58
+
59
+ def full_tree
60
+ branch = @client.repository(@repo).default_branch
61
+ # get all all paths and add a trailing slash for directories
62
+ paths = @client.tree(@repo, branch, recursive: true).tree.map { "#{_1.path}#{'/' if _1.type == 'tree'}" }
63
+ (["./"] + paths).sort
64
+ end
65
+
66
+ def fetch_token_via_gh
67
+ Log.debug("Fetching GitHub token via GitHub CLI")
68
+ Log.fatal "GitHub CLI not installed. Either install it or set GITHUB_TOKEN in your environment" unless ToolChecker.gh_available?
69
+ TTY::Command.new(printer: :null).run("gh auth token").out.strip
70
+ rescue TTY::Command::ExitError
71
+ Log.warn("Failed to fetch GitHub token via GitHub CLI. Install GH and authenticate with `gh auth login`, or set GITHUB_TOKEN in your environment")
72
+ nil
73
+ end
74
+
75
+ def grep_files(pattern)
76
+ Log.debug "Searching for pattern '#{pattern}' within #{@repo}"
77
+ results = @client.search_code("#{pattern} repo:#{@repo} in:file").items.map(&:path)
78
+ Log.debug "Found #{results.size} files matching pattern '#{pattern}' in #{@repo}"
79
+ results
80
+ rescue Octokit::Error => e
81
+ Log.error "Error during search for pattern '#{pattern}': #{e.message}"
82
+ []
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,76 @@
1
+ require_relative "base"
2
+ require "tty-command"
3
+ require "memery"
4
+
5
+ module Cafeznik
6
+ module Source
7
+ class Local < Base
8
+ include Memery
9
+ def initialize(grep: nil, exclude: [])
10
+ super
11
+ Log.fatal "fd not installed. We depend on it. Get it!" unless ToolChecker.fd_available?
12
+
13
+ @cmd = TTY::Command.new(printer: Log.verbose? ? :pretty : :null)
14
+ end
15
+
16
+ memoize def tree
17
+ Log.debug "Building file tree#{@grep ? ' with grep filter' : ''}, #{@exclude ? "excluding: #{@exclude.join(',')}" : ''}"
18
+ files = @grep ? grepped_files : full_tree
19
+ files.empty? ? [] : ["./"] + files.sort
20
+ end
21
+
22
+ def expand_dir(path)
23
+ if path == "./"
24
+ return grepped_files if @grep
25
+
26
+ return full_tree
27
+ end
28
+
29
+ list_paths(path, files_only: true)
30
+ end
31
+
32
+ def dir?(path) = File.directory?(path)
33
+
34
+ def content(path)
35
+ return nil if dir?(path) || exclude?(path)
36
+
37
+ File.read(path)
38
+ rescue StandardError
39
+ Log.error("File not found: #{path}")
40
+ nil
41
+ end
42
+
43
+ def full_tree = list_paths
44
+ def all_files = @grep ? grepped_files : list_paths(files_only: true)
45
+
46
+ private
47
+
48
+ def list_paths(path = ".", files_only: false)
49
+ args = ["--hidden", "--follow",
50
+ (["--type", "f"] if files_only),
51
+ "--full-path",
52
+ *exclusion_args,
53
+ path].flatten.compact
54
+ run_cmd("fd", args)
55
+ rescue TTY::Command::ExitError => e
56
+ Log.error("FD error: #{e.message}") unless e.message.include?("exit status: 1")
57
+ []
58
+ end
59
+
60
+ def exclusion_args = (@exclude + [".git"]).flat_map { |p| ["--exclude", p] }
61
+
62
+ memoize def grepped_files
63
+ Log.fatal "rg required for grep functionality. Install and retry." unless ToolChecker.rg_available?
64
+
65
+ args = @exclude.flat_map { |p| ["-g", "!#{p}"] } # formats the exclusion into rg glob format
66
+ result = run_cmd("rg", ["--files-with-matches", @grep, ".", *args])
67
+ result.map { |f| f.delete_prefix("./") }
68
+ rescue TTY::Command::ExitError => e
69
+ Log.error("RG error: #{e.message}") unless e.message.include?("exit status: 1")
70
+ []
71
+ end
72
+
73
+ def run_cmd(cmd, args) = @cmd.run(cmd, *args).out.split("\n")
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,3 @@
1
+ require_relative "sources/base"
2
+ require_relative "sources/local"
3
+ require_relative "sources/github"
@@ -0,0 +1,16 @@
1
+ module Cafeznik
2
+ module ToolChecker
3
+ def self.method_missing(method_name)
4
+ if method_name.to_s =~ /^(.+)_available\?$/
5
+ tool_name = Regexp.last_match(1)
6
+ system("command -v #{tool_name} > /dev/null 2>&1")
7
+ else
8
+ super
9
+ end
10
+ end
11
+
12
+ def self.respond_to_missing?(method_name, include_private = false)
13
+ method_name.to_s.end_with?("_available?") || super
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module Cafeznik
2
+ VERSION = "0.5.5".freeze
3
+ end
data/lib/cafeznik.rb ADDED
@@ -0,0 +1,7 @@
1
+ require_relative "cafeznik/cli"
2
+ require_relative "cafeznik/log"
3
+ require_relative "cafeznik/selector"
4
+ require_relative "cafeznik/sources"
5
+ require_relative "cafeznik/content"
6
+ require_relative "cafeznik/tool_checker"
7
+ require_relative "cafeznik/version"
metadata ADDED
@@ -0,0 +1,267 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cafeznik
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.5
5
+ platform: ruby
6
+ authors:
7
+ - Lem
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-02-19 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: clipboard
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: faraday-multipart
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: faraday-retry
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '2.0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '2.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: memery
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.6'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: '1.6'
82
+ - !ruby/object:Gem::Dependency
83
+ name: octokit
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: '9.2'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: '9.2'
96
+ - !ruby/object:Gem::Dependency
97
+ name: thor
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '1.3'
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '1.3'
110
+ - !ruby/object:Gem::Dependency
111
+ name: tty-command
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '0.10'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '0.10'
124
+ - !ruby/object:Gem::Dependency
125
+ name: rspec
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: '3.13'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - "~>"
136
+ - !ruby/object:Gem::Version
137
+ version: '3.13'
138
+ - !ruby/object:Gem::Dependency
139
+ name: rubocop
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '1.66'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '1.66'
152
+ - !ruby/object:Gem::Dependency
153
+ name: rubocop-performance
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - "~>"
157
+ - !ruby/object:Gem::Version
158
+ version: '1.22'
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - "~>"
164
+ - !ruby/object:Gem::Version
165
+ version: '1.22'
166
+ - !ruby/object:Gem::Dependency
167
+ name: rubocop-rspec
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '3.2'
173
+ type: :development
174
+ prerelease: false
175
+ version_requirements: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - "~>"
178
+ - !ruby/object:Gem::Version
179
+ version: '3.2'
180
+ - !ruby/object:Gem::Dependency
181
+ name: standard
182
+ requirement: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - "~>"
185
+ - !ruby/object:Gem::Version
186
+ version: '1.41'
187
+ type: :development
188
+ prerelease: false
189
+ version_requirements: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - "~>"
192
+ - !ruby/object:Gem::Version
193
+ version: '1.41'
194
+ - !ruby/object:Gem::Dependency
195
+ name: super_diff
196
+ requirement: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ type: :development
202
+ prerelease: false
203
+ version_requirements: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '0'
208
+ - !ruby/object:Gem::Dependency
209
+ name: webmock
210
+ requirement: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - "~>"
213
+ - !ruby/object:Gem::Version
214
+ version: '3.24'
215
+ type: :development
216
+ prerelease: false
217
+ version_requirements: !ruby/object:Gem::Requirement
218
+ requirements:
219
+ - - "~>"
220
+ - !ruby/object:Gem::Version
221
+ version: '3.24'
222
+ description: 'A CLI tool for copying files to your clipboard en masse, from a local
223
+ directory or a GitHub repository. Why? So you can feed them into LLMs like a lazy
224
+ lazy script kiddie.
225
+
226
+ '
227
+ executables:
228
+ - cafeznik
229
+ extensions: []
230
+ extra_rdoc_files: []
231
+ files:
232
+ - README.md
233
+ - bin/cafeznik
234
+ - lib/cafeznik.rb
235
+ - lib/cafeznik/cli.rb
236
+ - lib/cafeznik/content.rb
237
+ - lib/cafeznik/log.rb
238
+ - lib/cafeznik/selector.rb
239
+ - lib/cafeznik/sources.rb
240
+ - lib/cafeznik/sources/base.rb
241
+ - lib/cafeznik/sources/github.rb
242
+ - lib/cafeznik/sources/local.rb
243
+ - lib/cafeznik/tool_checker.rb
244
+ - lib/cafeznik/version.rb
245
+ homepage: https://github.com/LemuelCushing/cafeznik
246
+ licenses:
247
+ - MIT
248
+ metadata:
249
+ rubygems_mfa_required: 'true'
250
+ rdoc_options: []
251
+ require_paths:
252
+ - lib
253
+ required_ruby_version: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - ">="
256
+ - !ruby/object:Gem::Version
257
+ version: '3.3'
258
+ required_rubygems_version: !ruby/object:Gem::Requirement
259
+ requirements:
260
+ - - ">="
261
+ - !ruby/object:Gem::Version
262
+ version: '0'
263
+ requirements: []
264
+ rubygems_version: 3.6.3
265
+ specification_version: 4
266
+ summary: CLI tool for copying files to your clipboard en masse
267
+ test_files: []