sawara 0.1.1 → 1.0.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/Gemfile +1 -0
- data/Gemfile.lock +2 -0
- data/README.md +52 -6
- data/exe/sawara +1 -3
- data/lib/sawara/api_key.rb +27 -0
- data/lib/sawara/bot.rb +112 -0
- data/lib/sawara/chat_client.rb +2 -3
- data/lib/sawara/cli.rb +58 -0
- data/lib/sawara/talk.rb +18 -5
- data/lib/sawara/user_config.rb +32 -0
- data/lib/sawara/version.rb +1 -1
- data/lib/sawara.rb +4 -1
- metadata +6 -4
- data/lib/sawara/config.rb +0 -37
- data/sawara.gemspec +0 -39
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 33c01867584475125d8830efce467704af83e4704ea7eefd8160f7884fd726fe
|
4
|
+
data.tar.gz: e5fa76b6f98772772bdaa27443c858acef19fad65c5cd7ad6fbf31331e771dc0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f38fcf4a2acff8d37828661a4dd5a44dd5f79ca357dca3fd9c22a19dff3e5dc526c99866351e15c00b21164382fc832014037a7c7b8cb4906faa12bbafb08ce4
|
7
|
+
data.tar.gz: 5cd70107c0d993abc2027478cccf5d3d293f087b1a6c10d6b7bb09aab5c50b58e71bd9961b3fa4999fabc4c912f8ccb1b1dca175a56ad167336b9cfe3a5e3aa8
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -45,6 +45,7 @@ GEM
|
|
45
45
|
ruby-openai (3.5.0)
|
46
46
|
httparty (>= 0.18.1)
|
47
47
|
ruby-progressbar (1.13.0)
|
48
|
+
thor (1.2.1)
|
48
49
|
unicode-display_width (2.4.2)
|
49
50
|
|
50
51
|
PLATFORMS
|
@@ -56,6 +57,7 @@ DEPENDENCIES
|
|
56
57
|
rspec
|
57
58
|
rubocop
|
58
59
|
ruby-openai
|
60
|
+
thor
|
59
61
|
|
60
62
|
BUNDLED WITH
|
61
63
|
2.4.7
|
data/README.md
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
# Sawara
|
2
2
|
|
3
|
-
|
3
|
+

