slidict 0.2.0 → 0.3.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 +4 -4
- data/README.md +23 -6
- data/lib/slidict/cli/app.rb +316 -0
- data/lib/slidict/cli/serve.rb +113 -0
- data/lib/slidict/cli/slides.rb +271 -0
- data/lib/slidict/external/slidict_io/auth.rb +80 -0
- data/lib/slidict/external/slidict_io/client.rb +126 -0
- data/lib/slidict/external/slidict_io/credentials.rb +43 -0
- data/lib/slidict/llm/client.rb +146 -0
- data/lib/slidict/output/format.rb +44 -0
- data/lib/slidict/output/renderer.rb +36 -0
- data/lib/slidict/version.rb +1 -1
- data/lib/slidict.rb +9 -7
- metadata +53 -9
- data/lib/slidict/auth_client.rb +0 -75
- data/lib/slidict/cli.rb +0 -266
- data/lib/slidict/credentials.rb +0 -39
- data/lib/slidict/llm_client.rb +0 -144
- data/lib/slidict/markdown_renderer.rb +0 -39
- data/lib/slidict/slides_client.rb +0 -120
- data/lib/slidict/slides_command.rb +0 -253
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Slidict
|
|
4
|
+
module Output
|
|
5
|
+
# Single source of truth for everything that varies per output framework:
|
|
6
|
+
# the output file extension, the body_format sent to the slidict.io API,
|
|
7
|
+
# and (for markdown-based frameworks) the frontmatter block to render.
|
|
8
|
+
# Add a new framework by adding one entry to REGISTRY.
|
|
9
|
+
class Format
|
|
10
|
+
Definition = Struct.new(:name, :extension, :body_format, :frontmatter, keyword_init: true)
|
|
11
|
+
|
|
12
|
+
DEFAULT_NAME = "slidev"
|
|
13
|
+
|
|
14
|
+
REGISTRY = {
|
|
15
|
+
"slidev" => Definition.new(
|
|
16
|
+
name: "slidev",
|
|
17
|
+
extension: ".md",
|
|
18
|
+
body_format: "markdown",
|
|
19
|
+
frontmatter: "theme: default\nclass: text-center"
|
|
20
|
+
),
|
|
21
|
+
"marp" => Definition.new(
|
|
22
|
+
name: "marp",
|
|
23
|
+
extension: ".md",
|
|
24
|
+
body_format: "markdown",
|
|
25
|
+
frontmatter: "marp: true\ntheme: default"
|
|
26
|
+
),
|
|
27
|
+
"asciidoctor-revealjs" => Definition.new(
|
|
28
|
+
name: "asciidoctor-revealjs",
|
|
29
|
+
extension: ".adoc",
|
|
30
|
+
body_format: "asciidoc",
|
|
31
|
+
frontmatter: nil
|
|
32
|
+
)
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
def self.fetch(name)
|
|
36
|
+
REGISTRY.fetch(name.to_s.downcase, REGISTRY.fetch(DEFAULT_NAME))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.names
|
|
40
|
+
REGISTRY.keys
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Slidict
|
|
4
|
+
module Output
|
|
5
|
+
class Renderer
|
|
6
|
+
def render(deck)
|
|
7
|
+
return render_asciidoctor_revealjs(deck) if Format.fetch(deck.framework).body_format == "asciidoc"
|
|
8
|
+
|
|
9
|
+
[frontmatter(deck.framework), deck.slides.map { |slide| render_slide(slide) }.join("\n---\n\n")].join("\n")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def frontmatter(framework)
|
|
15
|
+
body = Format.fetch(framework).frontmatter
|
|
16
|
+
"---\n#{body}\ngenerated: #{Time.now.utc.iso8601}\n---\n"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def render_slide(slide)
|
|
20
|
+
lines = ["# #{slide.title}", ""]
|
|
21
|
+
lines.concat(slide.bullets.map { |bullet| "- #{bullet}" })
|
|
22
|
+
lines.join("\n")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def render_asciidoctor_revealjs(deck)
|
|
26
|
+
lines = ["= #{deck.topic}", ":revealjs_theme: white", ":slidict_generated: #{Time.now.utc.iso8601}", ""]
|
|
27
|
+
lines.concat(deck.slides.flat_map { |slide| render_asciidoctor_slide(slide) })
|
|
28
|
+
lines.join("\n")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def render_asciidoctor_slide(slide)
|
|
32
|
+
["== #{slide.title}", "", *slide.bullets.map { |bullet| "* #{bullet}" }, ""]
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/slidict/version.rb
CHANGED
data/lib/slidict.rb
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
require "time"
|
|
4
4
|
|
|
5
|
-
require_relative "slidict/
|
|
6
|
-
require_relative "slidict/cli"
|
|
5
|
+
require_relative "slidict/cli/app"
|
|
6
|
+
require_relative "slidict/cli/serve"
|
|
7
|
+
require_relative "slidict/cli/slides"
|
|
7
8
|
require_relative "slidict/config"
|
|
8
|
-
require_relative "slidict/credentials"
|
|
9
9
|
require_relative "slidict/deck"
|
|
10
|
-
require_relative "slidict/
|
|
11
|
-
require_relative "slidict/
|
|
12
|
-
require_relative "slidict/
|
|
13
|
-
require_relative "slidict/
|
|
10
|
+
require_relative "slidict/external/slidict_io/auth"
|
|
11
|
+
require_relative "slidict/external/slidict_io/client"
|
|
12
|
+
require_relative "slidict/external/slidict_io/credentials"
|
|
13
|
+
require_relative "slidict/llm/client"
|
|
14
|
+
require_relative "slidict/output/format"
|
|
15
|
+
require_relative "slidict/output/renderer"
|
|
14
16
|
require_relative "slidict/version"
|
|
15
17
|
|
|
16
18
|
module Slidict
|
metadata
CHANGED
|
@@ -1,14 +1,56 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: slidict
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yusuke Abe
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: puma
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '6.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '6.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rackup
|
|
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: sinatra
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '4.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '4.0'
|
|
12
54
|
description: Slidict is a Ruby CLI for turning rough ideas into presentation-ready
|
|
13
55
|
Markdown slides.
|
|
14
56
|
email:
|
|
@@ -31,15 +73,17 @@ files:
|
|
|
31
73
|
- README.md
|
|
32
74
|
- Rakefile
|
|
33
75
|
- lib/slidict.rb
|
|
34
|
-
- lib/slidict/
|
|
35
|
-
- lib/slidict/cli.rb
|
|
76
|
+
- lib/slidict/cli/app.rb
|
|
77
|
+
- lib/slidict/cli/serve.rb
|
|
78
|
+
- lib/slidict/cli/slides.rb
|
|
36
79
|
- lib/slidict/config.rb
|
|
37
|
-
- lib/slidict/credentials.rb
|
|
38
80
|
- lib/slidict/deck.rb
|
|
39
|
-
- lib/slidict/
|
|
40
|
-
- lib/slidict/
|
|
41
|
-
- lib/slidict/
|
|
42
|
-
- lib/slidict/
|
|
81
|
+
- lib/slidict/external/slidict_io/auth.rb
|
|
82
|
+
- lib/slidict/external/slidict_io/client.rb
|
|
83
|
+
- lib/slidict/external/slidict_io/credentials.rb
|
|
84
|
+
- lib/slidict/llm/client.rb
|
|
85
|
+
- lib/slidict/output/format.rb
|
|
86
|
+
- lib/slidict/output/renderer.rb
|
|
43
87
|
- lib/slidict/version.rb
|
|
44
88
|
homepage: https://labs.slidict.io/slidict/
|
|
45
89
|
licenses:
|
data/lib/slidict/auth_client.rb
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "json"
|
|
4
|
-
require "net/http"
|
|
5
|
-
require "uri"
|
|
6
|
-
|
|
7
|
-
module Slidict
|
|
8
|
-
class AuthClient
|
|
9
|
-
Error = Class.new(StandardError)
|
|
10
|
-
Pending = Class.new(StandardError)
|
|
11
|
-
|
|
12
|
-
DEFAULT_BASE_URL = "https://slidict.io"
|
|
13
|
-
|
|
14
|
-
attr_reader :base_url
|
|
15
|
-
|
|
16
|
-
def initialize(base_url: ENV.fetch("SLIDICT_AUTH_BASE_URL", DEFAULT_BASE_URL))
|
|
17
|
-
@base_url = base_url
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def request_device_code
|
|
21
|
-
response = post_json("/api/cli/device/code", { provider: "github" })
|
|
22
|
-
{
|
|
23
|
-
device_code: fetch!(response, "device_code"),
|
|
24
|
-
user_code: fetch!(response, "user_code"),
|
|
25
|
-
verification_uri: response["verification_uri"] || "#{base_url}/cli/activate",
|
|
26
|
-
interval: response.fetch("interval", 5).to_i,
|
|
27
|
-
expires_in: response.fetch("expires_in", 600).to_i
|
|
28
|
-
}
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def poll_token(device_code:)
|
|
32
|
-
response = post_json(
|
|
33
|
-
"/api/cli/device/token",
|
|
34
|
-
{ device_code: device_code, grant_type: "urn:ietf:params:oauth:grant-type:device_code" },
|
|
35
|
-
raise_on_http_error: false
|
|
36
|
-
)
|
|
37
|
-
return response if response["access_token"]
|
|
38
|
-
|
|
39
|
-
error = response["error"].to_s
|
|
40
|
-
raise Pending if %w[authorization_pending slow_down].include?(error)
|
|
41
|
-
|
|
42
|
-
raise Error, response["error_description"] || error unless error.empty?
|
|
43
|
-
|
|
44
|
-
raise Error, "token response did not include access_token"
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
def post_json(path, payload, raise_on_http_error: true)
|
|
50
|
-
uri = URI.join(base_url, path)
|
|
51
|
-
request = Net::HTTP::Post.new(uri)
|
|
52
|
-
request["Accept"] = "application/json"
|
|
53
|
-
request["Content-Type"] = "application/json"
|
|
54
|
-
request.body = JSON.generate(payload)
|
|
55
|
-
|
|
56
|
-
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") do |http|
|
|
57
|
-
http.request(request)
|
|
58
|
-
end
|
|
59
|
-
body = response.body.to_s.empty? ? {} : JSON.parse(response.body)
|
|
60
|
-
return body if response.is_a?(Net::HTTPSuccess) || !raise_on_http_error
|
|
61
|
-
|
|
62
|
-
raise Error, body["error_description"] || body["error"] || "HTTP #{response.code}"
|
|
63
|
-
rescue JSON::ParserError => e
|
|
64
|
-
raise Error, "invalid JSON response: #{e.message}"
|
|
65
|
-
rescue SystemCallError, Timeout::Error => e
|
|
66
|
-
raise Error, e.message
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def fetch!(hash, key)
|
|
70
|
-
hash.fetch(key)
|
|
71
|
-
rescue KeyError
|
|
72
|
-
raise Error, "device code response did not include #{key}"
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
data/lib/slidict/cli.rb
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Slidict
|
|
4
|
-
class CLI
|
|
5
|
-
DEFAULT_OUTPUT = "slides.md"
|
|
6
|
-
DEFAULT_OUTPUT_BY_FRAMEWORK = {
|
|
7
|
-
"slidev" => "slides.md",
|
|
8
|
-
"marp" => "slides.md",
|
|
9
|
-
"asciidoctor-revealjs" => "slides.adoc"
|
|
10
|
-
}.freeze
|
|
11
|
-
|
|
12
|
-
def initialize(input: $stdin, output: $stdout, renderer: MarkdownRenderer.new, auth_client: nil,
|
|
13
|
-
credentials: nil, sleeper: Kernel, slides_command: nil)
|
|
14
|
-
@input = input
|
|
15
|
-
@output = output
|
|
16
|
-
@renderer = renderer
|
|
17
|
-
@auth_client = auth_client
|
|
18
|
-
@credentials = credentials
|
|
19
|
-
@sleeper = sleeper
|
|
20
|
-
@slides_command = slides_command
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def run(argv = [])
|
|
24
|
-
options = parse(argv)
|
|
25
|
-
return print_help if options[:help]
|
|
26
|
-
return auth if options[:command] == "auth"
|
|
27
|
-
return slides(options[:args]) if options[:command] == "slides"
|
|
28
|
-
|
|
29
|
-
config = build_config(options)
|
|
30
|
-
client = llm_client_for(config)
|
|
31
|
-
return 1 if client && !verify_connection(client)
|
|
32
|
-
|
|
33
|
-
deck = Deck.new(
|
|
34
|
-
topic: ask("What would you like to talk about?", options[:topic]),
|
|
35
|
-
duration: ask("How long is the presentation?", options[:duration]),
|
|
36
|
-
audience: ask("Who is the audience?", options[:audience]),
|
|
37
|
-
goal: ask("What should the audience remember or do?", options[:goal]),
|
|
38
|
-
framework: options[:framework]
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
if client
|
|
42
|
-
begin
|
|
43
|
-
slides = client.generate_slides(deck)
|
|
44
|
-
rescue LLMClient::Error => e
|
|
45
|
-
@output.puts "Error: LLM request failed (#{e.message})"
|
|
46
|
-
return 1
|
|
47
|
-
end
|
|
48
|
-
deck = Deck.new(
|
|
49
|
-
topic: deck.topic, duration: deck.duration, audience: deck.audience, goal: deck.goal,
|
|
50
|
-
framework: deck.framework, slides: slides
|
|
51
|
-
)
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
path = options[:output]
|
|
55
|
-
content = @renderer.render(deck)
|
|
56
|
-
File.write(path, content)
|
|
57
|
-
@output.puts "Created #{path}"
|
|
58
|
-
|
|
59
|
-
return publish_to_slidict(deck, content, options) if options[:publish] || options[:slide_id]
|
|
60
|
-
|
|
61
|
-
0
|
|
62
|
-
rescue ArgumentError => e
|
|
63
|
-
@output.puts "Error: #{e.message}"
|
|
64
|
-
@output.puts
|
|
65
|
-
print_help
|
|
66
|
-
1
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
private
|
|
70
|
-
|
|
71
|
-
def parse(argv)
|
|
72
|
-
options = { framework: "slidev" }
|
|
73
|
-
args = argv.dup
|
|
74
|
-
|
|
75
|
-
if args.first == "auth"
|
|
76
|
-
args.shift
|
|
77
|
-
raise ArgumentError, "auth does not accept options" unless args.empty?
|
|
78
|
-
|
|
79
|
-
options[:command] = "auth"
|
|
80
|
-
return options
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
if args.first == "slides"
|
|
84
|
-
args.shift
|
|
85
|
-
options[:command] = "slides"
|
|
86
|
-
options[:args] = args
|
|
87
|
-
return options
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
until args.empty?
|
|
91
|
-
case (arg = args.shift)
|
|
92
|
-
when "-h", "--help"
|
|
93
|
-
options[:help] = true
|
|
94
|
-
when "-o", "--output"
|
|
95
|
-
options[:output] = fetch_value!(args, arg)
|
|
96
|
-
when "--topic"
|
|
97
|
-
options[:topic] = fetch_value!(args, arg)
|
|
98
|
-
when "--duration"
|
|
99
|
-
options[:duration] = fetch_value!(args, arg)
|
|
100
|
-
when "--audience"
|
|
101
|
-
options[:audience] = fetch_value!(args, arg)
|
|
102
|
-
when "--goal"
|
|
103
|
-
options[:goal] = fetch_value!(args, arg)
|
|
104
|
-
when "--framework"
|
|
105
|
-
options[:framework] = fetch_value!(args, arg)
|
|
106
|
-
when "--llm-base-url"
|
|
107
|
-
options[:llm_base_url] = fetch_value!(args, arg)
|
|
108
|
-
when "--llm-api-key"
|
|
109
|
-
options[:llm_api_key] = fetch_value!(args, arg)
|
|
110
|
-
when "--llm-model"
|
|
111
|
-
options[:llm_model] = fetch_value!(args, arg)
|
|
112
|
-
when "--no-llm"
|
|
113
|
-
options[:no_llm] = true
|
|
114
|
-
when "--publish"
|
|
115
|
-
options[:publish] = true
|
|
116
|
-
when "--slide-id"
|
|
117
|
-
options[:slide_id] = fetch_value!(args, arg)
|
|
118
|
-
when "--slide-title"
|
|
119
|
-
options[:slide_title] = fetch_value!(args, arg)
|
|
120
|
-
when "--visibility"
|
|
121
|
-
options[:visibility] = fetch_value!(args, arg)
|
|
122
|
-
else
|
|
123
|
-
raise ArgumentError, "unknown option #{arg}"
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
options[:output] ||= default_output_for(options[:framework])
|
|
128
|
-
options
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def build_config(options)
|
|
132
|
-
Config.from_env.merge(
|
|
133
|
-
base_url: options[:llm_base_url],
|
|
134
|
-
api_key: options[:llm_api_key],
|
|
135
|
-
model: options[:llm_model],
|
|
136
|
-
enabled: options[:no_llm] ? false : nil
|
|
137
|
-
)
|
|
138
|
-
end
|
|
139
|
-
|
|
140
|
-
def llm_client_for(config)
|
|
141
|
-
return nil unless config.llm_enabled?
|
|
142
|
-
|
|
143
|
-
LLMClient.new(base_url: config.base_url, api_key: config.api_key, model: config.model)
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def verify_connection(client)
|
|
147
|
-
client.verify_connection!
|
|
148
|
-
true
|
|
149
|
-
rescue LLMClient::Error => e
|
|
150
|
-
@output.puts "Error: LLM request failed (#{e.message})"
|
|
151
|
-
false
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def auth
|
|
155
|
-
client = @auth_client || AuthClient.new
|
|
156
|
-
credentials = @credentials || Credentials.new
|
|
157
|
-
|
|
158
|
-
device = client.request_device_code
|
|
159
|
-
@output.puts "1. Open #{device[:verification_uri]} in your browser"
|
|
160
|
-
@output.puts "2. Enter code: #{device[:user_code]}"
|
|
161
|
-
@output.puts "3. Log in with GitHub"
|
|
162
|
-
@output.puts "Waiting for GitHub authentication..."
|
|
163
|
-
|
|
164
|
-
deadline = Time.now + device[:expires_in]
|
|
165
|
-
loop do
|
|
166
|
-
token = client.poll_token(device_code: device[:device_code])
|
|
167
|
-
path = credentials.write_cli_token!(
|
|
168
|
-
access_token: token.fetch("access_token"),
|
|
169
|
-
token_type: token.fetch("token_type", "Bearer"),
|
|
170
|
-
provider: token.fetch("provider", "github")
|
|
171
|
-
)
|
|
172
|
-
@output.puts "4. Saved CLI access token to #{path}"
|
|
173
|
-
return 0
|
|
174
|
-
rescue AuthClient::Pending
|
|
175
|
-
return login_expired if Time.now >= deadline
|
|
176
|
-
|
|
177
|
-
@sleeper.sleep(device[:interval])
|
|
178
|
-
end
|
|
179
|
-
rescue AuthClient::Error, KeyError => e
|
|
180
|
-
@output.puts "Error: GitHub auth failed (#{e.message})"
|
|
181
|
-
1
|
|
182
|
-
end
|
|
183
|
-
|
|
184
|
-
def slides(args)
|
|
185
|
-
slides_command.run(args)
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def publish_to_slidict(deck, content, options)
|
|
189
|
-
slides_command.publish(
|
|
190
|
-
id: options[:slide_id],
|
|
191
|
-
title: options[:slide_title] || deck.topic,
|
|
192
|
-
body: content,
|
|
193
|
-
body_format: body_format_for(deck.framework),
|
|
194
|
-
visibility: options[:visibility]
|
|
195
|
-
)
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def slides_command
|
|
199
|
-
@slides_command ||= SlidesCommand.new(output: @output, credentials: @credentials, reauthenticate: method(:auth))
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
def body_format_for(framework)
|
|
203
|
-
framework.to_s.downcase == "asciidoctor-revealjs" ? "asciidoc" : "markdown"
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def login_expired
|
|
207
|
-
@output.puts "Error: GitHub auth timed out. Run `slidict auth` and try again."
|
|
208
|
-
1
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
def fetch_value!(args, option)
|
|
212
|
-
value = args.shift
|
|
213
|
-
raise ArgumentError, "#{option} requires a value" if value.nil? || value.start_with?("-")
|
|
214
|
-
|
|
215
|
-
value
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
def ask(question, provided)
|
|
219
|
-
return provided unless provided.nil? || provided.strip.empty?
|
|
220
|
-
|
|
221
|
-
@output.puts question
|
|
222
|
-
@output.print "> "
|
|
223
|
-
@input.gets&.chomp.to_s
|
|
224
|
-
end
|
|
225
|
-
|
|
226
|
-
def print_help
|
|
227
|
-
@output.puts <<~HELP
|
|
228
|
-
Usage: slidict [options]
|
|
229
|
-
Usage: slidict auth
|
|
230
|
-
Usage: slidict slides <list|show|create|edit> [options]
|
|
231
|
-
|
|
232
|
-
Generate presentation source files from a short conversation.
|
|
233
|
-
|
|
234
|
-
Commands:
|
|
235
|
-
auth Authenticate the CLI with GitHub and save a CLI access token
|
|
236
|
-
slides Manage your slides on slidict.io (run `slidict slides -h` for details)
|
|
237
|
-
|
|
238
|
-
Options:
|
|
239
|
-
--topic TEXT Presentation topic
|
|
240
|
-
--duration TEXT Presentation length, for example "5 minutes"
|
|
241
|
-
--audience TEXT Target audience
|
|
242
|
-
--goal TEXT Desired audience takeaway or action
|
|
243
|
-
--framework NAME slidev, marp, or asciidoctor-revealjs (default: slidev)
|
|
244
|
-
--llm-base-url URL OpenAI Compatible API base URL (env: SLIDICT_LLM_BASE_URL).
|
|
245
|
-
When omitted, the built-in slide template is used instead.
|
|
246
|
-
--llm-api-key KEY API key for the LLM endpoint (env: SLIDICT_LLM_API_KEY)
|
|
247
|
-
--llm-model NAME Model name to request (env: SLIDICT_LLM_MODEL, default: gpt-4o-mini)
|
|
248
|
-
--no-llm Skip the LLM call and use the built-in slide template
|
|
249
|
-
--publish Publish the generated slides to slidict.io as a draft
|
|
250
|
-
(requires `slidict auth`; creates a new slide, or edits
|
|
251
|
-
an existing one when --slide-id is given)
|
|
252
|
-
--slide-id ID Edit this existing draft instead of creating a new one
|
|
253
|
-
(implies --publish)
|
|
254
|
-
--slide-title TEXT Title for the published slide (default: --topic)
|
|
255
|
-
--visibility VIS public, unlisted, or group_only (default: public)
|
|
256
|
-
-o, --output PATH Output file (default depends on --framework)
|
|
257
|
-
-h, --help Show this help
|
|
258
|
-
HELP
|
|
259
|
-
0
|
|
260
|
-
end
|
|
261
|
-
|
|
262
|
-
def default_output_for(framework)
|
|
263
|
-
DEFAULT_OUTPUT_BY_FRAMEWORK.fetch(framework.to_s.downcase, DEFAULT_OUTPUT)
|
|
264
|
-
end
|
|
265
|
-
end
|
|
266
|
-
end
|
data/lib/slidict/credentials.rb
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "fileutils"
|
|
4
|
-
require "json"
|
|
5
|
-
|
|
6
|
-
module Slidict
|
|
7
|
-
class Credentials
|
|
8
|
-
DEFAULT_PATH = File.expand_path("~/.config/slidict/credentials.json")
|
|
9
|
-
|
|
10
|
-
def initialize(path: DEFAULT_PATH)
|
|
11
|
-
@path = path
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def write_cli_token!(access_token:, token_type: "Bearer", provider: "github")
|
|
15
|
-
FileUtils.mkdir_p(File.dirname(@path), mode: 0o700)
|
|
16
|
-
json = JSON.pretty_generate(
|
|
17
|
-
"access_token" => access_token,
|
|
18
|
-
"token_type" => token_type,
|
|
19
|
-
"provider" => provider,
|
|
20
|
-
"kind" => "cli_access_token"
|
|
21
|
-
)
|
|
22
|
-
File.write(@path, "#{json}\n", mode: "w", perm: 0o600)
|
|
23
|
-
File.chmod(0o600, @path)
|
|
24
|
-
@path
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Returns { access_token:, token_type: } or nil if no CLI access token is saved.
|
|
28
|
-
def read_cli_token
|
|
29
|
-
return nil unless File.exist?(@path)
|
|
30
|
-
|
|
31
|
-
data = JSON.parse(File.read(@path))
|
|
32
|
-
return nil unless data["kind"] == "cli_access_token" && data["access_token"]
|
|
33
|
-
|
|
34
|
-
{ access_token: data["access_token"], token_type: data.fetch("token_type", "Bearer") }
|
|
35
|
-
rescue JSON::ParserError
|
|
36
|
-
nil
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|