ratproto 0.0.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/CHANGELOG.md +1 -0
- data/LICENSE.txt +21 -0
- data/README.md +35 -0
- data/exe/rat +403 -0
- data/lib/ratproto/version.rb +5 -0
- data/lib/ratproto.rb +6 -0
- data/sig/ratproto.rbs +4 -0
- metadata +110 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: c1495c9728dd1630d8f9b62e14187dd7066c16afbbc31161deac5697ee67f5d0
|
|
4
|
+
data.tar.gz: dbfa95dd48889d5c1a5fd06489ea316aeb2e4ec475cf54fe922bd3f31ca28aa8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6119eda973e4279d58dfad1bc1f35aabd42de1dcbc892808b649593fb09a0a063cfdfec239e4af6c83ec203833c108201a36b4d08fbe2631b0e2c1bbf0ce4b49
|
|
7
|
+
data.tar.gz: e7feeeb64a73705722cf5f4fc457617349c846b310815f6d2bddbb985a171c1c9e1017fb3070a949c7e994eb4cc33f1857b189529e218aeae6303557cc954dfe
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
## [Unreleased]
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The zlib License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jakub Suder
|
|
4
|
+
|
|
5
|
+
This software is provided 'as-is', without any express or implied
|
|
6
|
+
warranty. In no event will the authors be held liable for any damages
|
|
7
|
+
arising from the use of this software.
|
|
8
|
+
|
|
9
|
+
Permission is granted to anyone to use this software for any purpose,
|
|
10
|
+
including commercial applications, and to alter it and redistribute it
|
|
11
|
+
freely, subject to the following restrictions:
|
|
12
|
+
|
|
13
|
+
1. The origin of this software must not be misrepresented; you must not
|
|
14
|
+
claim that you wrote the original software. If you use this software
|
|
15
|
+
in a product, an acknowledgment in the product documentation would be
|
|
16
|
+
appreciated but is not required.
|
|
17
|
+
|
|
18
|
+
2. Altered source versions must be plainly marked as such, and must not be
|
|
19
|
+
misrepresented as being the original software.
|
|
20
|
+
|
|
21
|
+
3. This notice may not be removed or altered from any source distribution.
|
data/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Ratproto
|
|
2
|
+
|
|
3
|
+
TODO: Delete this and the text below, and describe your gem
|
|
4
|
+
|
|
5
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/ratproto`. To experiment with that code, run `bin/console` for an interactive prompt.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
|
10
|
+
|
|
11
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
TODO: Write usage instructions here
|
|
26
|
+
|
|
27
|
+
## Development
|
|
28
|
+
|
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
30
|
+
|
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
32
|
+
|
|
33
|
+
## Contributing
|
|
34
|
+
|
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ratproto.
|
data/exe/rat
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# rat – Ruby ATProto Tool
|
|
5
|
+
#
|
|
6
|
+
# Simple CLI for Bluesky AT Protocol using:
|
|
7
|
+
# - Minisky (XRPC client)
|
|
8
|
+
# - Skyfall (firehose / Jetstream streaming)
|
|
9
|
+
# - DIDKit (DID / handle resolution)
|
|
10
|
+
#
|
|
11
|
+
# Usage:
|
|
12
|
+
# rat fetch at://<did-or-handle>/<collection-nsid>/<rkey>
|
|
13
|
+
# rat stream <relay.host> [-j|--jetstream] [-r|--cursor CURSOR] \
|
|
14
|
+
# [-d|--did DID,...] [-c|--collection NSID,...]
|
|
15
|
+
# rat resolve <did-or-handle>
|
|
16
|
+
# rat help | --help
|
|
17
|
+
# rat version | --version
|
|
18
|
+
|
|
19
|
+
require 'optparse'
|
|
20
|
+
require 'json'
|
|
21
|
+
require 'time'
|
|
22
|
+
require 'uri'
|
|
23
|
+
|
|
24
|
+
require 'minisky'
|
|
25
|
+
require 'skyfall'
|
|
26
|
+
require 'didkit' # defines DIDKit::DID and top-level DID alias
|
|
27
|
+
|
|
28
|
+
VERSION = '0.0.1'
|
|
29
|
+
|
|
30
|
+
DID_REGEX = /\Adid:[a-z0-9]+:[a-zA-Z0-9.\-_:]+\z/
|
|
31
|
+
DOMAIN_REGEX = /\A[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+\z/i
|
|
32
|
+
HOST_PORT_REGEX = /\A#{DOMAIN_REGEX.source}(?::\d+)?\z/i
|
|
33
|
+
|
|
34
|
+
def global_help
|
|
35
|
+
<<~HELP
|
|
36
|
+
rat #{VERSION}
|
|
37
|
+
|
|
38
|
+
Usage:
|
|
39
|
+
rat fetch at://<did-or-handle>/<collection-nsid>/<rkey>
|
|
40
|
+
rat stream <relay.host> [options]
|
|
41
|
+
rat resolve <did-or-handle>
|
|
42
|
+
rat help | --help
|
|
43
|
+
rat version | --version
|
|
44
|
+
|
|
45
|
+
Commands:
|
|
46
|
+
fetch Fetch a single record given its at:// URI
|
|
47
|
+
stream Stream commit events from a relay / PDS firehose
|
|
48
|
+
resolve Resolve a DID or @handle using DIDKit
|
|
49
|
+
help Show this help
|
|
50
|
+
version Show version
|
|
51
|
+
|
|
52
|
+
Stream options:
|
|
53
|
+
-j, --jetstream Use Skyfall::Jetstream (JSON)
|
|
54
|
+
(uses Jetstream wanted_dids / wanted_collections
|
|
55
|
+
when -d/-c are provided)
|
|
56
|
+
-r, --cursor CURSOR Start from cursor (seq or time_us)
|
|
57
|
+
-d, --did DID[,DID...] Filter only events from given DID(s)
|
|
58
|
+
(can be passed multiple times)
|
|
59
|
+
-c, --collection NSID[,NSID...] Filter only events of given collection(s)
|
|
60
|
+
(can be passed multiple times)
|
|
61
|
+
HELP
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def abort_with_help(message = nil)
|
|
65
|
+
warn "Error: #{message}" if message
|
|
66
|
+
warn
|
|
67
|
+
warn global_help
|
|
68
|
+
exit 1
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def valid_did?(s)
|
|
72
|
+
DID_REGEX.match?(s)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def valid_handle?(s)
|
|
76
|
+
DOMAIN_REGEX.match?(s)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def valid_nsid?(s)
|
|
80
|
+
# NSIDs look like domain names (reverse DNS), so reuse DOMAIN_REGEX
|
|
81
|
+
DOMAIN_REGEX.match?(s)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def validate_handle!(s, context: 'handle')
|
|
85
|
+
unless valid_handle?(s)
|
|
86
|
+
abort_with_help("#{s.inspect} doesn't look like a valid #{context}")
|
|
87
|
+
end
|
|
88
|
+
s
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def validate_did!(s)
|
|
92
|
+
unless valid_did?(s)
|
|
93
|
+
abort_with_help("#{s.inspect} doesn't look like a valid DID")
|
|
94
|
+
end
|
|
95
|
+
s
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def validate_nsid!(nsid)
|
|
99
|
+
unless valid_nsid?(nsid)
|
|
100
|
+
abort_with_help("#{nsid.inspect} doesn't look like a valid collection NSID")
|
|
101
|
+
end
|
|
102
|
+
nsid
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def parse_at_uri(str)
|
|
106
|
+
unless str.start_with?('at://')
|
|
107
|
+
abort_with_help("not an at:// URI: #{str.inspect}")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
m = /\Aat:\/\/([^\/]+)\/([^\/]+)\/([^\/]+)\z/.match(str)
|
|
111
|
+
unless m
|
|
112
|
+
abort_with_help("invalid at:// URI: expected at://<repo>/<collection>/<rkey>")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
repo, collection, rkey = m.captures
|
|
116
|
+
[repo, collection, rkey]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def resolve_repo_to_did_and_pds(repo_str)
|
|
120
|
+
if repo_str.start_with?('did:')
|
|
121
|
+
validate_did!(repo_str)
|
|
122
|
+
did_obj = DID.new(repo_str)
|
|
123
|
+
else
|
|
124
|
+
handle = repo_str.sub(/\A@/, '')
|
|
125
|
+
validate_handle!(handle, context: 'handle in at:// URI')
|
|
126
|
+
did_obj = DID.resolve_handle(handle)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
doc = did_obj.document
|
|
130
|
+
pds_host = doc.pds_host
|
|
131
|
+
unless pds_host && !pds_host.empty?
|
|
132
|
+
abort_with_help("DID document for #{did_obj} does not contain a pds_host")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
[did_obj.to_s, pds_host]
|
|
136
|
+
rescue StandardError => e
|
|
137
|
+
warn "Error resolving repo #{repo_str.inspect}: #{e.class}: #{e.message}"
|
|
138
|
+
exit 1
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def do_fetch(argv)
|
|
142
|
+
uri = argv.shift or abort_with_help("fetch requires an at:// URI")
|
|
143
|
+
unless argv.empty?
|
|
144
|
+
abort_with_help("unexpected extra arguments for fetch: #{argv.join(' ')}")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
repo_str, collection, rkey = parse_at_uri(uri)
|
|
148
|
+
|
|
149
|
+
# basic validation of collection (NSID-ish)
|
|
150
|
+
validate_nsid!(collection)
|
|
151
|
+
|
|
152
|
+
did, pds_host = resolve_repo_to_did_and_pds(repo_str)
|
|
153
|
+
|
|
154
|
+
client = Minisky.new(pds_host, nil)
|
|
155
|
+
params = { repo: did, collection: collection, rkey: rkey }
|
|
156
|
+
|
|
157
|
+
begin
|
|
158
|
+
res = client.get_request('com.atproto.repo.getRecord', params)
|
|
159
|
+
rescue StandardError => e
|
|
160
|
+
warn "Error calling com.atproto.repo.getRecord: #{e.class}: #{e.message}"
|
|
161
|
+
exit 1
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
value = res['value']
|
|
165
|
+
if value.nil?
|
|
166
|
+
warn "Warning: response did not contain a 'value' field"
|
|
167
|
+
puts JSON.pretty_generate(res)
|
|
168
|
+
else
|
|
169
|
+
puts JSON.pretty_generate(value)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def do_resolve(argv)
|
|
174
|
+
target = argv.shift or abort_with_help("resolve requires a DID or handle")
|
|
175
|
+
unless argv.empty?
|
|
176
|
+
abort_with_help("unexpected extra arguments for resolve: #{argv.join(' ')}")
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
if target.start_with?('did:')
|
|
180
|
+
validate_did!(target)
|
|
181
|
+
begin
|
|
182
|
+
did_obj = DID.new(target)
|
|
183
|
+
doc = did_obj.document
|
|
184
|
+
json = doc.respond_to?(:json) ? doc.json : doc
|
|
185
|
+
puts did_obj.to_s
|
|
186
|
+
puts JSON.pretty_generate(json)
|
|
187
|
+
rescue StandardError => e
|
|
188
|
+
warn "Error resolving DID #{target.inspect}: #{e.class}: #{e.message}"
|
|
189
|
+
exit 1
|
|
190
|
+
end
|
|
191
|
+
else
|
|
192
|
+
handle = target.sub(/\A@/, '')
|
|
193
|
+
validate_handle!(handle)
|
|
194
|
+
begin
|
|
195
|
+
did_obj = DID.resolve_handle(handle)
|
|
196
|
+
doc = did_obj.document
|
|
197
|
+
json = doc.respond_to?(:json) ? doc.json : doc
|
|
198
|
+
puts did_obj.to_s
|
|
199
|
+
puts JSON.pretty_generate(json)
|
|
200
|
+
rescue StandardError => e
|
|
201
|
+
warn "Error resolving handle #{target.inspect}: #{e.class}: #{e.message}"
|
|
202
|
+
exit 1
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def validate_relay_host!(host)
|
|
208
|
+
# Accept:
|
|
209
|
+
# - bare hostname or hostname:port
|
|
210
|
+
# - ws://host[:port]
|
|
211
|
+
# - wss://host[:port]
|
|
212
|
+
if host =~ /\Aws:\/\//i || host =~ /\Awss:\/\//i
|
|
213
|
+
uri = URI(host)
|
|
214
|
+
if uri.path && uri.path != '' && uri.path != '/'
|
|
215
|
+
abort_with_help("relay URL must not contain a path: #{host.inspect}")
|
|
216
|
+
end
|
|
217
|
+
unless uri.host && DOMAIN_REGEX.match?(uri.host)
|
|
218
|
+
abort_with_help("invalid relay hostname in URL: #{host.inspect}")
|
|
219
|
+
end
|
|
220
|
+
else
|
|
221
|
+
unless HOST_PORT_REGEX.match?(host)
|
|
222
|
+
abort_with_help("invalid relay hostname: #{host.inspect}")
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
host
|
|
227
|
+
rescue URI::InvalidURIError
|
|
228
|
+
abort_with_help("invalid relay URL: #{host.inspect}")
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def parse_stream_options(argv)
|
|
232
|
+
options = {
|
|
233
|
+
use_jetstream: false,
|
|
234
|
+
cursor: nil,
|
|
235
|
+
dids: [],
|
|
236
|
+
collections: []
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
parser = OptionParser.new do |opts|
|
|
240
|
+
opts.banner = "Usage: rat stream <relay.host> [options]"
|
|
241
|
+
|
|
242
|
+
opts.on('-j', '--jetstream', 'Use Skyfall::Jetstream (JSON)') do
|
|
243
|
+
options[:use_jetstream] = true
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
opts.on('-rCURSOR', '--cursor=CURSOR', 'Start from cursor (seq or time_us)') do |cursor|
|
|
247
|
+
options[:cursor] = cursor
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
opts.on('-dLIST', '--did=LIST',
|
|
251
|
+
'Filter only events from DID(s) (comma-separated or repeated)') do |list|
|
|
252
|
+
items = list.split(',').map(&:strip).reject(&:empty?)
|
|
253
|
+
if items.empty?
|
|
254
|
+
abort_with_help("empty argument to -d/--did")
|
|
255
|
+
end
|
|
256
|
+
options[:dids].concat(items)
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
opts.on('-cLIST', '--collection=LIST',
|
|
260
|
+
'Filter only events of given collection NSID(s)') do |list|
|
|
261
|
+
items = list.split(',').map(&:strip).reject(&:empty?)
|
|
262
|
+
if items.empty?
|
|
263
|
+
abort_with_help("empty argument to -c/--collection")
|
|
264
|
+
end
|
|
265
|
+
options[:collections].concat(items)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
opts.on('-h', '--help', 'Show stream-specific help') do
|
|
269
|
+
puts opts
|
|
270
|
+
exit 0
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
remaining = []
|
|
275
|
+
begin
|
|
276
|
+
parser.order!(argv) { |nonopt| remaining << nonopt }
|
|
277
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument => e
|
|
278
|
+
warn "Error: #{e.message}"
|
|
279
|
+
warn parser
|
|
280
|
+
exit 1
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
[options, remaining]
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def do_stream(argv)
|
|
287
|
+
options, remaining = parse_stream_options(argv)
|
|
288
|
+
|
|
289
|
+
host = remaining.shift or abort_with_help("stream requires a relay hostname")
|
|
290
|
+
validate_relay_host!(host)
|
|
291
|
+
|
|
292
|
+
unless remaining.empty?
|
|
293
|
+
abort_with_help("unexpected extra arguments for stream: #{remaining.join(' ')}")
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
# validate cursor (if given)
|
|
297
|
+
if options[:cursor] && options[:cursor] !~ /\A\d+\z/
|
|
298
|
+
abort_with_help("cursor must be a decimal integer, got #{options[:cursor].inspect}")
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# validate DIDs
|
|
302
|
+
options[:dids].each do |did|
|
|
303
|
+
validate_did!(did)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# validate collections
|
|
307
|
+
options[:collections].each do |nsid|
|
|
308
|
+
validate_nsid!(nsid)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Build Skyfall client
|
|
312
|
+
if options[:use_jetstream]
|
|
313
|
+
jet_opts = {}
|
|
314
|
+
jet_opts[:cursor] = options[:cursor].to_i if options[:cursor]
|
|
315
|
+
|
|
316
|
+
# Pass filters through to Jetstream server-side
|
|
317
|
+
jet_opts[:wanted_dids] = options[:dids] unless options[:dids].empty?
|
|
318
|
+
jet_opts[:wanted_collections] = options[:collections] unless options[:collections].empty?
|
|
319
|
+
|
|
320
|
+
sky = Skyfall::Jetstream.new(host, jet_opts)
|
|
321
|
+
else
|
|
322
|
+
cursor = options[:cursor]&.to_i
|
|
323
|
+
sky = Skyfall::Firehose.new(host, :subscribe_repos, cursor)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Lifecycle logging
|
|
327
|
+
sky.on_connecting { |url| puts "Connecting to #{url}..." }
|
|
328
|
+
sky.on_connect { puts "Connected" }
|
|
329
|
+
sky.on_disconnect { puts "Disconnected" }
|
|
330
|
+
sky.on_reconnect { puts "Connection lost, trying to reconnect..." }
|
|
331
|
+
sky.on_timeout { puts "Connection stalled, triggering a reconnect..." } if sky.respond_to?(:on_timeout)
|
|
332
|
+
sky.on_error { |e| warn "ERROR: #{e}" }
|
|
333
|
+
|
|
334
|
+
# Message handler
|
|
335
|
+
sky.on_message do |msg|
|
|
336
|
+
next unless msg.type == :commit
|
|
337
|
+
|
|
338
|
+
did = if msg.respond_to?(:repo) && msg.repo
|
|
339
|
+
msg.repo
|
|
340
|
+
elsif msg.respond_to?(:did)
|
|
341
|
+
msg.did
|
|
342
|
+
else
|
|
343
|
+
nil
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
if !options[:dids].empty? && (!did || !options[:dids].include?(did))
|
|
347
|
+
next
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
ts = msg.time ? msg.time.getlocal.iso8601 : 'unknown-time'
|
|
351
|
+
|
|
352
|
+
msg.operations.each do |op|
|
|
353
|
+
# collection filter (still applied client-side, in case server doesn't)
|
|
354
|
+
if !options[:collections].empty? &&
|
|
355
|
+
!options[:collections].include?(op.collection)
|
|
356
|
+
next
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
line = "[#{ts}] #{did || '-'} :#{op.action} #{op.collection} #{op.rkey}"
|
|
360
|
+
|
|
361
|
+
if op.respond_to?(:raw_record) && op.raw_record && !op.raw_record.empty?
|
|
362
|
+
# compact JSON on one line
|
|
363
|
+
line << " " << JSON.generate(op.raw_record)
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
puts line
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Clean disconnect on Ctrl+C
|
|
371
|
+
trap('SIGINT') do
|
|
372
|
+
puts 'Disconnecting...'
|
|
373
|
+
sky.disconnect
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
sky.connect
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# ---- main dispatcher ----
|
|
380
|
+
|
|
381
|
+
if ARGV.empty?
|
|
382
|
+
puts global_help
|
|
383
|
+
exit 0
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
cmd = ARGV.shift
|
|
387
|
+
|
|
388
|
+
case cmd
|
|
389
|
+
when 'help', '--help', '-h'
|
|
390
|
+
puts global_help
|
|
391
|
+
exit 0
|
|
392
|
+
when 'version', '--version'
|
|
393
|
+
puts VERSION
|
|
394
|
+
exit 0
|
|
395
|
+
when 'fetch'
|
|
396
|
+
do_fetch(ARGV)
|
|
397
|
+
when 'resolve'
|
|
398
|
+
do_resolve(ARGV)
|
|
399
|
+
when 'stream'
|
|
400
|
+
do_stream(ARGV)
|
|
401
|
+
else
|
|
402
|
+
abort_with_help("unknown command: #{cmd}")
|
|
403
|
+
end
|
data/lib/ratproto.rb
ADDED
data/sig/ratproto.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ratproto
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Kuba Suder
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: minisky
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.5'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '2.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '0.5'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '2.0'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: skyfall
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '0.6'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '2.0'
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0.6'
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '2.0'
|
|
52
|
+
- !ruby/object:Gem::Dependency
|
|
53
|
+
name: didkit
|
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: 0.3.1
|
|
59
|
+
- - "<"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '2.0'
|
|
62
|
+
type: :runtime
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: 0.3.1
|
|
69
|
+
- - "<"
|
|
70
|
+
- !ruby/object:Gem::Version
|
|
71
|
+
version: '2.0'
|
|
72
|
+
email:
|
|
73
|
+
- jakub.suder@gmail.com
|
|
74
|
+
executables:
|
|
75
|
+
- rat
|
|
76
|
+
extensions: []
|
|
77
|
+
extra_rdoc_files: []
|
|
78
|
+
files:
|
|
79
|
+
- CHANGELOG.md
|
|
80
|
+
- LICENSE.txt
|
|
81
|
+
- README.md
|
|
82
|
+
- exe/rat
|
|
83
|
+
- lib/ratproto.rb
|
|
84
|
+
- lib/ratproto/version.rb
|
|
85
|
+
- sig/ratproto.rbs
|
|
86
|
+
homepage: https://ruby.sdk.blue
|
|
87
|
+
licenses:
|
|
88
|
+
- Zlib
|
|
89
|
+
metadata:
|
|
90
|
+
bug_tracker_uri: https://tangled.org/mackuba.eu/ratproto/issues
|
|
91
|
+
changelog_uri: https://tangled.org/mackuba.eu/ratproto/blob/master/CHANGELOG.md
|
|
92
|
+
source_code_uri: https://tangled.org/mackuba.eu/ratproto
|
|
93
|
+
rdoc_options: []
|
|
94
|
+
require_paths:
|
|
95
|
+
- lib
|
|
96
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - ">="
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: 2.6.0
|
|
101
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
|
+
requirements:
|
|
103
|
+
- - ">="
|
|
104
|
+
- !ruby/object:Gem::Version
|
|
105
|
+
version: '0'
|
|
106
|
+
requirements: []
|
|
107
|
+
rubygems_version: 4.0.3
|
|
108
|
+
specification_version: 4
|
|
109
|
+
summary: Ruby CLI tool for accessing Bluesky API / ATProto
|
|
110
|
+
test_files: []
|