aigency 0.2.0 → 0.4.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/CHANGELOG.md +31 -0
- data/README.md +12 -13
- data/lib/aigency/aigent/current_time.rb +33 -0
- data/lib/aigency/aigent/shell.rb +108 -0
- data/lib/aigency/aigent.rb +117 -65
- data/lib/aigency/delegator.rb +11 -0
- data/lib/aigency/plugin/shell.rb +11 -0
- data/lib/aigency/plugin.rb +39 -0
- data/lib/aigency/rake.rb +13 -0
- data/lib/aigency/sandbox/exec.rb +21 -0
- data/lib/aigency/sandbox.rb +22 -0
- data/lib/aigency/shell.rb +5 -5
- data/lib/aigency/tempdir/helpers.rb +15 -0
- data/lib/aigency/tempdir/list.rb +17 -0
- data/lib/aigency/tempdir/read.rb +32 -0
- data/lib/aigency/tempdir/size.rb +27 -0
- data/lib/aigency/tempdir/write.rb +28 -0
- data/lib/aigency/tempdir.rb +19 -0
- data/lib/aigency/tool.rb +5 -0
- data/lib/aigency/usability.rb +1 -0
- data/lib/aigency/version.rb +1 -1
- data/lib/aigency/workspace/cat.rb +11 -0
- data/lib/aigency/workspace/cmd.rb +59 -0
- data/lib/aigency/workspace/copy_tempdir_file.rb +28 -0
- data/lib/aigency/workspace/cp.rb +11 -0
- data/lib/aigency/workspace/diff.rb +11 -0
- data/lib/aigency/workspace/grep.rb +15 -0
- data/lib/aigency/workspace/head.rb +11 -0
- data/lib/aigency/workspace/helpers.rb +40 -3
- data/lib/aigency/workspace/ls.rb +11 -0
- data/lib/aigency/workspace/mkdir.rb +11 -0
- data/lib/aigency/workspace/mv.rb +11 -0
- data/lib/aigency/workspace/rm.rb +11 -0
- data/lib/aigency/workspace/tail.rb +11 -0
- data/lib/aigency/workspace/wc.rb +13 -0
- data/lib/aigency/workspace/write.rb +36 -0
- data/lib/aigency/workspace.rb +20 -7
- data/lib/aigency.rb +16 -13
- data/scaffold/{Rakefile → aigent/aigent.rake} +2 -11
- data/scaffold/enlist/.env.example +4 -0
- data/scaffold/{.gitignore → enlist/.gitignore} +1 -0
- data/scaffold/{Gemfile → enlist/Gemfile} +1 -1
- data/scaffold/enlist/README.md +90 -0
- data/scaffold/enlist/Rakefile +9 -0
- data/scaffold/enlist/bin/aigent +11 -0
- data/scaffold/{config → enlist/config}/schedule.rb +5 -0
- data/scaffold/sandbox/Dockerfile +37 -0
- data/scaffold/sandbox/docker-compose.yml +37 -0
- data/scaffold/sandbox/squid.conf +25 -0
- data/scaffold/sandbox/ssh_config +2 -0
- metadata +114 -16
- data/lib/aigency/tools.rb +0 -55
- data/lib/aigency/workspace/directory_deleter.rb +0 -23
- data/lib/aigency/workspace/directory_reader.rb +0 -33
- data/lib/aigency/workspace/file_deleter.rb +0 -22
- data/lib/aigency/workspace/file_reader.rb +0 -27
- data/lib/aigency/workspace/file_writer.rb +0 -26
- data/scaffold/README.md +0 -74
- data/scaffold/config/aigent.yml +0 -6
- /data/scaffold/{IDENTITY.md → enlist/IDENTITY.md} +0 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aigency
|
|
4
|
+
module Tempdir
|
|
5
|
+
class List < Tool
|
|
6
|
+
description "List all files in the temp directory"
|
|
7
|
+
|
|
8
|
+
def execute
|
|
9
|
+
aigent.log.puts "[Tempdir::List]"
|
|
10
|
+
{
|
|
11
|
+
success: true,
|
|
12
|
+
absolute_paths: Dir.glob(File.join(PATH, '**', '*'))
|
|
13
|
+
}
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aigency
|
|
4
|
+
module Tempdir
|
|
5
|
+
class Read < Tool
|
|
6
|
+
include Helpers
|
|
7
|
+
|
|
8
|
+
description "Read a file in the temp directory"
|
|
9
|
+
|
|
10
|
+
parameters do
|
|
11
|
+
string :filename, description: "The file to read"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def execute(filename:)
|
|
15
|
+
aigent.log.puts "[Tempdir::Read] #{filename}"
|
|
16
|
+
absolute_path = expand_in_tempdir(filename)
|
|
17
|
+
# @todo Hardcoded max size
|
|
18
|
+
if File.size(absolute_path) > 500_000
|
|
19
|
+
{ success: false, error: "File is too large to read (max 500000 bytes)"}
|
|
20
|
+
else
|
|
21
|
+
{
|
|
22
|
+
success: true,
|
|
23
|
+
absolute_path: absolute_path,
|
|
24
|
+
content: File.read(File.join(PATH, filename))
|
|
25
|
+
}
|
|
26
|
+
end
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
{ success: false, error: "[#{e.class}] #{e.message}"}
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aigency
|
|
4
|
+
module Tempdir
|
|
5
|
+
class Size < Tool
|
|
6
|
+
include Helpers
|
|
7
|
+
|
|
8
|
+
description "Get the size of a file in the temp directory"
|
|
9
|
+
|
|
10
|
+
parameters do
|
|
11
|
+
string :filename, description: "The file to measure"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def execute(filename:)
|
|
15
|
+
aigent.log.puts "[Tempdir::Read] #{filename}"
|
|
16
|
+
absolute_path = expand_in_tempdir(filename)
|
|
17
|
+
{
|
|
18
|
+
success: true,
|
|
19
|
+
absolute_path: absolute_path,
|
|
20
|
+
size: File.size(absolute_path)
|
|
21
|
+
}
|
|
22
|
+
rescue StandardError => e
|
|
23
|
+
{ success: false, error: "[#{e.class}] #{e.message}"}
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aigency
|
|
4
|
+
module Tempdir
|
|
5
|
+
class Write < Tool
|
|
6
|
+
include Helpers
|
|
7
|
+
|
|
8
|
+
description "Write a file in the temp directory"
|
|
9
|
+
|
|
10
|
+
parameters do
|
|
11
|
+
string :filename, description: "The filename to write"
|
|
12
|
+
string :content, description: "The file's content"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def execute(filename:, content:)
|
|
16
|
+
aigent.log.puts "[Tempdir::Write] #{filename}"
|
|
17
|
+
absolute_path = expand_in_tempdir(filename)
|
|
18
|
+
File.write absolute_path, content
|
|
19
|
+
{
|
|
20
|
+
success: true,
|
|
21
|
+
absolute_path: absolute_path
|
|
22
|
+
}
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
{ success: false, error: "[#{e.class}] #{e.message}"}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'tmpdir'
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
module Aigency
|
|
7
|
+
module Tempdir
|
|
8
|
+
require 'aigency/tempdir/helpers'
|
|
9
|
+
require 'aigency/tempdir/list'
|
|
10
|
+
require 'aigency/tempdir/read'
|
|
11
|
+
require 'aigency/tempdir/write'
|
|
12
|
+
require 'aigency/tempdir/size'
|
|
13
|
+
|
|
14
|
+
PATH = Dir.mktmpdir
|
|
15
|
+
TOOLS = [List, Read, Write, Size]
|
|
16
|
+
|
|
17
|
+
at_exit { FileUtils.rm_rf PATH }
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/aigency/tool.rb
CHANGED
data/lib/aigency/usability.rb
CHANGED
data/lib/aigency/version.rb
CHANGED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
module Aigency
|
|
6
|
+
module Workspace
|
|
7
|
+
class Cmd < Tool
|
|
8
|
+
include Helpers
|
|
9
|
+
|
|
10
|
+
# @abstract
|
|
11
|
+
# @return [String]
|
|
12
|
+
def command
|
|
13
|
+
raise 'Not implemented'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def non_path_arguments = []
|
|
17
|
+
|
|
18
|
+
def prohibited_arguments = []
|
|
19
|
+
|
|
20
|
+
def execute(directory: '.', arguments: [])
|
|
21
|
+
aigent.log.puts "[Workspace::Shell] #{command} #{arguments.inspect}"
|
|
22
|
+
if any_prohibited?(arguments)
|
|
23
|
+
return { success: false, error: "Permission denied: the following arguments to `#{command}` are prohibited: #{prohibited_arguments.inspect}" }
|
|
24
|
+
end
|
|
25
|
+
resolved = expand_in_workspace(directory)
|
|
26
|
+
if arguments != ['--help'] && arguments[0, non_path_arguments.length].any? { |arg| arg.start_with?('-') }
|
|
27
|
+
return { success: false, error: non_path_argument_error }
|
|
28
|
+
end
|
|
29
|
+
validate_path_arguments(arguments, non_path_arguments.length)
|
|
30
|
+
output, error, result = Open3.capture3(command, *arguments, chdir: resolved)
|
|
31
|
+
{ success: result.success?, stdout: output, stderr: error }
|
|
32
|
+
rescue StandardError => e
|
|
33
|
+
{ success: false, error: "[#{e.class}] #{e.message}" }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.inherited other
|
|
37
|
+
other.parameters do
|
|
38
|
+
string :directory, description: "The working directory relative to workspace", required: false
|
|
39
|
+
array :arguments, of: :string, description: "Command arguments", required: false
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def non_path_argument_error
|
|
46
|
+
arg_count = if non_path_arguments.length > 1
|
|
47
|
+
"#{non_path_arguments.length} arguments"
|
|
48
|
+
else
|
|
49
|
+
'argument'
|
|
50
|
+
end
|
|
51
|
+
"Permission denied: first #{arg_count} must be [#{non_path_arguments.join(', ')}] unless the only argument is `--help`"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def any_prohibited?(arguments)
|
|
55
|
+
arguments.any? { |arg| arg.start_with?(*prohibited_arguments) }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module Aigency
|
|
6
|
+
module Workspace
|
|
7
|
+
class CopyTempdirFile < Tool
|
|
8
|
+
include Tempdir::Helpers
|
|
9
|
+
include Workspace::Helpers
|
|
10
|
+
|
|
11
|
+
description 'Copy a file from tempdir to workspace'
|
|
12
|
+
|
|
13
|
+
parameters do
|
|
14
|
+
string :tempdir_source, description: "A source filename in tempdir", required: true
|
|
15
|
+
string :workspace_dest, description: "A destination filename in workspace", required: true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def execute(tempdir_source:, workspace_dest:)
|
|
19
|
+
aigent.log.puts "[Workspace::CopyTempdirFile] #{tempdir_source.inspect} #{workspace_dest.inspect}"
|
|
20
|
+
abs_tempdir = expand_in_tempdir(tempdir_source)
|
|
21
|
+
abs_workspace = expand_in_workspace(workspace_dest)
|
|
22
|
+
FileUtils.copy_file abs_tempdir, abs_workspace
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
{ success: false, error: "[#{e.class}] #{e.message}"}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aigency
|
|
4
|
+
module Workspace
|
|
5
|
+
class Grep < Cmd
|
|
6
|
+
description 'Run the `grep` shell command in your workspace'
|
|
7
|
+
|
|
8
|
+
def command = 'grep'
|
|
9
|
+
|
|
10
|
+
def non_path_arguments = %w[PATTERN]
|
|
11
|
+
|
|
12
|
+
def prohibited_arguments = %w[-f --file]
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -3,21 +3,58 @@
|
|
|
3
3
|
module Aigency
|
|
4
4
|
module Workspace
|
|
5
5
|
module Helpers
|
|
6
|
+
module_function
|
|
7
|
+
|
|
6
8
|
# @!method aigent
|
|
7
9
|
# @abstract
|
|
8
10
|
# @return [Aigency::Aigent]
|
|
9
11
|
|
|
10
|
-
# Expand a path relative to the aigent's workspace
|
|
12
|
+
# Expand a path relative to the aigent's workspace.
|
|
11
13
|
#
|
|
12
14
|
# @raise [PathError] if the expanded path is not inside the workspace
|
|
13
15
|
# @param path [String]
|
|
14
16
|
# @return [String]
|
|
15
|
-
def
|
|
17
|
+
def expand_in_workspace(path)
|
|
16
18
|
workspace = aigent.data('workspace')
|
|
17
19
|
resolved = File.expand_path(path, workspace)
|
|
18
20
|
return resolved if resolved == workspace || resolved.start_with?("#{workspace}/")
|
|
21
|
+
raise PathError, "Permission denied: `#{path}` is not relative to your workspace"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @raise [PathError] if the expanded path is not inside the workspace
|
|
25
|
+
# @param arguments [Array<String>]
|
|
26
|
+
# @param start [Integer]
|
|
27
|
+
# @return [void]
|
|
28
|
+
def validate_path_arguments(arguments, start = 0)
|
|
29
|
+
current = 0
|
|
30
|
+
glued_shorts = arguments.select { |arg| arg =~ /^-[^\-].+/ }
|
|
31
|
+
unless glued_shorts.empty?
|
|
32
|
+
raise PathError, "Permission denied: glued short options (#{glued_shorts.inspect}) are not allowed"
|
|
33
|
+
end
|
|
34
|
+
workspace = aigent.data('workspace')
|
|
35
|
+
path_arguments(arguments).each do |arg|
|
|
36
|
+
if !arg.start_with?('-')
|
|
37
|
+
current += 1
|
|
38
|
+
next unless current > start
|
|
39
|
+
end
|
|
40
|
+
path = split_path_part(arg)
|
|
41
|
+
resolved = File.expand_path(path, workspace)
|
|
42
|
+
next if resolved == workspace || resolved.start_with?("#{workspace}/")
|
|
43
|
+
raise PathError, "Permission denied: `#{path}` is not relative to your workspace"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def path_arguments(arguments)
|
|
48
|
+
arguments.reject { |arg| arg =~ /^-[^\-]$/ || (arg.start_with?('--') && !arg.include?('=')) }
|
|
49
|
+
end
|
|
19
50
|
|
|
20
|
-
|
|
51
|
+
def split_path_part argument
|
|
52
|
+
return argument unless argument.start_with?('-')
|
|
53
|
+
if argument.start_with?('--')
|
|
54
|
+
argument.split('=', 2).last
|
|
55
|
+
else
|
|
56
|
+
argument[2..]
|
|
57
|
+
end
|
|
21
58
|
end
|
|
22
59
|
end
|
|
23
60
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
|
|
5
|
+
module Aigency
|
|
6
|
+
module Workspace
|
|
7
|
+
# @note Unlike most of the Workspace tools, this one does not execute a shell
|
|
8
|
+
# command. It uses ruby's File module instead.
|
|
9
|
+
#
|
|
10
|
+
class Write < Tool
|
|
11
|
+
include Helpers
|
|
12
|
+
|
|
13
|
+
description 'Write or append to a file in the workspace'
|
|
14
|
+
|
|
15
|
+
parameters do
|
|
16
|
+
string :filename, description: "The filename relative to the workspace", required: true
|
|
17
|
+
string :content, description: "The data to write to the file", required: true
|
|
18
|
+
string :append, description: "Append if true or overwrite if false", required: true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def execute(filename:, content:, append:)
|
|
22
|
+
aigent.log.puts "[Workspace::Write] #{filename}"
|
|
23
|
+
resolved = expand_in_workspace(filename)
|
|
24
|
+
write_mode = if append
|
|
25
|
+
"a"
|
|
26
|
+
else
|
|
27
|
+
"w"
|
|
28
|
+
end
|
|
29
|
+
File.write resolved, content, mode: "#{write_mode}b"
|
|
30
|
+
{ success: true, filename: filename }
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
{ success: false, error: "[#{e.class}] #{e.message}" }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/aigency/workspace.rb
CHANGED
|
@@ -3,13 +3,26 @@
|
|
|
3
3
|
module Aigency
|
|
4
4
|
module Workspace
|
|
5
5
|
require 'aigency/workspace/helpers'
|
|
6
|
-
require 'aigency/workspace/
|
|
7
|
-
require 'aigency/workspace/
|
|
8
|
-
require 'aigency/workspace/
|
|
9
|
-
require 'aigency/workspace/
|
|
6
|
+
require 'aigency/workspace/cmd'
|
|
7
|
+
require 'aigency/workspace/cat'
|
|
8
|
+
require 'aigency/workspace/cp'
|
|
9
|
+
require 'aigency/workspace/diff'
|
|
10
|
+
require 'aigency/workspace/grep'
|
|
11
|
+
require 'aigency/workspace/ls'
|
|
12
|
+
require 'aigency/workspace/mkdir'
|
|
13
|
+
require 'aigency/workspace/mv'
|
|
14
|
+
require 'aigency/workspace/rm'
|
|
15
|
+
require 'aigency/workspace/wc'
|
|
16
|
+
require 'aigency/workspace/write'
|
|
17
|
+
require 'aigency/workspace/copy_tempdir_file'
|
|
10
18
|
|
|
11
|
-
|
|
19
|
+
Plugin.register Plugin.new(
|
|
20
|
+
'workspace',
|
|
21
|
+
directories: ['data/workspace'],
|
|
22
|
+
tools: [Cat, Cp, Diff, Grep, Ls, Mkdir, Mv, Rm, Wc, Write, CopyTempdirFile],
|
|
23
|
+
config: <<~FILE
|
|
24
|
+
prompt: "The workspace tools provide access to a readable and writeable directory."
|
|
25
|
+
FILE
|
|
26
|
+
)
|
|
12
27
|
end
|
|
13
|
-
|
|
14
|
-
Tools.mkdir_p 'data/workspace'
|
|
15
28
|
end
|
data/lib/aigency.rb
CHANGED
|
@@ -2,20 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
require 'ruby_llm'
|
|
4
4
|
|
|
5
|
-
# Core
|
|
6
|
-
require 'aigency/version'
|
|
7
|
-
require 'aigency/usability'
|
|
8
|
-
require 'aigency/tool'
|
|
9
|
-
require 'aigency/tools'
|
|
10
|
-
require 'aigency/aigent'
|
|
11
|
-
require 'aigency/shell'
|
|
12
|
-
|
|
13
|
-
# Extensions
|
|
14
|
-
require 'aigency/workspace'
|
|
15
|
-
|
|
16
5
|
module Aigency
|
|
6
|
+
class PathError < ArgumentError; end
|
|
7
|
+
|
|
17
8
|
GEM_PATH = File.absolute_path(File.join(__dir__, '..'))
|
|
18
9
|
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
# Core
|
|
11
|
+
require 'aigency/version'
|
|
12
|
+
require 'aigency/usability'
|
|
13
|
+
require 'aigency/tool'
|
|
14
|
+
require 'aigency/delegator'
|
|
15
|
+
require 'aigency/plugin'
|
|
16
|
+
require 'aigency/aigent'
|
|
17
|
+
require 'aigency/shell'
|
|
18
|
+
require 'aigency/rake'
|
|
19
|
+
|
|
20
|
+
# Plugins and Tools
|
|
21
|
+
require 'aigency/sandbox'
|
|
22
|
+
require 'aigency/tempdir'
|
|
23
|
+
require 'aigency/workspace'
|
|
21
24
|
end
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
require 'yaml'
|
|
3
|
-
require 'dotenv/load'
|
|
4
|
-
require 'aigency'
|
|
1
|
+
# frozen_string_literal: true
|
|
5
2
|
|
|
6
3
|
desc "Run a prompt from a file"
|
|
7
4
|
task :load, [:filename] do |_, args|
|
|
@@ -17,13 +14,7 @@ task :load, [:filename] do |_, args|
|
|
|
17
14
|
end
|
|
18
15
|
end
|
|
19
16
|
|
|
20
|
-
desc "
|
|
21
|
-
task :say, [:text] do |_, args|
|
|
22
|
-
aigent = Aigency::Aigent.new('.')
|
|
23
|
-
aigent.stream args[:text]
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
desc "Set up aigent configurations"
|
|
17
|
+
desc "Set up aigent plugins"
|
|
27
18
|
task :setup do
|
|
28
19
|
aigent = Aigency::Aigent.new('.')
|
|
29
20
|
aigent.setup
|