|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
Sawara is a Ruby Gem and command-line interface (CLI) tool that enables you to use ChatGPT in your terminal via OpenAI API.
|
8
|
+
|
9
|
+
With Sawara, you can easily create and manage multiple bots with different prompts, and have natural language conversations with them through the CLI.
|
10
|
+
|
11
|
+
Of course, you can also start a conversation with a bot without setting a prompt in advance.
|
4
12
|
|
5
13
|
## Installation
|
6
14
|
|
@@ -9,15 +17,53 @@ Sawara is a Ruby Gem for using ChatGPT in your terminal via OpenAI API.
|
|
9
17
|
## Initial setup
|
10
18
|
|
11
19
|
1. Get your API key from [https://platform.openai.com/account/api-keys](https://platform.openai.com/account/api-keys)
|
12
|
-
2. `$ sawara`
|
20
|
+
2. `$ sawara call` or `$ sawara setkey`
|
13
21
|
3. Enter your API Key.
|
14
22
|
|
15
23
|
## Usage
|
16
24
|
|
17
|
-
|
18
|
-
|
19
|
-
$ sawara
|
20
|
-
|
25
|
+
### Call a bot
|
26
|
+
|
27
|
+
$ sawara call
|
28
|
+
$ sawara -c
|
29
|
+
|
30
|
+
Use the `sawara call` command to start a conversation.
|
31
|
+
|
32
|
+
### Call a registered bot
|
33
|
+
|
34
|
+
$ sawara call <bot_id>
|
35
|
+
$ sawara -c <bot_id>
|
36
|
+
|
37
|
+
Use the `sawara call` command followed by the ID of the bot to start a conversation with the registered bot.
|
38
|
+
|
39
|
+
### Add a bot
|
40
|
+
|
41
|
+
$ sawara add <bot_id>
|
42
|
+
|
43
|
+
Use the `sawara add` command followed by the ID of the bot you want to register. You'll be asked to provide a name and a prompt for the bot.
|
44
|
+
|
45
|
+
### Delete a bot
|
46
|
+
|
47
|
+
$ sawara delete <bot_id>
|
48
|
+
|
49
|
+
Use the `sawara delete` command followed by the ID of the bot you want to remove.
|
50
|
+
|
51
|
+
### List all bots
|
52
|
+
|
53
|
+
$ sawara list
|
54
|
+
$ sawara -l
|
55
|
+
|
56
|
+
Use the `sawara list` command to list all the bots you've registered.
|
57
|
+
|
58
|
+
### Set or update your OpenAI API key
|
59
|
+
|
60
|
+
$ sawara setkey
|
61
|
+
|
62
|
+
Use the `sawara setkey` command to set or update your OpenAI API key.
|
63
|
+
|
64
|
+
### Configuration file
|
65
|
+
|
66
|
+
Configuration data will be saved in `~/.sawara.yml`.
|
21
67
|
|
22
68
|
## Development
|
23
69
|
|
data/exe/sawara
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sawara
|
4
|
+
class ApiKey
|
5
|
+
def read
|
6
|
+
UserConfig.read['api_key']
|
7
|
+
end
|
8
|
+
|
9
|
+
def update
|
10
|
+
api_key = await_api_key
|
11
|
+
return if api_key.nil?
|
12
|
+
|
13
|
+
UserConfig.save('api_key', api_key)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def await_api_key
|
19
|
+
puts 'Please enter your OpenAI API Key🔑'
|
20
|
+
input = $stdin.gets
|
21
|
+
return if input.nil?
|
22
|
+
|
23
|
+
api_key = input.chomp
|
24
|
+
api_key.empty? ? await_api_key : api_key
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/sawara/bot.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'readline'
|
4
|
+
|
5
|
+
module Sawara
|
6
|
+
class Bot
|
7
|
+
def initialize
|
8
|
+
@bots = UserConfig.read['bots']
|
9
|
+
end
|
10
|
+
|
11
|
+
def list
|
12
|
+
puts
|
13
|
+
if @bots.empty?
|
14
|
+
puts 'No bots are registered🫥'
|
15
|
+
puts 'To register a bot, use `sawara add [NAME]`.'
|
16
|
+
else
|
17
|
+
@bots.each do |id, profile|
|
18
|
+
puts "#{profile['name']}(#{id}):"
|
19
|
+
puts " \"#{profile['prompt']}\""
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def create(id)
|
25
|
+
return unless id_is_valid?(id)
|
26
|
+
|
27
|
+
name = await_name
|
28
|
+
return if name.nil?
|
29
|
+
|
30
|
+
prompt = await_prompt
|
31
|
+
return if prompt.empty?
|
32
|
+
|
33
|
+
@bots[id] = { 'name' => name, 'prompt' => prompt }
|
34
|
+
UserConfig.save('bots', @bots)
|
35
|
+
puts
|
36
|
+
puts "#{id}'s default prompt registration was successful🎉"
|
37
|
+
puts "To begin a conversation with #{id}, use `sawara -c #{id}`."
|
38
|
+
end
|
39
|
+
|
40
|
+
def find(id)
|
41
|
+
if exists?(id)
|
42
|
+
profile = @bots[id]
|
43
|
+
puts
|
44
|
+
puts "Loaded the bot \"#{profile['name']}\". The prompt is as follows:"
|
45
|
+
puts profile['prompt']
|
46
|
+
profile
|
47
|
+
else
|
48
|
+
puts "The bot has id \"#{id}\" does not exist in the registered bots."
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def delete(id)
|
53
|
+
if exists?(id)
|
54
|
+
puts "⚠️Are you sure you want to delete #{id}? (y/n)"
|
55
|
+
return unless await_confirm
|
56
|
+
|
57
|
+
@bots.delete(id)
|
58
|
+
UserConfig.save('bots', @bots)
|
59
|
+
puts
|
60
|
+
puts "\"#{id}\" has been deleted."
|
61
|
+
else
|
62
|
+
puts "The bot has id \"#{id}\" does not exist in the registered bots."
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def await_name
|
69
|
+
puts
|
70
|
+
puts 'Please enter the name of your bot😉'
|
71
|
+
line = Readline.readline
|
72
|
+
return if line.nil?
|
73
|
+
|
74
|
+
name = line.chomp
|
75
|
+
name.empty? ? await_name : name
|
76
|
+
end
|
77
|
+
|
78
|
+
def await_prompt
|
79
|
+
puts
|
80
|
+
puts 'Please enter the prompt of your bot📜'
|
81
|
+
lines = []
|
82
|
+
while (line = Readline.readline)
|
83
|
+
lines << ("#{line}\n")
|
84
|
+
end
|
85
|
+
lines.join.gsub(/^\n+|\n+$/, '')
|
86
|
+
end
|
87
|
+
|
88
|
+
def await_confirm
|
89
|
+
line = Readline.readline
|
90
|
+
return if line.nil?
|
91
|
+
|
92
|
+
confirm = line.chomp.downcase
|
93
|
+
confirm == 'y'
|
94
|
+
end
|
95
|
+
|
96
|
+
def id_is_valid?(id)
|
97
|
+
if id.empty?
|
98
|
+
puts 'Bot id must have at least one character.'
|
99
|
+
false
|
100
|
+
elsif exists?(id)
|
101
|
+
puts "\"#{id}\" is already registered id."
|
102
|
+
false
|
103
|
+
else
|
104
|
+
true
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def exists?(id)
|
109
|
+
@bots.key?(id)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/lib/sawara/chat_client.rb
CHANGED
@@ -4,8 +4,7 @@ require 'openai'
|
|
4
4
|
|
5
5
|
module Sawara
|
6
6
|
class ChatClient
|
7
|
-
def initialize(
|
8
|
-
api_key = user_config.api_key
|
7
|
+
def initialize(api_key)
|
9
8
|
OpenAI.configure do |config|
|
10
9
|
config.access_token = api_key
|
11
10
|
end
|
@@ -16,7 +15,7 @@ module Sawara
|
|
16
15
|
response = @openai_client.chat(
|
17
16
|
parameters: { model: 'gpt-3.5-turbo', messages: }
|
18
17
|
)
|
19
|
-
response.dig('choices', 0, 'message', 'content')
|
18
|
+
response.dig('choices', 0, 'message', 'content').sub(/^\n*/, '')
|
20
19
|
end
|
21
20
|
end
|
22
21
|
end
|
data/lib/sawara/cli.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module Sawara
|
6
|
+
class CLI < Thor
|
7
|
+
desc 'call [ID], -c [ID]',
|
8
|
+
'Start a conversation (If [ID] is there, preload the prompt of the bot specified by [ID])'
|
9
|
+
map '-c' => :call
|
10
|
+
def call(id = nil)
|
11
|
+
client = launch_client
|
12
|
+
if id.nil?
|
13
|
+
Talk.new.start(client)
|
14
|
+
else
|
15
|
+
profile = Bot.new.find(id)
|
16
|
+
return if profile.nil?
|
17
|
+
|
18
|
+
talk = Talk.new(profile)
|
19
|
+
talk.start(client)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'setkey', 'Register or update an API key for OpenAI API'
|
24
|
+
def setkey
|
25
|
+
ApiKey.new.update
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'add [ID]', 'Register a new bot with name and prompt'
|
29
|
+
def add(id)
|
30
|
+
Bot.new.create(id)
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'delete [ID]', 'Delete the bot specified by [ID]'
|
34
|
+
def delete(id)
|
35
|
+
Bot.new.delete(id)
|
36
|
+
end
|
37
|
+
|
38
|
+
desc 'list, -l', 'List all bots with their names and prompts'
|
39
|
+
map '-l' => :list
|
40
|
+
def list
|
41
|
+
Bot.new.list
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'version, -v', 'Print version'
|
45
|
+
map %w[-v --version] => :version
|
46
|
+
def version
|
47
|
+
puts VERSION
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def launch_client
|
53
|
+
api_key = ApiKey.new
|
54
|
+
api_key.update if api_key.read.empty?
|
55
|
+
ChatClient.new(api_key.read)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/sawara/talk.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require '
|
3
|
+
require 'readline'
|
4
4
|
|
5
5
|
module Sawara
|
6
6
|
class Talk
|
7
|
-
def initialize
|
7
|
+
def initialize(profile = nil)
|
8
8
|
@messages = []
|
9
|
+
if profile.nil?
|
10
|
+
@name = 'Sawara'
|
11
|
+
else
|
12
|
+
@name = profile['name']
|
13
|
+
@messages << { role: 'system', content: profile['prompt'] }
|
14
|
+
end
|
9
15
|
end
|
10
16
|
|
11
17
|
def start(client)
|
@@ -36,15 +42,22 @@ module Sawara
|
|
36
42
|
def await_user_content
|
37
43
|
puts
|
38
44
|
puts 'You:'
|
39
|
-
|
45
|
+
|
46
|
+
lines = []
|
47
|
+
while (line = Readline.readline)
|
48
|
+
lines << ("#{line}\n")
|
49
|
+
end
|
50
|
+
|
51
|
+
content = lines.join
|
40
52
|
@messages << { role: 'user', content: }
|
41
53
|
content
|
42
54
|
end
|
43
55
|
|
44
56
|
def await_assistant_content(client)
|
45
57
|
puts
|
46
|
-
puts
|
47
|
-
|
58
|
+
puts "#{@name}:"
|
59
|
+
|
60
|
+
content = client.fetch(@messages)
|
48
61
|
@messages << { role: 'assistant', content: }
|
49
62
|
puts content
|
50
63
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'yaml/store'
|
5
|
+
|
6
|
+
module Sawara
|
7
|
+
class UserConfig
|
8
|
+
CONFIG_PATH = "#{Dir.home}/.sawara.yml".freeze
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def read
|
12
|
+
create_config_file unless File.exist?(CONFIG_PATH)
|
13
|
+
YAML.load_file(CONFIG_PATH)
|
14
|
+
end
|
15
|
+
|
16
|
+
def save(key, value)
|
17
|
+
store = YAML::Store.new(CONFIG_PATH)
|
18
|
+
store.transaction do
|
19
|
+
store[key] = value
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def create_config_file
|
26
|
+
File.new(CONFIG_PATH, 'w')
|
27
|
+
save('api_key', '')
|
28
|
+
save('bots', {})
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/sawara/version.rb
CHANGED
data/lib/sawara.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'sawara/api_key'
|
3
4
|
require_relative 'sawara/chat_client'
|
4
|
-
require_relative 'sawara/
|
5
|
+
require_relative 'sawara/cli'
|
6
|
+
require_relative 'sawara/bot'
|
7
|
+
require_relative 'sawara/user_config'
|
5
8
|
require_relative 'sawara/talk'
|
6
9
|
require_relative 'sawara/version'
|
7
10
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sawara
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- yocajii
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-03-
|
11
|
+
date: 2023-03-09 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Sawara is a comannd-line tool for using ChatGPT via OpenAI API.
|
14
14
|
email:
|
@@ -29,11 +29,13 @@ files:
|
|
29
29
|
- Rakefile
|
30
30
|
- exe/sawara
|
31
31
|
- lib/sawara.rb
|
32
|
+
- lib/sawara/api_key.rb
|
33
|
+
- lib/sawara/bot.rb
|
32
34
|
- lib/sawara/chat_client.rb
|
33
|
-
- lib/sawara/
|
35
|
+
- lib/sawara/cli.rb
|
34
36
|
- lib/sawara/talk.rb
|
37
|
+
- lib/sawara/user_config.rb
|
35
38
|
- lib/sawara/version.rb
|
36
|
-
- sawara.gemspec
|
37
39
|
- sig/chatgpt_cli.rbs
|
38
40
|
homepage: https://github.com/yocajii/sawara
|
39
41
|
licenses:
|
data/lib/sawara/config.rb
DELETED
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'yaml'
|
4
|
-
require 'yaml/store'
|
5
|
-
|
6
|
-
module Sawara
|
7
|
-
class Config
|
8
|
-
CONFIG_PATH = "#{Dir.home}/.sawara".freeze
|
9
|
-
|
10
|
-
def initialize
|
11
|
-
unless File.exist?(CONFIG_PATH)
|
12
|
-
File.new(CONFIG_PATH, 'w')
|
13
|
-
update_config
|
14
|
-
end
|
15
|
-
@config = load_config
|
16
|
-
end
|
17
|
-
|
18
|
-
def api_key
|
19
|
-
@config['api_key']
|
20
|
-
end
|
21
|
-
|
22
|
-
private
|
23
|
-
|
24
|
-
def load_config
|
25
|
-
YAML.load_file(CONFIG_PATH)
|
26
|
-
end
|
27
|
-
|
28
|
-
def update_config
|
29
|
-
puts 'Enter your OpenAI API Key.'
|
30
|
-
api_key = $stdin.gets
|
31
|
-
store = YAML::Store.new(CONFIG_PATH)
|
32
|
-
store.transaction do
|
33
|
-
store['api_key'] = api_key
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
data/sawara.gemspec
DELETED
@@ -1,39 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'lib/sawara/version'
|
4
|
-
|
5
|
-
Gem::Specification.new do |spec|
|
6
|
-
spec.name = 'sawara'
|
7
|
-
spec.version = Sawara::VERSION
|
8
|
-
spec.authors = ['yocajii']
|
9
|
-
spec.email = ['yocajii@stone.sakura.ne.jp']
|
10
|
-
|
11
|
-
spec.summary = 'This is ChatGPT CLI.'
|
12
|
-
spec.description = 'Sawara is a comannd-line tool for using ChatGPT via OpenAI API.'
|
13
|
-
|
14
|
-
spec.homepage = 'https://github.com/yocajii/sawara'
|
15
|
-
spec.license = 'MIT'
|
16
|
-
spec.required_ruby_version = '>= 3.1.0'
|
17
|
-
|
18
|
-
spec.metadata['homepage_uri'] = spec.homepage
|
19
|
-
spec.metadata['source_code_uri'] = 'https://github.com/yocajii/sawara'
|
20
|
-
spec.metadata['changelog_uri'] = 'https://github.com/yocajii/sawara/blob/main/CHANGELOG.md'
|
21
|
-
|
22
|
-
# Specify which files should be added to the gem when it is released.
|
23
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
-
spec.files = Dir.chdir(__dir__) do
|
25
|
-
`git ls-files -z`.split("\x0").reject do |f|
|
26
|
-
(File.expand_path(f) == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|circleci)|appveyor)})
|
27
|
-
end
|
28
|
-
end
|
29
|
-
spec.bindir = 'exe'
|
30
|
-
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
31
|
-
spec.require_paths = ['lib']
|
32
|
-
spec.metadata['rubygems_mfa_required'] = 'true'
|
33
|
-
|
34
|
-
# Uncomment to register a new dependency of your gem
|
35
|
-
# spec.add_dependency 'example-gem', '~> 1.0'
|
36
|
-
|
37
|
-
# For more information and examples about making a new gem, check out our
|
38
|
-
# guide at: https://bundler.io/guides/creating_gem.html
|
39
|
-
end
|