nano-bots 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.rspec +1 -0
- data/.rubocop.yml +9 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +88 -0
- data/LICENSE +21 -0
- data/README.md +173 -0
- data/bin/rnb +4 -0
- data/components/provider.rb +20 -0
- data/components/providers/base.rb +15 -0
- data/components/providers/openai.rb +79 -0
- data/components/storage.rb +76 -0
- data/controllers/instance.rb +65 -0
- data/controllers/interfaces/cli.rb +47 -0
- data/controllers/interfaces/repl.rb +67 -0
- data/controllers/session.rb +114 -0
- data/logic/helpers/hash.rb +35 -0
- data/nano-bots.gemspec +42 -0
- data/ports/dsl/nano-bots/cli.rb +5 -0
- data/ports/dsl/nano-bots.rb +25 -0
- data/static/gem.rb +15 -0
- metadata +172 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: eb32dfa429c178b7af6c3f9a4c8046846ab9ae894c94bbf2038644ceef9c2368
|
4
|
+
data.tar.gz: 21e37c25c085c27f0f0357f8bb878cba2a366641f9d4649dc25536bcd51afc28
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0a4a1f1a7f33eb6da088b8418cd0d4da61d8e6890ee8b6a0276330217164966b7266c032eabf3e3454e7879bd5e525b47cc99eff40259b7a1a9f9cf86a72db4f
|
7
|
+
data.tar.gz: 2098e8e5da98da2499ea4267e70be61ba9a06feb5c3fb6375d8ff05dbd01ae72134d34e5205884af80f3c4029c32dc09fec3c91b744ee6f61b05db8a5309b8ec
|
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--require spec_helper
|
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
nano-bots (0.0.1)
|
5
|
+
babosa (~> 2.0)
|
6
|
+
dotenv (~> 2.8, >= 2.8.1)
|
7
|
+
faraday (~> 2.7, >= 2.7.4)
|
8
|
+
pry (~> 0.14.2)
|
9
|
+
rainbow (~> 3.1, >= 3.1.1)
|
10
|
+
ruby-openai (~> 4.0)
|
11
|
+
|
12
|
+
GEM
|
13
|
+
remote: https://rubygems.org/
|
14
|
+
specs:
|
15
|
+
ast (2.4.2)
|
16
|
+
babosa (2.0.0)
|
17
|
+
coderay (1.1.3)
|
18
|
+
diff-lcs (1.5.0)
|
19
|
+
dotenv (2.8.1)
|
20
|
+
faraday (2.7.4)
|
21
|
+
faraday-net_http (>= 2.0, < 3.1)
|
22
|
+
ruby2_keywords (>= 0.0.4)
|
23
|
+
faraday-multipart (1.0.4)
|
24
|
+
multipart-post (~> 2)
|
25
|
+
faraday-net_http (3.0.2)
|
26
|
+
json (2.6.3)
|
27
|
+
method_source (1.0.0)
|
28
|
+
multipart-post (2.3.0)
|
29
|
+
parallel (1.23.0)
|
30
|
+
parser (3.2.2.1)
|
31
|
+
ast (~> 2.4.1)
|
32
|
+
pry (0.14.2)
|
33
|
+
coderay (~> 1.1)
|
34
|
+
method_source (~> 1.0)
|
35
|
+
rainbow (3.1.1)
|
36
|
+
regexp_parser (2.8.0)
|
37
|
+
rexml (3.2.5)
|
38
|
+
rspec (3.12.0)
|
39
|
+
rspec-core (~> 3.12.0)
|
40
|
+
rspec-expectations (~> 3.12.0)
|
41
|
+
rspec-mocks (~> 3.12.0)
|
42
|
+
rspec-core (3.12.2)
|
43
|
+
rspec-support (~> 3.12.0)
|
44
|
+
rspec-expectations (3.12.3)
|
45
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
46
|
+
rspec-support (~> 3.12.0)
|
47
|
+
rspec-mocks (3.12.5)
|
48
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
49
|
+
rspec-support (~> 3.12.0)
|
50
|
+
rspec-support (3.12.0)
|
51
|
+
rubocop (1.50.2)
|
52
|
+
json (~> 2.3)
|
53
|
+
parallel (~> 1.10)
|
54
|
+
parser (>= 3.2.0.0)
|
55
|
+
rainbow (>= 2.2.2, < 4.0)
|
56
|
+
regexp_parser (>= 1.8, < 3.0)
|
57
|
+
rexml (>= 3.2.5, < 4.0)
|
58
|
+
rubocop-ast (>= 1.28.0, < 2.0)
|
59
|
+
ruby-progressbar (~> 1.7)
|
60
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
61
|
+
rubocop-ast (1.28.1)
|
62
|
+
parser (>= 3.2.1.0)
|
63
|
+
rubocop-capybara (2.18.0)
|
64
|
+
rubocop (~> 1.41)
|
65
|
+
rubocop-factory_bot (2.22.0)
|
66
|
+
rubocop (~> 1.33)
|
67
|
+
rubocop-rspec (2.22.0)
|
68
|
+
rubocop (~> 1.33)
|
69
|
+
rubocop-capybara (~> 2.17)
|
70
|
+
rubocop-factory_bot (~> 2.22)
|
71
|
+
ruby-openai (4.0.0)
|
72
|
+
faraday (>= 1)
|
73
|
+
faraday-multipart (>= 1)
|
74
|
+
ruby-progressbar (1.13.0)
|
75
|
+
ruby2_keywords (0.0.5)
|
76
|
+
unicode-display_width (2.4.2)
|
77
|
+
|
78
|
+
PLATFORMS
|
79
|
+
x86_64-linux
|
80
|
+
|
81
|
+
DEPENDENCIES
|
82
|
+
nano-bots!
|
83
|
+
rspec (~> 3.12)
|
84
|
+
rubocop (~> 1.47)
|
85
|
+
rubocop-rspec (~> 2.22)
|
86
|
+
|
87
|
+
BUNDLED WITH
|
88
|
+
2.4.13
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2023 icebaker
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
# Nano Bots 💎 🤖
|
2
|
+
|
3
|
+
A Ruby implementation of the [Nano Bots](https://github.com/icebaker/nano-bots) specification.
|
4
|
+
|
5
|
+
![Ruby Nano Bots](https://user-images.githubusercontent.com/113217272/237839690-7880915a-b287-4484-a75e-0b96284b8a32.png)
|
6
|
+
_Image artificially created by Midjourney through a prompt generated by a Nano Bot specialized in Midjourney._
|
7
|
+
|
8
|
+
https://user-images.githubusercontent.com/113217272/237840989-1e29a5cc-6644-48d0-87b4-62798dc6ebd3.mp4
|
9
|
+
|
10
|
+
- [Setup](#setup)
|
11
|
+
- [Usage](#usage)
|
12
|
+
- [Command Line](#command-line)
|
13
|
+
- [Library](#library)
|
14
|
+
- [Cartridges](#cartridges)
|
15
|
+
- [Development](#development)
|
16
|
+
- [Publish to RubyGems](#publish-to-rubygems)
|
17
|
+
|
18
|
+
## Setup
|
19
|
+
|
20
|
+
For a system usage:
|
21
|
+
|
22
|
+
```sh
|
23
|
+
gem install nano-bots -v 0.0.1
|
24
|
+
```
|
25
|
+
|
26
|
+
To use it in a project, add it to your `Gemfile`:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
gem 'nano-bots', '~> 0.0.1'
|
30
|
+
```
|
31
|
+
|
32
|
+
```sh
|
33
|
+
bundle install
|
34
|
+
```
|
35
|
+
|
36
|
+
For credentials and configurations, relevant environment variables can be set in your `.bashrc`, `.zshrc`, or equivalent files, as well as in your Docker Container or System Environment. Example:
|
37
|
+
|
38
|
+
```sh
|
39
|
+
export OPENAI_API_ADDRESS=https://api.openai.com
|
40
|
+
export OPENAI_API_ACCESS_TOKEN=your-token
|
41
|
+
export OPENAI_API_USER_IDENTIFIER=your-user
|
42
|
+
|
43
|
+
export NANO_BOTS_STATE_DIRECTORY=/home/user/.local/state/nano-bots
|
44
|
+
export NANO_BOTS_CARTRIDGES_DIRECTORY=/home/user/.local/share/nano-bots/cartridges
|
45
|
+
```
|
46
|
+
|
47
|
+
Alternatively, if your current directory has a `.env` file with the environment variables, they will be automatically loaded.
|
48
|
+
|
49
|
+
## Usage
|
50
|
+
|
51
|
+
### Command Line
|
52
|
+
|
53
|
+
After installing the gem, the `rnb` binary command will be available for your project or system.
|
54
|
+
|
55
|
+
Examples of usage:
|
56
|
+
|
57
|
+
```bash
|
58
|
+
rnb to-en-us-translator.yml - eval "Salut, comment ça va?"
|
59
|
+
# => Hello, how are you doing?
|
60
|
+
|
61
|
+
rnb midjourney.yml - eval "happy and friendly cyberpunk robot"
|
62
|
+
# => The robot exploring a bustling city, surrounded by neon lights
|
63
|
+
# and high-rise buildings. The prompt should include colorful
|
64
|
+
# lighting and a sense of excitement in the facial expression.
|
65
|
+
|
66
|
+
rnb lisp.yml - eval "(+ 1 2)"
|
67
|
+
# => 3
|
68
|
+
|
69
|
+
cat article.txt |
|
70
|
+
rnb to-en-us-translator.yml - eval |
|
71
|
+
rnb summarizer.yml - eval
|
72
|
+
# -> LLM stands for Large Language Model, which refers to an
|
73
|
+
# artificial intelligence algorithm capable of processing
|
74
|
+
# and understanding vast amounts of natural language data,
|
75
|
+
# allowing it to generate human-like responses and perform
|
76
|
+
# a range of language-related tasks.
|
77
|
+
```
|
78
|
+
|
79
|
+
```bash
|
80
|
+
rnb assistant.yml - repl
|
81
|
+
```
|
82
|
+
|
83
|
+
All of the commands above are stateless. If you want to preserve the history of your interactions, replace the `-` with a state key. You can use a simple key, such as your username, or a randomly generated one:
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
require 'securerandom'
|
87
|
+
|
88
|
+
SecureRandom.hex # => 6ea6c43c42a1c076b1e3c36fa349ac2c
|
89
|
+
```
|
90
|
+
|
91
|
+
```bash
|
92
|
+
rnb assistant.yml your-user eval "Salut, comment ça va?"
|
93
|
+
rnb assistant.yml your-user repl
|
94
|
+
|
95
|
+
rnb assistant.yml 6ea6c43c42a1c076b1e3c36fa349ac2c eval "Salut, comment ça va?"
|
96
|
+
rnb assistant.yml 6ea6c43c42a1c076b1e3c36fa349ac2c repl
|
97
|
+
```
|
98
|
+
|
99
|
+
### Library
|
100
|
+
|
101
|
+
To use it as a library:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
require 'nano-bots/cli' # Equivalent to the `rnb` command.
|
105
|
+
```
|
106
|
+
|
107
|
+
```ruby
|
108
|
+
require 'nano-bots'
|
109
|
+
|
110
|
+
NanoBot.cli # Equivalent to the `rnb` command.
|
111
|
+
|
112
|
+
NanoBot.repl(cartridge: 'cartridge.yml') # Starts a new REPL.
|
113
|
+
|
114
|
+
bot = NanoBot.new(cartridge: 'cartridge.yml')
|
115
|
+
|
116
|
+
bot.eval('Hello')
|
117
|
+
|
118
|
+
bot.repl # Starts a new REPL.
|
119
|
+
|
120
|
+
NanoBot.repl(cartridge: 'cartridge.yml', state: '6ea6c43c42a1c076b1e3c36fa349ac2c')
|
121
|
+
|
122
|
+
bot = NanoBot.new(cartridge: 'cartridge.yml', state: '6ea6c43c42a1c076b1e3c36fa349ac2c')
|
123
|
+
```
|
124
|
+
|
125
|
+
## Cartridges
|
126
|
+
|
127
|
+
Here's what a Nano Bot Cartridge looks like:
|
128
|
+
|
129
|
+
```yaml
|
130
|
+
---
|
131
|
+
name: Assistant
|
132
|
+
version: 0.0.1
|
133
|
+
|
134
|
+
behaviors:
|
135
|
+
interaction:
|
136
|
+
directive: You are a helpful assistant.
|
137
|
+
|
138
|
+
interfaces:
|
139
|
+
repl:
|
140
|
+
prompt:
|
141
|
+
- text: '🤖'
|
142
|
+
- text: '> '
|
143
|
+
color: blue
|
144
|
+
|
145
|
+
provider:
|
146
|
+
name: openai
|
147
|
+
settings:
|
148
|
+
model: gpt-3.5-turbo
|
149
|
+
credentials:
|
150
|
+
address: ENV/OPENAI_API_ADDRESS
|
151
|
+
access-token: ENV/OPENAI_API_ACCESS_TOKEN
|
152
|
+
user-identifier: ENV/OPENAI_API_USER_IDENTIFIER
|
153
|
+
```
|
154
|
+
|
155
|
+
Check the Nano Bots specification to learn more about [how to build cartridges](https://icebaker.github.io/nano-bots/#/README?id=cartridges).
|
156
|
+
|
157
|
+
## Development
|
158
|
+
|
159
|
+
```bash
|
160
|
+
bundle
|
161
|
+
rubocop -A
|
162
|
+
rspec
|
163
|
+
```
|
164
|
+
|
165
|
+
### Publish to RubyGems
|
166
|
+
|
167
|
+
```bash
|
168
|
+
gem build nano-bots.gemspec
|
169
|
+
|
170
|
+
gem signin
|
171
|
+
|
172
|
+
gem push nano-bots-0.0.1.gem
|
173
|
+
```
|
data/bin/rnb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openai'
|
4
|
+
|
5
|
+
require_relative './providers/openai'
|
6
|
+
|
7
|
+
module NanoBot
|
8
|
+
module Components
|
9
|
+
class Provider
|
10
|
+
def self.new(provider)
|
11
|
+
case provider[:name]
|
12
|
+
when 'openai'
|
13
|
+
Providers::OpenAI.new(provider[:settings])
|
14
|
+
else
|
15
|
+
raise "Unsupported provider #{provider[:name]}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openai'
|
4
|
+
|
5
|
+
module NanoBot
|
6
|
+
module Components
|
7
|
+
module Providers
|
8
|
+
class Base
|
9
|
+
def evaluate(_payload)
|
10
|
+
raise NoMethodError, "The 'evaluate' method is not implemented for the current provider."
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openai'
|
4
|
+
|
5
|
+
require_relative './base'
|
6
|
+
|
7
|
+
module NanoBot
|
8
|
+
module Components
|
9
|
+
module Providers
|
10
|
+
class OpenAI < Base
|
11
|
+
CHAT_SETTINGS = %i[
|
12
|
+
model stream temperature top_p n stop max_tokens
|
13
|
+
presence_penalty frequency_penalty logit_bias
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
attr_reader :settings
|
17
|
+
|
18
|
+
def initialize(settings)
|
19
|
+
@settings = settings
|
20
|
+
|
21
|
+
@client = ::OpenAI::Client.new(
|
22
|
+
uri_base: "#{@settings[:credentials][:address].sub(%r{/$}, '')}/",
|
23
|
+
access_token: @settings[:credentials][:'access-token']
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def evaluate(input, &block)
|
28
|
+
messages = input[:history].map do |event|
|
29
|
+
{ role: event[:who] == 'user' ? 'user' : 'assistant',
|
30
|
+
content: event[:message] }
|
31
|
+
end
|
32
|
+
|
33
|
+
%i[instruction backdrop directive].each do |key|
|
34
|
+
next unless input[:behavior][key]
|
35
|
+
|
36
|
+
messages.prepend(
|
37
|
+
{ role: key == :directive ? 'system' : 'user',
|
38
|
+
content: input[:behavior][key] }
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
payload = {
|
43
|
+
model: @settings[:model],
|
44
|
+
user: @settings[:credentials][:'user-identifier'],
|
45
|
+
messages:
|
46
|
+
}
|
47
|
+
|
48
|
+
CHAT_SETTINGS.each do |key|
|
49
|
+
payload[key] = @settings[key] if @settings.key?(key)
|
50
|
+
end
|
51
|
+
|
52
|
+
payload.delete(:logit_bias) if payload.key?(:logit_bias) && payload[:logit_bias].nil?
|
53
|
+
|
54
|
+
if @settings[:stream] && input[:interface][:stream]
|
55
|
+
content = ''
|
56
|
+
|
57
|
+
payload[:stream] = proc do |chunk, _bytesize|
|
58
|
+
partial = chunk.dig('choices', 0, 'delta', 'content')
|
59
|
+
if partial
|
60
|
+
content += partial
|
61
|
+
block.call({ who: 'AI', message: partial }, false)
|
62
|
+
end
|
63
|
+
|
64
|
+
block.call({ who: 'AI', message: content }, true) if chunk.dig('choices', 0, 'finish_reason')
|
65
|
+
end
|
66
|
+
|
67
|
+
@client.chat(parameters: payload)
|
68
|
+
else
|
69
|
+
result = @client.chat(parameters: payload)
|
70
|
+
|
71
|
+
raise StandardError, result['error'] if result['error']
|
72
|
+
|
73
|
+
block.call({ who: 'AI', message: result.dig('choices', 0, 'message', 'content') }, true)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'babosa'
|
4
|
+
|
5
|
+
require_relative '../logic/helpers/hash'
|
6
|
+
|
7
|
+
module NanoBot
|
8
|
+
module Components
|
9
|
+
class Storage
|
10
|
+
def self.build_path_and_ensure_state_file!(key, cartridge)
|
11
|
+
path = [
|
12
|
+
Logic::Helpers::Hash.fetch(cartridge, %i[state directory]),
|
13
|
+
ENV.fetch('NANO_BOTS_STATE_DIRECTORY', nil)
|
14
|
+
].find do |candidate|
|
15
|
+
!candidate.nil? && !candidate.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
path = "#{user_home!.sub(%r{/$}, '')}/.local/state/nano-bots" if path.nil?
|
19
|
+
|
20
|
+
path = "#{path.sub(%r{/$}, '')}/nano-bots-rb/#{cartridge[:name].to_slug.normalize}"
|
21
|
+
path = "#{path}/#{cartridge[:version].to_slug.normalize}/#{key.to_slug.normalize}"
|
22
|
+
path = "#{path}/state.json"
|
23
|
+
|
24
|
+
FileUtils.mkdir_p(File.dirname(path))
|
25
|
+
|
26
|
+
File.write(path, JSON.generate({ key:, history: [] })) unless File.exist?(path)
|
27
|
+
|
28
|
+
path
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.cartridge_path(path)
|
32
|
+
partial = File.join(File.dirname(path), File.basename(path, File.extname(path)))
|
33
|
+
|
34
|
+
candidates = [
|
35
|
+
path,
|
36
|
+
"#{partial}.yml",
|
37
|
+
"#{partial}.yaml"
|
38
|
+
]
|
39
|
+
|
40
|
+
unless ENV.fetch('NANO_BOTS_CARTRIDGES_DIRECTORY', nil).nil?
|
41
|
+
directory = ENV.fetch('NANO_BOTS_CARTRIDGES_DIRECTORY').sub(%r{/$}, '')
|
42
|
+
|
43
|
+
partial = File.join(File.dirname(partial), File.basename(partial, File.extname(partial)))
|
44
|
+
|
45
|
+
partial = path.sub(%r{^\.?/}, '')
|
46
|
+
|
47
|
+
candidates << "#{directory}/#{partial}"
|
48
|
+
candidates << "#{directory}/#{partial}.yml"
|
49
|
+
candidates << "#{directory}/#{partial}.yaml"
|
50
|
+
end
|
51
|
+
|
52
|
+
directory = "#{user_home!.sub(%r{/$}, '')}/.local/share/nano-bots/cartridges"
|
53
|
+
|
54
|
+
partial = File.join(File.dirname(partial), File.basename(partial, File.extname(partial)))
|
55
|
+
|
56
|
+
partial = path.sub(%r{^\.?/}, '')
|
57
|
+
|
58
|
+
candidates << "#{directory}/#{partial}"
|
59
|
+
candidates << "#{directory}/#{partial}.yml"
|
60
|
+
candidates << "#{directory}/#{partial}.yaml"
|
61
|
+
|
62
|
+
candidates = candidates.uniq
|
63
|
+
|
64
|
+
candidates.find do |candidate|
|
65
|
+
File.exist?(candidate) && File.file?(candidate)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.user_home!
|
70
|
+
[Dir.home, `echo ~`.strip, '~'].find do |candidate|
|
71
|
+
!candidate.nil? && !candidate.empty?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
require_relative '../logic/helpers/hash'
|
6
|
+
require_relative '../components/provider'
|
7
|
+
require_relative '../components/storage'
|
8
|
+
require_relative './interfaces/repl'
|
9
|
+
require_relative './session'
|
10
|
+
|
11
|
+
module NanoBot
|
12
|
+
module Controllers
|
13
|
+
class Instance
|
14
|
+
def initialize(cartridge_path:, state: nil)
|
15
|
+
load_cartridge!(cartridge_path)
|
16
|
+
|
17
|
+
provider = Components::Provider.new(@cartridge[:provider])
|
18
|
+
|
19
|
+
@session = Session.new(provider:, cartridge: @cartridge, state:)
|
20
|
+
end
|
21
|
+
|
22
|
+
def debug
|
23
|
+
@session.debug
|
24
|
+
end
|
25
|
+
|
26
|
+
def eval(input)
|
27
|
+
@session.evaluate_and_print(input, mode: 'eval')
|
28
|
+
end
|
29
|
+
|
30
|
+
def repl
|
31
|
+
Interfaces::REPL.start(@cartridge, @session)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def load_cartridge!(path)
|
37
|
+
@cartridge = Logic::Helpers::Hash.symbolize_keys(
|
38
|
+
YAML.safe_load(
|
39
|
+
File.read(Components::Storage.cartridge_path(path)),
|
40
|
+
permitted_classes: [Symbol]
|
41
|
+
)
|
42
|
+
)
|
43
|
+
|
44
|
+
inject_environment_variables!(@cartridge)
|
45
|
+
end
|
46
|
+
|
47
|
+
def inject_environment_variables!(node)
|
48
|
+
case node
|
49
|
+
when Hash
|
50
|
+
node.each do |key, value|
|
51
|
+
node[key] = inject_environment_variables!(value)
|
52
|
+
end
|
53
|
+
when Array
|
54
|
+
node.each_with_index do |value, index|
|
55
|
+
node[index] = inject_environment_variables!(value)
|
56
|
+
end
|
57
|
+
when String
|
58
|
+
node.start_with?('ENV') ? ENV.fetch(node.sub(/^ENV./, ''), nil) : node
|
59
|
+
else
|
60
|
+
node
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../instance'
|
4
|
+
require_relative '../../static/gem'
|
5
|
+
|
6
|
+
module NanoBot
|
7
|
+
module Controllers
|
8
|
+
module Interfaces
|
9
|
+
module CLI
|
10
|
+
def self.handle!
|
11
|
+
case ARGV[0]
|
12
|
+
when 'version'
|
13
|
+
puts NanoBot::GEM[:version]
|
14
|
+
exit
|
15
|
+
when 'help', '', nil
|
16
|
+
puts "Ruby Nano Bots #{NanoBot::GEM[:version]}"
|
17
|
+
puts ' rnb cartridge.yml - eval "Hello"'
|
18
|
+
puts ' rnb cartridge.yml - repl'
|
19
|
+
puts ' rnb cartridge.yml - debug'
|
20
|
+
puts ' rnb cartridge.yml STATE-KEY eval "Hello"'
|
21
|
+
puts ' rnb cartridge.yml STATE-KEY repl'
|
22
|
+
puts ' rnb cartridge.yml STATE-KEY debug'
|
23
|
+
puts ' rnb version'
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
|
27
|
+
params = { cartridge_path: ARGV[0], state: ARGV[1], command: ARGV[2] }
|
28
|
+
|
29
|
+
bot = Instance.new(cartridge_path: params[:cartridge_path], state: params[:state])
|
30
|
+
|
31
|
+
case params[:command]
|
32
|
+
when 'eval'
|
33
|
+
params[:input] = ARGV[3..]&.join(' ')
|
34
|
+
params[:input] = $stdin.read.chomp if params[:input].nil? || params[:input].empty?
|
35
|
+
bot.eval(params[:input])
|
36
|
+
when 'repl'
|
37
|
+
bot.repl
|
38
|
+
when 'debug'
|
39
|
+
bot.debug
|
40
|
+
else
|
41
|
+
raise "TODO: [#{params[:command]}]"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pry'
|
4
|
+
require 'rainbow'
|
5
|
+
|
6
|
+
require_relative '../../logic/helpers/hash'
|
7
|
+
|
8
|
+
module NanoBot
|
9
|
+
module Controllers
|
10
|
+
module Interfaces
|
11
|
+
module REPL
|
12
|
+
def self.start(cartridge, session)
|
13
|
+
if Logic::Helpers::Hash.fetch(
|
14
|
+
cartridge, %i[interfaces repl prefix]
|
15
|
+
)
|
16
|
+
session.print(Logic::Helpers::Hash.fetch(cartridge,
|
17
|
+
%i[interfaces repl prefix]))
|
18
|
+
end
|
19
|
+
|
20
|
+
session.boot(mode: 'repl')
|
21
|
+
|
22
|
+
session.print(Logic::Helpers::Hash.fetch(cartridge, %i[interfaces repl postfix]) || "\n")
|
23
|
+
|
24
|
+
session.flush
|
25
|
+
|
26
|
+
prompt = build_prompt(cartridge[:interfaces][:repl][:prompt])
|
27
|
+
|
28
|
+
Pry.config.prompt = Pry::Prompt.new(
|
29
|
+
'REPL',
|
30
|
+
'REPL Prompt',
|
31
|
+
[proc { prompt }, proc { 'MISSING INPUT' }]
|
32
|
+
)
|
33
|
+
|
34
|
+
Pry.commands.block_command(/(.*)/, 'handler') do |line|
|
35
|
+
if Logic::Helpers::Hash.fetch(
|
36
|
+
cartridge, %i[interfaces repl prefix]
|
37
|
+
)
|
38
|
+
session.print(Logic::Helpers::Hash.fetch(
|
39
|
+
cartridge, %i[interfaces repl prefix]
|
40
|
+
))
|
41
|
+
end
|
42
|
+
|
43
|
+
session.evaluate_and_print(line, mode: 'repl')
|
44
|
+
session.print(Logic::Helpers::Hash.fetch(cartridge, %i[interfaces repl postfix]) || "\n")
|
45
|
+
session.flush
|
46
|
+
end
|
47
|
+
|
48
|
+
Pry.start
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.build_prompt(prompt)
|
52
|
+
result = ''
|
53
|
+
|
54
|
+
prompt.each do |partial|
|
55
|
+
result += if partial[:color]
|
56
|
+
Rainbow(partial[:text]).send(partial[:color])
|
57
|
+
else
|
58
|
+
partial[:text]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
result
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'babosa'
|
4
|
+
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
require_relative '../logic/helpers/hash'
|
8
|
+
require_relative '../components/storage'
|
9
|
+
|
10
|
+
module NanoBot
|
11
|
+
module Controllers
|
12
|
+
STREAM_TIMEOUT_IN_SECONDS = 5
|
13
|
+
|
14
|
+
class Session
|
15
|
+
def initialize(provider:, cartridge:, state: nil)
|
16
|
+
@provider = provider
|
17
|
+
@cartridge = cartridge
|
18
|
+
|
19
|
+
@output = $stdout
|
20
|
+
|
21
|
+
@stateless = state.nil? || state.strip == '-' || state.strip.empty?
|
22
|
+
|
23
|
+
if @stateless
|
24
|
+
@state = { history: [] }
|
25
|
+
else
|
26
|
+
@state_path = Components::Storage.build_path_and_ensure_state_file!(
|
27
|
+
state.strip, @cartridge
|
28
|
+
)
|
29
|
+
@state = load_state
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def debug
|
34
|
+
pp({
|
35
|
+
state: {
|
36
|
+
path: @state_path,
|
37
|
+
content: @state
|
38
|
+
}
|
39
|
+
})
|
40
|
+
end
|
41
|
+
|
42
|
+
def load_state
|
43
|
+
@state = Logic::Helpers::Hash.symbolize_keys(JSON.parse(File.read(@state_path)))
|
44
|
+
end
|
45
|
+
|
46
|
+
def store_state!
|
47
|
+
File.write(@state_path, JSON.generate(@state))
|
48
|
+
end
|
49
|
+
|
50
|
+
def boot(mode:)
|
51
|
+
return unless Logic::Helpers::Hash.fetch(@cartridge, %i[behaviors boot instruction])
|
52
|
+
|
53
|
+
behavior = Logic::Helpers::Hash.fetch(@cartridge, %i[behaviors boot]) || {}
|
54
|
+
|
55
|
+
input = { behavior:, history: [] }
|
56
|
+
|
57
|
+
process(input, mode:)
|
58
|
+
end
|
59
|
+
|
60
|
+
def evaluate_and_print(message, mode:)
|
61
|
+
behavior = Logic::Helpers::Hash.fetch(@cartridge, %i[behaviors interaction]) || {}
|
62
|
+
|
63
|
+
@state[:history] << ({ who: 'user', message: })
|
64
|
+
|
65
|
+
input = { behavior:, history: @state[:history] }
|
66
|
+
|
67
|
+
process(input, mode:)
|
68
|
+
end
|
69
|
+
|
70
|
+
def process(input, mode:)
|
71
|
+
streaming = @provider.settings[:stream] && Logic::Helpers::Hash.fetch(
|
72
|
+
@cartridge, [:interfaces, mode.to_sym, :stream]
|
73
|
+
)
|
74
|
+
|
75
|
+
interface = Logic::Helpers::Hash.fetch(@cartridge, [:interfaces, mode.to_sym]) || {}
|
76
|
+
|
77
|
+
input[:interface] = interface
|
78
|
+
|
79
|
+
updated_at = Time.now
|
80
|
+
|
81
|
+
ready = false
|
82
|
+
@provider.evaluate(input) do |output, finished|
|
83
|
+
updated_at = Time.now
|
84
|
+
if finished
|
85
|
+
@state[:history] << output
|
86
|
+
self.print(output[:message]) unless streaming
|
87
|
+
unless Logic::Helpers::Hash.fetch(@cartridge, [:interfaces, mode.to_sym, :postfix]).nil?
|
88
|
+
self.print(Logic::Helpers::Hash.fetch(@cartridge, [:interfaces, mode.to_sym, :postfix]))
|
89
|
+
end
|
90
|
+
ready = true
|
91
|
+
flush
|
92
|
+
elsif streaming
|
93
|
+
self.print(output[:message])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
until ready
|
98
|
+
seconds = (Time.now - updated_at).to_i
|
99
|
+
raise StandardError, 'The stream has become unresponsive.' if seconds >= STREAM_TIMEOUT_IN_SECONDS
|
100
|
+
end
|
101
|
+
|
102
|
+
store_state! unless @stateless
|
103
|
+
end
|
104
|
+
|
105
|
+
def flush
|
106
|
+
@output.flush
|
107
|
+
end
|
108
|
+
|
109
|
+
def print(content)
|
110
|
+
@output.write(content)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NanoBot
|
4
|
+
module Logic
|
5
|
+
module Helpers
|
6
|
+
module Hash
|
7
|
+
def self.symbolize_keys(object)
|
8
|
+
case object
|
9
|
+
when ::Hash
|
10
|
+
object.each_with_object({}) do |(key, value), result|
|
11
|
+
result[key.to_sym] = symbolize_keys(value)
|
12
|
+
end
|
13
|
+
when Array
|
14
|
+
object.map { |e| symbolize_keys(e) }
|
15
|
+
else
|
16
|
+
object
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.fetch(object, path)
|
21
|
+
node = object
|
22
|
+
|
23
|
+
return nil unless node
|
24
|
+
|
25
|
+
path.each do |key|
|
26
|
+
node = node[key]
|
27
|
+
break if node.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
node
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/nano-bots.gemspec
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'static/gem'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = NanoBot::GEM[:name]
|
7
|
+
spec.version = NanoBot::GEM[:version]
|
8
|
+
spec.authors = [NanoBot::GEM[:author]]
|
9
|
+
|
10
|
+
spec.summary = NanoBot::GEM[:summary]
|
11
|
+
spec.description = NanoBot::GEM[:description]
|
12
|
+
|
13
|
+
spec.homepage = NanoBot::GEM[:github]
|
14
|
+
|
15
|
+
spec.license = NanoBot::GEM[:license]
|
16
|
+
|
17
|
+
spec.required_ruby_version = Gem::Requirement.new(">= #{NanoBot::GEM[:ruby]}")
|
18
|
+
|
19
|
+
spec.metadata['allowed_push_host'] = NanoBot::GEM[:gem_server]
|
20
|
+
|
21
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
22
|
+
spec.metadata['source_code_uri'] = NanoBot::GEM[:github]
|
23
|
+
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
+
f.match(%r{\A(?:test|spec|features)/})
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
spec.require_paths = ['ports/dsl']
|
31
|
+
|
32
|
+
spec.executables = ['rnb']
|
33
|
+
|
34
|
+
spec.add_dependency 'babosa', '~> 2.0'
|
35
|
+
spec.add_dependency 'dotenv', '~> 2.8', '>= 2.8.1'
|
36
|
+
spec.add_dependency 'faraday', '~> 2.7', '>= 2.7.4'
|
37
|
+
spec.add_dependency 'pry', '~> 0.14.2'
|
38
|
+
spec.add_dependency 'rainbow', '~> 3.1', '>= 3.1.1'
|
39
|
+
spec.add_dependency 'ruby-openai', '~> 4.0'
|
40
|
+
|
41
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
42
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dotenv/load'
|
4
|
+
|
5
|
+
require_relative '../../static/gem'
|
6
|
+
require_relative '../../controllers/instance'
|
7
|
+
require_relative '../../controllers/interfaces/cli'
|
8
|
+
|
9
|
+
module NanoBot
|
10
|
+
def self.new(cartridge:, state: '-')
|
11
|
+
Controllers::Instance.new(cartridge_path: cartridge, state:)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.cli
|
15
|
+
Controllers::Interfaces::CLI.handle!
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.repl(cartridge:, state: '-')
|
19
|
+
Controllers::Instance.new(cartridge_path: cartridge, state:).repl
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.version
|
23
|
+
NanoBot::GEM[:version]
|
24
|
+
end
|
25
|
+
end
|
data/static/gem.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module NanoBot
|
4
|
+
GEM = {
|
5
|
+
name: 'nano-bots',
|
6
|
+
version: '0.0.1',
|
7
|
+
author: 'icebaker',
|
8
|
+
summary: 'Ruby Implementation of Nano Bots: small, AI-powered bots',
|
9
|
+
description: 'Ruby Implementation of Nano Bots: small, AI-powered bots easily shared as a single file, designed to support multiple providers such as Vicuna, OpenAI ChatGPT, Google PaLM, Alpaca, and LLaMA.',
|
10
|
+
github: 'https://github.com/icebaker/ruby-nano-bots',
|
11
|
+
gem_server: 'https://rubygems.org',
|
12
|
+
license: 'MIT',
|
13
|
+
ruby: '3.1.4'
|
14
|
+
}.freeze
|
15
|
+
end
|
metadata
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nano-bots
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- icebaker
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-05-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: babosa
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dotenv
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.8'
|
34
|
+
- - ">="
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: 2.8.1
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '2.8'
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 2.8.1
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: faraday
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.7'
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 2.7.4
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '2.7'
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 2.7.4
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: pry
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 0.14.2
|
74
|
+
type: :runtime
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - "~>"
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: 0.14.2
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: rainbow
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - "~>"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '3.1'
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 3.1.1
|
91
|
+
type: :runtime
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '3.1'
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: 3.1.1
|
101
|
+
- !ruby/object:Gem::Dependency
|
102
|
+
name: ruby-openai
|
103
|
+
requirement: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - "~>"
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '4.0'
|
108
|
+
type: :runtime
|
109
|
+
prerelease: false
|
110
|
+
version_requirements: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - "~>"
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '4.0'
|
115
|
+
description: 'Ruby Implementation of Nano Bots: small, AI-powered bots easily shared
|
116
|
+
as a single file, designed to support multiple providers such as Vicuna, OpenAI
|
117
|
+
ChatGPT, Google PaLM, Alpaca, and LLaMA.'
|
118
|
+
email:
|
119
|
+
executables:
|
120
|
+
- rnb
|
121
|
+
extensions: []
|
122
|
+
extra_rdoc_files: []
|
123
|
+
files:
|
124
|
+
- ".gitignore"
|
125
|
+
- ".rspec"
|
126
|
+
- ".rubocop.yml"
|
127
|
+
- Gemfile
|
128
|
+
- Gemfile.lock
|
129
|
+
- LICENSE
|
130
|
+
- README.md
|
131
|
+
- bin/rnb
|
132
|
+
- components/provider.rb
|
133
|
+
- components/providers/base.rb
|
134
|
+
- components/providers/openai.rb
|
135
|
+
- components/storage.rb
|
136
|
+
- controllers/instance.rb
|
137
|
+
- controllers/interfaces/cli.rb
|
138
|
+
- controllers/interfaces/repl.rb
|
139
|
+
- controllers/session.rb
|
140
|
+
- logic/helpers/hash.rb
|
141
|
+
- nano-bots.gemspec
|
142
|
+
- ports/dsl/nano-bots.rb
|
143
|
+
- ports/dsl/nano-bots/cli.rb
|
144
|
+
- static/gem.rb
|
145
|
+
homepage: https://github.com/icebaker/ruby-nano-bots
|
146
|
+
licenses:
|
147
|
+
- MIT
|
148
|
+
metadata:
|
149
|
+
allowed_push_host: https://rubygems.org
|
150
|
+
homepage_uri: https://github.com/icebaker/ruby-nano-bots
|
151
|
+
source_code_uri: https://github.com/icebaker/ruby-nano-bots
|
152
|
+
rubygems_mfa_required: 'true'
|
153
|
+
post_install_message:
|
154
|
+
rdoc_options: []
|
155
|
+
require_paths:
|
156
|
+
- ports/dsl
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: 3.1.4
|
162
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
requirements: []
|
168
|
+
rubygems_version: 3.4.10
|
169
|
+
signing_key:
|
170
|
+
specification_version: 4
|
171
|
+
summary: 'Ruby Implementation of Nano Bots: small, AI-powered bots'
|
172
|
+
test_files: []
|