libbeachcomber 0.5.0 → 0.6.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/lib/beachcomber/client.rb +223 -50
- data/lib/beachcomber/types.rb +32 -0
- data/lib/beachcomber/watch_stream.rb +50 -0
- data/lib/beachcomber.rb +2 -0
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8f5895c44fbc17e34a043845428bffda24b355679d6d02bfa322b81f90a732ca
|
|
4
|
+
data.tar.gz: c3b583ca4c94cf046a015c0ee914ae77113d83d670392bb9e5fd0a5e1b4d4529
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fe43031c1ea0b00bab8a280fa61bcef5de14473b88e2c036d3af9063945bde5e87f3858d37fa6a48954d567551c1568831c05655140063cda6f0c5aa1f1a39ba
|
|
7
|
+
data.tar.gz: 71baf44b81f858af49b1dc2db54f3949f5003d0b00ab4f02add2e3b193155748dec8ca1c3154d491a46e69aaa9825c47898efa493a2b2a06ba433897dbb1f328
|
data/lib/beachcomber/client.rb
CHANGED
|
@@ -44,29 +44,74 @@ module Beachcomber
|
|
|
44
44
|
roundtrip(req)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
# Reads a cached value with protocol flags.
|
|
48
|
+
#
|
|
49
|
+
# @param key [String]
|
|
50
|
+
# @param path [String, nil]
|
|
51
|
+
# @param force [Boolean] bypass cache and recompute
|
|
52
|
+
# @param wait [Boolean] block until a fresh value is available
|
|
53
|
+
# @return [Result]
|
|
54
|
+
def get_with_flags(key, path: nil, force: false, wait: false)
|
|
55
|
+
req = { op: 'get', key: key }
|
|
56
|
+
req[:path] = path if path
|
|
57
|
+
req[:force] = true if force
|
|
58
|
+
req[:wait] = true if wait
|
|
59
|
+
roundtrip(req)
|
|
60
|
+
end
|
|
61
|
+
|
|
47
62
|
# Forces the daemon to recompute a provider/key.
|
|
48
63
|
#
|
|
49
64
|
# @param key [String]
|
|
50
65
|
# @param path [String, nil]
|
|
51
|
-
def
|
|
52
|
-
req = { op: '
|
|
66
|
+
def refresh(key, path: nil)
|
|
67
|
+
req = { op: 'refresh', key: key }
|
|
53
68
|
req[:path] = path if path
|
|
54
69
|
roundtrip(req)
|
|
55
70
|
nil
|
|
56
71
|
end
|
|
57
72
|
|
|
58
|
-
#
|
|
73
|
+
# Returns cache rows from the daemon.
|
|
59
74
|
#
|
|
60
|
-
# @return [
|
|
61
|
-
def
|
|
62
|
-
|
|
75
|
+
# @return [Array<CacheRow>]
|
|
76
|
+
def status
|
|
77
|
+
resp_obj = roundtrip_raw({ op: 'status' })
|
|
78
|
+
parse_cache_rows(resp_obj)
|
|
63
79
|
end
|
|
64
80
|
|
|
65
|
-
#
|
|
81
|
+
# Sends a hello handshake and returns server info.
|
|
66
82
|
#
|
|
67
|
-
# @return [
|
|
68
|
-
def
|
|
69
|
-
|
|
83
|
+
# @return [HelloInfo]
|
|
84
|
+
def hello
|
|
85
|
+
resp = roundtrip_raw({ op: 'hello' })
|
|
86
|
+
parse_hello(resp)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Writes a value into the daemon cache.
|
|
90
|
+
#
|
|
91
|
+
# @param key [String]
|
|
92
|
+
# @param data [Object, nil]
|
|
93
|
+
# @param ttl [Numeric, nil] time-to-live in seconds
|
|
94
|
+
# @param path [String, nil]
|
|
95
|
+
# @return [nil]
|
|
96
|
+
def put(key, data = nil, ttl: nil, path: nil)
|
|
97
|
+
req = { op: 'put', key: key }
|
|
98
|
+
req[:data] = data unless data.nil?
|
|
99
|
+
req[:ttl] = ttl if ttl
|
|
100
|
+
req[:path] = path if path
|
|
101
|
+
roundtrip(req)
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Introspects a daemon subsystem.
|
|
106
|
+
#
|
|
107
|
+
# @param subject [String] one of the IntrospectSubject constants
|
|
108
|
+
# @param duration_secs [Numeric, nil]
|
|
109
|
+
# @return [IntrospectResponse]
|
|
110
|
+
def introspect(subject, duration_secs: nil)
|
|
111
|
+
req = { op: 'introspect', subject: subject.to_s }
|
|
112
|
+
req[:duration_secs] = duration_secs if duration_secs
|
|
113
|
+
resp = roundtrip_raw(req)
|
|
114
|
+
parse_introspect(subject.to_s, resp)
|
|
70
115
|
end
|
|
71
116
|
|
|
72
117
|
# Closes the underlying socket connection.
|
|
@@ -77,15 +122,20 @@ module Beachcomber
|
|
|
77
122
|
private
|
|
78
123
|
|
|
79
124
|
def roundtrip(req)
|
|
125
|
+
resp = roundtrip_raw(req)
|
|
126
|
+
build_result(resp)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def roundtrip_raw(req)
|
|
80
130
|
line = JSON.generate(req) + "\n"
|
|
81
131
|
@socket.write(line)
|
|
82
132
|
raw = @socket.gets
|
|
83
133
|
raise ProtocolError, "connection closed before response" if raw.nil?
|
|
84
134
|
|
|
85
|
-
|
|
135
|
+
parse_response_hash(raw.chomp)
|
|
86
136
|
end
|
|
87
137
|
|
|
88
|
-
def
|
|
138
|
+
def parse_response_hash(raw)
|
|
89
139
|
begin
|
|
90
140
|
resp = JSON.parse(raw)
|
|
91
141
|
rescue JSON::ParserError => e
|
|
@@ -96,17 +146,76 @@ module Beachcomber
|
|
|
96
146
|
raise ProtocolError, "expected JSON object, got #{resp.class}"
|
|
97
147
|
end
|
|
98
148
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
stale = resp['stale'] == true
|
|
103
|
-
error = resp['error']
|
|
149
|
+
unless resp['ok']
|
|
150
|
+
raise ServerError, (resp['error'] || 'unknown error')
|
|
151
|
+
end
|
|
104
152
|
|
|
105
|
-
|
|
106
|
-
|
|
153
|
+
resp
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def build_result(resp)
|
|
157
|
+
Result.new(
|
|
158
|
+
ok: resp['ok'],
|
|
159
|
+
data: resp['data'],
|
|
160
|
+
age_ms: (resp['age_ms'] || 0).to_i,
|
|
161
|
+
stale: resp['stale'] == true,
|
|
162
|
+
error: resp['error'],
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def parse_hello(resp)
|
|
167
|
+
data = resp["data"] || {}
|
|
168
|
+
HelloInfo.new(
|
|
169
|
+
protocol_version: data["protocol_version"].to_s,
|
|
170
|
+
daemon_version: data["daemon_version"].to_s,
|
|
171
|
+
)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def parse_cache_rows(resp)
|
|
175
|
+
arr = resp["data"]
|
|
176
|
+
raise ProtocolError, "status data is not an array" unless arr.is_a?(Array)
|
|
177
|
+
arr.map do |row|
|
|
178
|
+
CacheRow.new(
|
|
179
|
+
provider: row["provider"].to_s,
|
|
180
|
+
field: row["field"],
|
|
181
|
+
path: row["path"],
|
|
182
|
+
value: row["value"],
|
|
183
|
+
age_ms: Integer(row["age_ms"] || 0),
|
|
184
|
+
stale: row["stale"] == true,
|
|
185
|
+
kind: row["kind"],
|
|
186
|
+
poll_interval_secs: row["poll_interval_secs"],
|
|
187
|
+
keep_alive_polls: row["keep_alive_polls"],
|
|
188
|
+
fsevents_reinstate: row["fsevents_reinstate"],
|
|
189
|
+
failure: row["failure"],
|
|
190
|
+
source: row["source"],
|
|
191
|
+
)
|
|
107
192
|
end
|
|
193
|
+
end
|
|
108
194
|
|
|
109
|
-
|
|
195
|
+
def parse_daemon_health(data)
|
|
196
|
+
DaemonHealth.new(
|
|
197
|
+
pid: Integer(data["pid"] || 0),
|
|
198
|
+
version: data["version"].to_s,
|
|
199
|
+
uptime_secs: Integer(data["uptime_secs"] || 0),
|
|
200
|
+
socket_path: data["socket_path"].to_s,
|
|
201
|
+
config_path: data["config_path"],
|
|
202
|
+
requests_total: Integer(data["requests_total"] || 0),
|
|
203
|
+
in_flight: Integer(data["in_flight"] || 0),
|
|
204
|
+
active_watchers: Integer(data["active_watchers"] || 0),
|
|
205
|
+
cache_entries: Integer(data["cache_entries"] || 0),
|
|
206
|
+
verdicts: (data["verdicts"] || []).map do |v|
|
|
207
|
+
Verdict.new(level: v["level"].to_s, message: v["message"].to_s)
|
|
208
|
+
end,
|
|
209
|
+
)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def parse_introspect(subject, resp)
|
|
213
|
+
data = resp["data"]
|
|
214
|
+
if subject == IntrospectSubject::DAEMON && data.is_a?(Hash)
|
|
215
|
+
IntrospectResponse.new(subject: subject, daemon: parse_daemon_health(data), other: nil)
|
|
216
|
+
else
|
|
217
|
+
IntrospectResponse.new(subject: subject, daemon: nil, other: data)
|
|
218
|
+
end
|
|
110
219
|
end
|
|
111
220
|
end
|
|
112
221
|
|
|
@@ -137,7 +246,18 @@ module Beachcomber
|
|
|
137
246
|
def get(key, path: nil)
|
|
138
247
|
req = { op: 'get', key: key }
|
|
139
248
|
req[:path] = path if path
|
|
140
|
-
roundtrip
|
|
249
|
+
with_session { |s| s.send(:roundtrip, req) }
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
# Reads a cached value with protocol flags.
|
|
253
|
+
#
|
|
254
|
+
# @param key [String]
|
|
255
|
+
# @param path [String, nil]
|
|
256
|
+
# @param force [Boolean] bypass cache and recompute
|
|
257
|
+
# @param wait [Boolean] block until a fresh value is available
|
|
258
|
+
# @return [Result]
|
|
259
|
+
def get_with_flags(key, path: nil, force: false, wait: false)
|
|
260
|
+
with_session { |s| s.get_with_flags(key, path: path, force: force, wait: wait) }
|
|
141
261
|
end
|
|
142
262
|
|
|
143
263
|
# Forces the daemon to recompute a provider/key.
|
|
@@ -146,25 +266,59 @@ module Beachcomber
|
|
|
146
266
|
# @param path [String, nil]
|
|
147
267
|
# @raise [DaemonNotRunning]
|
|
148
268
|
# @raise [ServerError]
|
|
149
|
-
def
|
|
150
|
-
req = { op: '
|
|
269
|
+
def refresh(key, path: nil)
|
|
270
|
+
req = { op: 'refresh', key: key }
|
|
151
271
|
req[:path] = path if path
|
|
152
|
-
roundtrip
|
|
272
|
+
with_session { |s| s.send(:roundtrip, req) }
|
|
153
273
|
nil
|
|
154
274
|
end
|
|
155
275
|
|
|
156
|
-
#
|
|
276
|
+
# Returns cache rows from the daemon.
|
|
157
277
|
#
|
|
158
|
-
# @return [
|
|
159
|
-
def
|
|
160
|
-
|
|
278
|
+
# @return [Array<CacheRow>]
|
|
279
|
+
def status
|
|
280
|
+
with_session { |s| s.status }
|
|
161
281
|
end
|
|
162
282
|
|
|
163
|
-
#
|
|
283
|
+
# Sends a hello handshake and returns server info.
|
|
164
284
|
#
|
|
165
|
-
# @return [
|
|
166
|
-
def
|
|
167
|
-
|
|
285
|
+
# @return [HelloInfo]
|
|
286
|
+
def hello
|
|
287
|
+
with_session { |s| s.hello }
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Writes a value into the daemon cache.
|
|
291
|
+
#
|
|
292
|
+
# @param key [String]
|
|
293
|
+
# @param data [Object, nil]
|
|
294
|
+
# @param ttl [Numeric, nil] time-to-live in seconds
|
|
295
|
+
# @param path [String, nil]
|
|
296
|
+
# @return [nil]
|
|
297
|
+
def put(key, data = nil, ttl: nil, path: nil)
|
|
298
|
+
with_session { |s| s.put(key, data, ttl: ttl, path: path) }
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
# Introspects a daemon subsystem.
|
|
302
|
+
#
|
|
303
|
+
# @param subject [String] one of the IntrospectSubject constants
|
|
304
|
+
# @param duration_secs [Numeric, nil]
|
|
305
|
+
# @return [IntrospectResponse]
|
|
306
|
+
def introspect(subject, duration_secs: nil)
|
|
307
|
+
with_session { |s| s.introspect(subject, duration_secs: duration_secs) }
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
# Opens a persistent watch subscription. Returns a WatchStream (Enumerable).
|
|
311
|
+
# The caller is responsible for closing the stream.
|
|
312
|
+
#
|
|
313
|
+
# @param key [String]
|
|
314
|
+
# @param path [String, nil]
|
|
315
|
+
# @return [WatchStream]
|
|
316
|
+
def watch(key, path: nil)
|
|
317
|
+
sock = open_socket
|
|
318
|
+
req = { op: 'watch', key: key }
|
|
319
|
+
req[:path] = path if path
|
|
320
|
+
sock.write(JSON.generate(req) + "\n")
|
|
321
|
+
WatchStream.new(sock)
|
|
168
322
|
end
|
|
169
323
|
|
|
170
324
|
# Opens a persistent session and yields it to the block. The connection is
|
|
@@ -173,40 +327,59 @@ module Beachcomber
|
|
|
173
327
|
# @yield [Session]
|
|
174
328
|
# @return the block's return value
|
|
175
329
|
def session
|
|
176
|
-
sock
|
|
177
|
-
|
|
178
|
-
yield
|
|
330
|
+
sock = open_socket
|
|
331
|
+
sess = Session.new(sock, @timeout)
|
|
332
|
+
yield sess
|
|
179
333
|
ensure
|
|
180
|
-
|
|
334
|
+
sess&.close
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
RETRY_BACKOFFS = [0.250, 0.500, 1.000].freeze
|
|
338
|
+
|
|
339
|
+
# Connect to a Unix socket with 3 retries (250ms/500ms/1s exponential).
|
|
340
|
+
# Retries on ECONNREFUSED and ENOENT only — other errors surface immediately.
|
|
341
|
+
# Intended to cover the brief restart window when the daemon is restarting.
|
|
342
|
+
#
|
|
343
|
+
# @param sock_path [String] absolute path to the Unix domain socket
|
|
344
|
+
# @return [UNIXSocket]
|
|
345
|
+
# @raise [Errno::ECONNREFUSED, Errno::ENOENT] after all retries are exhausted
|
|
346
|
+
def self._connect_with_retry(sock_path)
|
|
347
|
+
last_error = nil
|
|
348
|
+
RETRY_BACKOFFS.each do |backoff|
|
|
349
|
+
begin
|
|
350
|
+
return UNIXSocket.new(sock_path)
|
|
351
|
+
rescue Errno::ECONNREFUSED, Errno::ENOENT => e
|
|
352
|
+
last_error = e
|
|
353
|
+
sleep backoff
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
# Final attempt — raises if still failing.
|
|
357
|
+
UNIXSocket.new(sock_path)
|
|
181
358
|
end
|
|
182
359
|
|
|
183
360
|
private
|
|
184
361
|
|
|
185
|
-
def
|
|
186
|
-
sock =
|
|
362
|
+
def with_session(&block)
|
|
363
|
+
sock = open_socket
|
|
187
364
|
begin
|
|
188
365
|
s = Session.new(sock, @timeout)
|
|
189
|
-
|
|
366
|
+
block.call(s)
|
|
190
367
|
ensure
|
|
191
368
|
sock.close unless sock.closed?
|
|
192
369
|
end
|
|
193
370
|
end
|
|
194
371
|
|
|
195
|
-
def
|
|
196
|
-
sock = Socket.new(:UNIX, :STREAM)
|
|
197
|
-
addr = Socket.pack_sockaddr_un(@socket_path)
|
|
198
|
-
|
|
199
|
-
# Apply timeout to both connect and subsequent reads/writes.
|
|
200
|
-
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, timeval(@timeout))
|
|
201
|
-
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, timeval(@timeout))
|
|
202
|
-
|
|
372
|
+
def open_socket
|
|
203
373
|
begin
|
|
204
|
-
sock.
|
|
205
|
-
rescue Errno::ENOENT, Errno::ECONNREFUSED, Errno::EACCES
|
|
206
|
-
sock.close
|
|
374
|
+
sock = self.class._connect_with_retry(@socket_path)
|
|
375
|
+
rescue Errno::ENOENT, Errno::ECONNREFUSED, Errno::EACCES => e
|
|
207
376
|
raise DaemonNotRunning.new(@socket_path)
|
|
208
377
|
end
|
|
209
378
|
|
|
379
|
+
# Apply timeouts to the connected socket.
|
|
380
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, timeval(@timeout))
|
|
381
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVTIMEO, timeval(@timeout))
|
|
382
|
+
|
|
210
383
|
sock
|
|
211
384
|
end
|
|
212
385
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Beachcomber
|
|
2
|
+
HelloInfo = Struct.new(:protocol_version, :daemon_version, keyword_init: true)
|
|
3
|
+
CacheRow = Struct.new(
|
|
4
|
+
:provider, :field, :path, :value, :age_ms, :stale,
|
|
5
|
+
:kind, :poll_interval_secs, :keep_alive_polls, :fsevents_reinstate, :failure,
|
|
6
|
+
:source,
|
|
7
|
+
keyword_init: true
|
|
8
|
+
)
|
|
9
|
+
Verdict = Struct.new(:level, :message, keyword_init: true)
|
|
10
|
+
DaemonHealth = Struct.new(
|
|
11
|
+
:pid, :version, :uptime_secs, :socket_path, :config_path,
|
|
12
|
+
:requests_total, :in_flight, :active_watchers, :cache_entries, :verdicts,
|
|
13
|
+
keyword_init: true
|
|
14
|
+
)
|
|
15
|
+
WatchEvent = Struct.new(:data, :age_ms, :stale, keyword_init: true)
|
|
16
|
+
|
|
17
|
+
module IntrospectSubject
|
|
18
|
+
DAEMON = "daemon"
|
|
19
|
+
PROVIDERS = "providers"
|
|
20
|
+
CONFIG = "config"
|
|
21
|
+
CACHE = "cache"
|
|
22
|
+
LIFECYCLE = "lifecycle"
|
|
23
|
+
WATCHES = "watches"
|
|
24
|
+
TIMERS = "timers"
|
|
25
|
+
DEMAND = "demand"
|
|
26
|
+
PROCS = "procs"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Introspect response wrapper. For DAEMON subject: #daemon is populated.
|
|
30
|
+
# For others: #other holds the raw Hash/Array.
|
|
31
|
+
IntrospectResponse = Struct.new(:subject, :daemon, :other, keyword_init: true)
|
|
32
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Beachcomber
|
|
2
|
+
class WatchStream
|
|
3
|
+
include Enumerable
|
|
4
|
+
|
|
5
|
+
def initialize(socket)
|
|
6
|
+
@socket = socket
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Yields a WatchEvent per emitted change.
|
|
10
|
+
def each
|
|
11
|
+
return enum_for(:each) unless block_given?
|
|
12
|
+
while (line = @socket.gets)
|
|
13
|
+
line.strip!
|
|
14
|
+
next if line.empty?
|
|
15
|
+
resp = JSON.parse(line)
|
|
16
|
+
unless resp["ok"]
|
|
17
|
+
raise ServerError, resp["error"] || "watch error"
|
|
18
|
+
end
|
|
19
|
+
yield WatchEvent.new(
|
|
20
|
+
data: resp["data"],
|
|
21
|
+
age_ms: Integer(resp["age_ms"] || 0),
|
|
22
|
+
stale: resp["stale"] == true,
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Read the next event; returns nil on connection close.
|
|
28
|
+
def next_event
|
|
29
|
+
loop do
|
|
30
|
+
line = @socket.gets
|
|
31
|
+
return nil if line.nil?
|
|
32
|
+
line.strip!
|
|
33
|
+
next if line.empty?
|
|
34
|
+
resp = JSON.parse(line)
|
|
35
|
+
unless resp["ok"]
|
|
36
|
+
raise ServerError, resp["error"] || "watch error"
|
|
37
|
+
end
|
|
38
|
+
return WatchEvent.new(
|
|
39
|
+
data: resp["data"],
|
|
40
|
+
age_ms: Integer(resp["age_ms"] || 0),
|
|
41
|
+
stale: resp["stale"] == true,
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def close
|
|
47
|
+
@socket.close
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
data/lib/beachcomber.rb
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
require_relative 'beachcomber/errors'
|
|
2
2
|
require_relative 'beachcomber/result'
|
|
3
|
+
require_relative 'beachcomber/types'
|
|
3
4
|
require_relative 'beachcomber/discovery'
|
|
5
|
+
require_relative 'beachcomber/watch_stream'
|
|
4
6
|
require_relative 'beachcomber/client'
|
|
5
7
|
|
|
6
8
|
# Beachcomber is a Ruby client for the beachcomber daemon.
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: libbeachcomber
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- NavistAu
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-05-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Communicates with the beachcomber daemon over a Unix domain socket to
|
|
14
14
|
query cached shell-environment data (git state, hostname, battery, etc.).
|
|
@@ -22,6 +22,8 @@ files:
|
|
|
22
22
|
- lib/beachcomber/discovery.rb
|
|
23
23
|
- lib/beachcomber/errors.rb
|
|
24
24
|
- lib/beachcomber/result.rb
|
|
25
|
+
- lib/beachcomber/types.rb
|
|
26
|
+
- lib/beachcomber/watch_stream.rb
|
|
25
27
|
homepage: https://github.com/NavistAu/beachcomber
|
|
26
28
|
licenses:
|
|
27
29
|
- MIT
|