openclacky 0.5.1
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 +7 -0
- data/.clackyrules +80 -0
- data/.rspec +3 -0
- data/.rubocop.yml +8 -0
- data/CHANGELOG.md +74 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +272 -0
- data/Rakefile +38 -0
- data/bin/clacky +7 -0
- data/bin/openclacky +6 -0
- data/clacky-legacy/clacky.gemspec +24 -0
- data/clacky-legacy/clarky.gemspec +24 -0
- data/lib/clacky/agent.rb +1015 -0
- data/lib/clacky/agent_config.rb +47 -0
- data/lib/clacky/cli.rb +713 -0
- data/lib/clacky/client.rb +159 -0
- data/lib/clacky/config.rb +43 -0
- data/lib/clacky/conversation.rb +41 -0
- data/lib/clacky/hook_manager.rb +61 -0
- data/lib/clacky/progress_indicator.rb +53 -0
- data/lib/clacky/session_manager.rb +124 -0
- data/lib/clacky/thinking_verbs.rb +26 -0
- data/lib/clacky/tool_registry.rb +44 -0
- data/lib/clacky/tools/base.rb +64 -0
- data/lib/clacky/tools/edit.rb +100 -0
- data/lib/clacky/tools/file_reader.rb +79 -0
- data/lib/clacky/tools/glob.rb +93 -0
- data/lib/clacky/tools/grep.rb +169 -0
- data/lib/clacky/tools/run_project.rb +287 -0
- data/lib/clacky/tools/safe_shell.rb +396 -0
- data/lib/clacky/tools/shell.rb +305 -0
- data/lib/clacky/tools/todo_manager.rb +228 -0
- data/lib/clacky/tools/trash_manager.rb +371 -0
- data/lib/clacky/tools/web_fetch.rb +161 -0
- data/lib/clacky/tools/web_search.rb +138 -0
- data/lib/clacky/tools/write.rb +65 -0
- data/lib/clacky/trash_directory.rb +112 -0
- data/lib/clacky/utils/arguments_parser.rb +139 -0
- data/lib/clacky/utils/limit_stack.rb +80 -0
- data/lib/clacky/utils/path_helper.rb +15 -0
- data/lib/clacky/version.rb +5 -0
- data/lib/clacky.rb +38 -0
- data/sig/clacky.rbs +4 -0
- metadata +160 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Clacky
|
|
4
|
+
module Tools
|
|
5
|
+
class Write < Base
|
|
6
|
+
self.tool_name = "write"
|
|
7
|
+
self.tool_description = "Write content to a file. Creates new files or overwrites existing ones."
|
|
8
|
+
self.tool_category = "file_system"
|
|
9
|
+
self.tool_parameters = {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
path: {
|
|
13
|
+
type: "string",
|
|
14
|
+
description: "The path of the file to write (absolute or relative)"
|
|
15
|
+
},
|
|
16
|
+
content: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "The content to write to the file"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
required: %w[path content]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
def execute(path:, content:)
|
|
25
|
+
# Validate path
|
|
26
|
+
if path.nil? || path.strip.empty?
|
|
27
|
+
return { error: "Path cannot be empty" }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
begin
|
|
31
|
+
# Ensure parent directory exists
|
|
32
|
+
dir = File.dirname(path)
|
|
33
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
34
|
+
|
|
35
|
+
# Write content to file
|
|
36
|
+
File.write(path, content)
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
path: File.expand_path(path),
|
|
40
|
+
bytes_written: content.bytesize,
|
|
41
|
+
error: nil
|
|
42
|
+
}
|
|
43
|
+
rescue Errno::EACCES => e
|
|
44
|
+
{ error: "Permission denied: #{e.message}" }
|
|
45
|
+
rescue Errno::ENOSPC => e
|
|
46
|
+
{ error: "No space left on device: #{e.message}" }
|
|
47
|
+
rescue StandardError => e
|
|
48
|
+
{ error: "Failed to write file: #{e.message}" }
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def format_call(args)
|
|
53
|
+
path = args[:path] || args['path']
|
|
54
|
+
"Write(#{Utils::PathHelper.safe_basename(path)})"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def format_result(result)
|
|
58
|
+
return result[:error] if result[:error]
|
|
59
|
+
|
|
60
|
+
bytes = result[:bytes_written] || result['bytes_written'] || 0
|
|
61
|
+
"Written #{bytes} bytes"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Clacky
|
|
7
|
+
# Manages global trash directory at ~/.clacky/trash
|
|
8
|
+
# Organizes trash by project directory using path hash
|
|
9
|
+
class TrashDirectory
|
|
10
|
+
GLOBAL_TRASH_ROOT = File.join(Dir.home, ".clacky", "trash")
|
|
11
|
+
|
|
12
|
+
attr_reader :project_root, :trash_dir, :backup_dir
|
|
13
|
+
|
|
14
|
+
def initialize(project_root = Dir.pwd)
|
|
15
|
+
@project_root = File.expand_path(project_root)
|
|
16
|
+
@project_hash = generate_project_hash(@project_root)
|
|
17
|
+
@trash_dir = File.join(GLOBAL_TRASH_ROOT, @project_hash)
|
|
18
|
+
@backup_dir = File.join(@trash_dir, "backups")
|
|
19
|
+
|
|
20
|
+
setup_directories
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Generate a unique hash for project path
|
|
24
|
+
def generate_project_hash(path)
|
|
25
|
+
# Use MD5 hash of the absolute path, take first 16 chars for readability
|
|
26
|
+
hash = Digest::MD5.hexdigest(path)[0..15]
|
|
27
|
+
# Also include a readable suffix based on project name
|
|
28
|
+
project_name = File.basename(path).gsub(/[^a-zA-Z0-9_-]/, '_')[0..20]
|
|
29
|
+
"#{hash}_#{project_name}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Setup trash and backup directories with proper structure
|
|
33
|
+
def setup_directories
|
|
34
|
+
[@trash_dir, @backup_dir].each do |dir|
|
|
35
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
36
|
+
|
|
37
|
+
# Create .gitignore file to avoid trash files being committed
|
|
38
|
+
gitignore_path = File.join(dir, '.gitignore')
|
|
39
|
+
unless File.exist?(gitignore_path)
|
|
40
|
+
File.write(gitignore_path, "*\n!.gitignore\n")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Create project metadata file
|
|
45
|
+
create_project_metadata
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Create or update metadata about this project
|
|
49
|
+
def create_project_metadata
|
|
50
|
+
metadata_file = File.join(@trash_dir, '.project_metadata.json')
|
|
51
|
+
|
|
52
|
+
metadata = {
|
|
53
|
+
project_root: @project_root,
|
|
54
|
+
project_name: File.basename(@project_root),
|
|
55
|
+
project_hash: @project_hash,
|
|
56
|
+
created_at: File.exist?(metadata_file) ? JSON.parse(File.read(metadata_file))['created_at'] : Time.now.iso8601,
|
|
57
|
+
last_accessed: Time.now.iso8601
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
File.write(metadata_file, JSON.pretty_generate(metadata))
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
# Log warning but don't block operation
|
|
63
|
+
warn "Warning: Could not create project metadata: #{e.message}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Get all project directories that have trash
|
|
67
|
+
def self.all_projects
|
|
68
|
+
return [] unless Dir.exist?(GLOBAL_TRASH_ROOT)
|
|
69
|
+
|
|
70
|
+
projects = []
|
|
71
|
+
Dir.glob(File.join(GLOBAL_TRASH_ROOT, "*", ".project_metadata.json")).each do |metadata_file|
|
|
72
|
+
begin
|
|
73
|
+
metadata = JSON.parse(File.read(metadata_file))
|
|
74
|
+
projects << {
|
|
75
|
+
project_root: metadata['project_root'],
|
|
76
|
+
project_name: metadata['project_name'],
|
|
77
|
+
project_hash: metadata['project_hash'],
|
|
78
|
+
trash_dir: File.dirname(metadata_file),
|
|
79
|
+
last_accessed: metadata['last_accessed']
|
|
80
|
+
}
|
|
81
|
+
rescue StandardError
|
|
82
|
+
# Skip corrupted metadata
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
projects.sort_by { |p| p[:last_accessed] }.reverse
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Get trash directory for a specific project
|
|
90
|
+
def self.for_project(project_root)
|
|
91
|
+
new(project_root)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Clean up trash directories for non-existent projects
|
|
95
|
+
def self.cleanup_orphaned_projects
|
|
96
|
+
return 0 unless Dir.exist?(GLOBAL_TRASH_ROOT)
|
|
97
|
+
|
|
98
|
+
cleaned_count = 0
|
|
99
|
+
all_projects.each do |project|
|
|
100
|
+
unless Dir.exist?(project[:project_root])
|
|
101
|
+
# Project no longer exists, optionally remove trash
|
|
102
|
+
# For safety, we'll just mark it as orphaned
|
|
103
|
+
orphan_file = File.join(project[:trash_dir], '.orphaned')
|
|
104
|
+
File.write(orphan_file, "Original project path no longer exists: #{project[:project_root]}\n")
|
|
105
|
+
cleaned_count += 1
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
cleaned_count
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Clacky
|
|
6
|
+
module Utils
|
|
7
|
+
class ArgumentsParser
|
|
8
|
+
# Parse and validate tool call arguments with JSON repair capability
|
|
9
|
+
def self.parse_and_validate(call, tool_registry)
|
|
10
|
+
# 1. Try standard parsing
|
|
11
|
+
begin
|
|
12
|
+
args = JSON.parse(call[:arguments], symbolize_names: true)
|
|
13
|
+
return validate_required_params(call, args, tool_registry)
|
|
14
|
+
rescue JSON::ParserError => e
|
|
15
|
+
# Continue to repair
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# 2. Try simple repair
|
|
19
|
+
repaired = repair_json(call[:arguments])
|
|
20
|
+
|
|
21
|
+
begin
|
|
22
|
+
args = JSON.parse(repaired, symbolize_names: true)
|
|
23
|
+
return validate_required_params(call, args, tool_registry)
|
|
24
|
+
rescue JSON::ParserError, MissingRequiredParamsError => e
|
|
25
|
+
# 3. Repair failed or missing params, return helpful error
|
|
26
|
+
raise_helpful_error(call, tool_registry, e)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# Simple JSON repair: complete brackets and quotes
|
|
33
|
+
def self.repair_json(json_str)
|
|
34
|
+
result = json_str.strip
|
|
35
|
+
|
|
36
|
+
# Complete unclosed strings
|
|
37
|
+
result += '"' if result.count('"').odd?
|
|
38
|
+
|
|
39
|
+
# Complete unclosed braces
|
|
40
|
+
depth = 0
|
|
41
|
+
result.each_char { |c| depth += 1 if c == '{'; depth -= 1 if c == '}' }
|
|
42
|
+
result += '}' * depth if depth > 0
|
|
43
|
+
|
|
44
|
+
result
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Validate required parameters
|
|
48
|
+
def self.validate_required_params(call, args, tool_registry)
|
|
49
|
+
tool = tool_registry.get(call[:name])
|
|
50
|
+
required = tool.parameters&.dig(:required) || []
|
|
51
|
+
|
|
52
|
+
missing = required.reject { |param|
|
|
53
|
+
args.key?(param.to_sym) || args.key?(param.to_s)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if missing.any?
|
|
57
|
+
raise MissingRequiredParamsError.new(call[:name], missing, args.keys)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
args
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Generate error message with tool definition
|
|
64
|
+
def self.raise_helpful_error(call, tool_registry, original_error)
|
|
65
|
+
tool = tool_registry.get(call[:name])
|
|
66
|
+
error_msg = build_error_message(call, tool, original_error)
|
|
67
|
+
raise StandardError, error_msg
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.build_error_message(call, tool, original_error)
|
|
71
|
+
# Extract tool information
|
|
72
|
+
required_params = tool.parameters&.dig(:required) || []
|
|
73
|
+
|
|
74
|
+
# Try to parse provided parameters from incomplete JSON
|
|
75
|
+
provided_params = extract_provided_params(call[:arguments])
|
|
76
|
+
|
|
77
|
+
# Build clear error message
|
|
78
|
+
msg = []
|
|
79
|
+
msg << "Failed to parse arguments for tool '#{call[:name]}'."
|
|
80
|
+
msg << ""
|
|
81
|
+
msg << "Error: #{original_error.message}"
|
|
82
|
+
msg << ""
|
|
83
|
+
|
|
84
|
+
if provided_params.any?
|
|
85
|
+
msg << "Provided parameters: #{provided_params.join(', ')}"
|
|
86
|
+
else
|
|
87
|
+
msg << "No valid parameters could be extracted."
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
msg << "Required parameters: #{required_params.join(', ')}"
|
|
91
|
+
msg << ""
|
|
92
|
+
msg << "Tool definition:"
|
|
93
|
+
msg << format_tool_definition(tool)
|
|
94
|
+
msg << ""
|
|
95
|
+
msg << "Suggestions:"
|
|
96
|
+
msg << "- If the parameter value is too large (e.g., large file content), consider breaking it into smaller operations"
|
|
97
|
+
msg << "- Ensure all required parameters are provided"
|
|
98
|
+
msg << "- Simplify complex parameter values"
|
|
99
|
+
|
|
100
|
+
msg.join("\n")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Extract parameter names from incomplete JSON
|
|
104
|
+
def self.extract_provided_params(json_str)
|
|
105
|
+
# Simple extraction: find all "key": patterns
|
|
106
|
+
json_str.scan(/"(\w+)"\s*:/).flatten.uniq
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Format tool definition (concise version)
|
|
110
|
+
def self.format_tool_definition(tool)
|
|
111
|
+
lines = []
|
|
112
|
+
lines << " Name: #{tool.name}"
|
|
113
|
+
lines << " Description: #{tool.description}"
|
|
114
|
+
|
|
115
|
+
if tool.parameters[:properties]
|
|
116
|
+
lines << " Parameters:"
|
|
117
|
+
tool.parameters[:properties].each do |param, spec|
|
|
118
|
+
required_mark = tool.parameters[:required]&.include?(param.to_s) ? " (required)" : ""
|
|
119
|
+
lines << " - #{param}#{required_mark}: #{spec[:description]}"
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
lines.join("\n")
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Custom exception for missing required parameters
|
|
128
|
+
class MissingRequiredParamsError < StandardError
|
|
129
|
+
attr_reader :tool_name, :missing_params, :provided_params
|
|
130
|
+
|
|
131
|
+
def initialize(tool_name, missing_params, provided_params)
|
|
132
|
+
@tool_name = tool_name
|
|
133
|
+
@missing_params = missing_params
|
|
134
|
+
@provided_params = provided_params
|
|
135
|
+
super("Missing required parameters: #{missing_params.join(', ')}")
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Clacky
|
|
4
|
+
module Utils
|
|
5
|
+
# Auto-rolling fixed-size array
|
|
6
|
+
# Automatically discards oldest elements when size limit is exceeded
|
|
7
|
+
class LimitStack
|
|
8
|
+
attr_reader :max_size, :items
|
|
9
|
+
|
|
10
|
+
def initialize(max_size: 5000)
|
|
11
|
+
@max_size = max_size
|
|
12
|
+
@items = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Add elements (supports single or multiple)
|
|
16
|
+
def push(*elements)
|
|
17
|
+
elements.each do |element|
|
|
18
|
+
@items << element
|
|
19
|
+
trim_if_needed
|
|
20
|
+
end
|
|
21
|
+
self
|
|
22
|
+
end
|
|
23
|
+
alias_method :<<, :push
|
|
24
|
+
|
|
25
|
+
# Add multi-line text (split by lines and add)
|
|
26
|
+
def push_lines(text)
|
|
27
|
+
return self if text.nil? || text.empty?
|
|
28
|
+
|
|
29
|
+
lines = text.is_a?(Array) ? text : text.lines
|
|
30
|
+
lines.each { |line| push(line) }
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Get last N elements
|
|
35
|
+
def last(n = nil)
|
|
36
|
+
n ? @items.last(n) : @items.last
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Get all elements
|
|
40
|
+
def to_a
|
|
41
|
+
@items.dup
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Convert to string (for text content)
|
|
45
|
+
def to_s
|
|
46
|
+
@items.join
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Current size
|
|
50
|
+
def size
|
|
51
|
+
@items.size
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check if empty
|
|
55
|
+
def empty?
|
|
56
|
+
@items.empty?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Clear all elements
|
|
60
|
+
def clear
|
|
61
|
+
@items.clear
|
|
62
|
+
self
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Iterate over elements
|
|
66
|
+
def each(&block)
|
|
67
|
+
@items.each(&block)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def trim_if_needed
|
|
73
|
+
if @items.size > @max_size
|
|
74
|
+
# Remove oldest elements, keep only the latest max_size items
|
|
75
|
+
@items.shift(@items.size - @max_size)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Clacky
|
|
4
|
+
module Utils
|
|
5
|
+
module PathHelper
|
|
6
|
+
# Safely get basename from path, return placeholder if path is nil
|
|
7
|
+
def self.safe_basename(path, placeholder: "?")
|
|
8
|
+
return placeholder if path.nil? || path.to_s.empty?
|
|
9
|
+
File.basename(path.to_s)
|
|
10
|
+
rescue StandardError
|
|
11
|
+
placeholder
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
data/lib/clacky.rb
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "clacky/version"
|
|
4
|
+
require_relative "clacky/config"
|
|
5
|
+
require_relative "clacky/client"
|
|
6
|
+
require_relative "clacky/conversation"
|
|
7
|
+
|
|
8
|
+
# Agent system
|
|
9
|
+
require_relative "clacky/agent_config"
|
|
10
|
+
require_relative "clacky/hook_manager"
|
|
11
|
+
require_relative "clacky/tool_registry"
|
|
12
|
+
require_relative "clacky/thinking_verbs"
|
|
13
|
+
require_relative "clacky/progress_indicator"
|
|
14
|
+
require_relative "clacky/session_manager"
|
|
15
|
+
require_relative "clacky/utils/limit_stack"
|
|
16
|
+
require_relative "clacky/utils/path_helper"
|
|
17
|
+
require_relative "clacky/tools/base"
|
|
18
|
+
|
|
19
|
+
require_relative "clacky/tools/shell"
|
|
20
|
+
require_relative "clacky/tools/file_reader"
|
|
21
|
+
require_relative "clacky/tools/write"
|
|
22
|
+
require_relative "clacky/tools/edit"
|
|
23
|
+
require_relative "clacky/tools/glob"
|
|
24
|
+
require_relative "clacky/tools/grep"
|
|
25
|
+
require_relative "clacky/tools/web_search"
|
|
26
|
+
require_relative "clacky/tools/web_fetch"
|
|
27
|
+
require_relative "clacky/tools/todo_manager"
|
|
28
|
+
require_relative "clacky/tools/run_project"
|
|
29
|
+
require_relative "clacky/tools/safe_shell"
|
|
30
|
+
require_relative "clacky/tools/trash_manager"
|
|
31
|
+
require_relative "clacky/agent"
|
|
32
|
+
|
|
33
|
+
require_relative "clacky/cli"
|
|
34
|
+
|
|
35
|
+
module Clacky
|
|
36
|
+
class Error < StandardError; end
|
|
37
|
+
class AgentInterrupted < StandardError; end
|
|
38
|
+
end
|
data/sig/clacky.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: openclacky
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.5.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- windy
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: faraday
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '2.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: thor
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.3'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.3'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: tty-prompt
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0.23'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0.23'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: tty-spinner
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0.9'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0.9'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: diffy
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '3.4'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.4'
|
|
82
|
+
description: OpenClacky is a Ruby CLI tool for interacting with AI models via OpenAI-compatible
|
|
83
|
+
APIs. It provides chat functionality and autonomous AI agent capabilities with tool
|
|
84
|
+
use.
|
|
85
|
+
email:
|
|
86
|
+
- yafei@dao42.com
|
|
87
|
+
executables:
|
|
88
|
+
- clacky
|
|
89
|
+
- openclacky
|
|
90
|
+
extensions: []
|
|
91
|
+
extra_rdoc_files: []
|
|
92
|
+
files:
|
|
93
|
+
- ".clackyrules"
|
|
94
|
+
- ".rspec"
|
|
95
|
+
- ".rubocop.yml"
|
|
96
|
+
- CHANGELOG.md
|
|
97
|
+
- CODE_OF_CONDUCT.md
|
|
98
|
+
- LICENSE.txt
|
|
99
|
+
- README.md
|
|
100
|
+
- Rakefile
|
|
101
|
+
- bin/clacky
|
|
102
|
+
- bin/openclacky
|
|
103
|
+
- clacky-legacy/clacky.gemspec
|
|
104
|
+
- clacky-legacy/clarky.gemspec
|
|
105
|
+
- lib/clacky.rb
|
|
106
|
+
- lib/clacky/agent.rb
|
|
107
|
+
- lib/clacky/agent_config.rb
|
|
108
|
+
- lib/clacky/cli.rb
|
|
109
|
+
- lib/clacky/client.rb
|
|
110
|
+
- lib/clacky/config.rb
|
|
111
|
+
- lib/clacky/conversation.rb
|
|
112
|
+
- lib/clacky/hook_manager.rb
|
|
113
|
+
- lib/clacky/progress_indicator.rb
|
|
114
|
+
- lib/clacky/session_manager.rb
|
|
115
|
+
- lib/clacky/thinking_verbs.rb
|
|
116
|
+
- lib/clacky/tool_registry.rb
|
|
117
|
+
- lib/clacky/tools/base.rb
|
|
118
|
+
- lib/clacky/tools/edit.rb
|
|
119
|
+
- lib/clacky/tools/file_reader.rb
|
|
120
|
+
- lib/clacky/tools/glob.rb
|
|
121
|
+
- lib/clacky/tools/grep.rb
|
|
122
|
+
- lib/clacky/tools/run_project.rb
|
|
123
|
+
- lib/clacky/tools/safe_shell.rb
|
|
124
|
+
- lib/clacky/tools/shell.rb
|
|
125
|
+
- lib/clacky/tools/todo_manager.rb
|
|
126
|
+
- lib/clacky/tools/trash_manager.rb
|
|
127
|
+
- lib/clacky/tools/web_fetch.rb
|
|
128
|
+
- lib/clacky/tools/web_search.rb
|
|
129
|
+
- lib/clacky/tools/write.rb
|
|
130
|
+
- lib/clacky/trash_directory.rb
|
|
131
|
+
- lib/clacky/utils/arguments_parser.rb
|
|
132
|
+
- lib/clacky/utils/limit_stack.rb
|
|
133
|
+
- lib/clacky/utils/path_helper.rb
|
|
134
|
+
- lib/clacky/version.rb
|
|
135
|
+
- sig/clacky.rbs
|
|
136
|
+
homepage: https://github.com/yafeilee/clacky
|
|
137
|
+
licenses:
|
|
138
|
+
- MIT
|
|
139
|
+
metadata:
|
|
140
|
+
homepage_uri: https://github.com/yafeilee/clacky
|
|
141
|
+
source_code_uri: https://github.com/yafeilee/clacky
|
|
142
|
+
changelog_uri: https://github.com/yafeilee/clacky/blob/main/CHANGELOG.md
|
|
143
|
+
rdoc_options: []
|
|
144
|
+
require_paths:
|
|
145
|
+
- lib
|
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
147
|
+
requirements:
|
|
148
|
+
- - ">="
|
|
149
|
+
- !ruby/object:Gem::Version
|
|
150
|
+
version: 3.1.0
|
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
152
|
+
requirements:
|
|
153
|
+
- - ">="
|
|
154
|
+
- !ruby/object:Gem::Version
|
|
155
|
+
version: '0'
|
|
156
|
+
requirements: []
|
|
157
|
+
rubygems_version: 3.6.9
|
|
158
|
+
specification_version: 4
|
|
159
|
+
summary: A command-line interface for AI models (Claude, OpenAI, etc.)
|
|
160
|
+
test_files: []
|