cgminer_api_client 0.2.6 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8df86a4b12bb2eef41633534532290139749a0491fb27d89a5c7ce55b596d4a5
4
- data.tar.gz: adce4406c1bcec642a7b6698473a355a211b5c467765aaba466ef34efd563d64
3
+ metadata.gz: 97da0040fa9d47c7d6a8157e53a30decf6e62ca216e3fb50af638dbcc6189d7c
4
+ data.tar.gz: aa30a0192cbf07991b90d904ecb476558ad509d15f001d0cc1f5b3daefc0f835
5
5
  SHA512:
6
- metadata.gz: 0305613f25ab54883339bb54f7155ff08b09af899d42a57dabd8dd051b6176d4aed3f698584606dd6081045756cb6ffe16035ec8abaca382271289f30859e86c
7
- data.tar.gz: e093f302848547c460865383a7fd65eb48103c1dab9d2991109376ab5bc9c3c3e1d32c66140dcb031da5e65ee634f840d51dfff6a689364426b360f6099cdf91
6
+ metadata.gz: 73458d32d4079dc3483967d9329cf5bbbb1be4a73cd6ef8e202e3872b36ae9c18eac60bfb5ce81befe575cb0085ec1fd6f49e7df28fd7386e432ad40f83d71a3
7
+ data.tar.gz: 7bcde1758bd4ce3a8f111c7d21fe931683158a284de7055d3eed1be3b91bafc1ee2f8694dbd53a14173383da35e5691f2be64091dd4dbf317e3a65dc66f70bbf
data/CHANGELOG.md ADDED
@@ -0,0 +1,385 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.4.0] - 2026-04-25
11
+
12
+ ### Added
13
+ - **`CgminerApiClient::AccessDeniedError`**, a subclass of `ApiError`
14
+ for the most commonly dispatched-on case (cgminer Code 45 + the
15
+ `access_denied?` local guard). Inherits from `ApiError` so existing
16
+ `rescue ApiError` clauses still catch it; callers wanting finer
17
+ dispatch use `rescue AccessDeniedError`. Constructor pins
18
+ `code: :access_denied` so the symbolic tag is consistent. The
19
+ wire-side `Miner#check_status` delegates to a new factory
20
+ `ApiError.for_status(c, msg)` that picks the right subclass
21
+ based on the integer Code.
22
+ - **Structured error codes on `CgminerApiClient::ApiError`.** Two new
23
+ reader methods alongside the existing `#message`:
24
+ `#cgminer_code` (the integer Code from cgminer's STATUS hash —
25
+ e.g., `45` for access denied — or `nil` if the wire integer was
26
+ discarded, as happens when the `access_denied?` local guard's
27
+ call to `privileged` hits the wire and the rescue inside
28
+ `privileged` drops the integer before re-raising) and `#code`
29
+ (a symbolic tag derived from the integer via
30
+ `ApiError::CGMINER_CODES`, falling back to `:unknown` for codes
31
+ not in the map). Callers can now `case e.code; when :access_denied`
32
+ instead of parsing English error strings. **Prefer `e.code` for
33
+ dispatch over `e.cgminer_code`** — `cgminer_code` can be nil even
34
+ when the symbolic tag is set. The map is intentionally
35
+ conservative — `14 → :invalid_command`, `45 → :access_denied`,
36
+ the codes observed against real cgminer wire fixtures.
37
+ Backward-compatible: `raise ApiError, "msg"` still works and
38
+ `e.message` is unchanged.
39
+
40
+ ### Changed
41
+ - `ApiError` constructor now validates `cgminer_code:` is `Integer`
42
+ or `nil` and `code:` is `Symbol`, `String`, or `nil`. Bad input
43
+ raises `ArgumentError` with a clear message instead of silently
44
+ producing `code: :unknown` (string `cgminer_code`) or
45
+ `NoMethodError` deep in the constructor (`code: 42`). Wire-side
46
+ callers go through `ApiError.for_status` which coerces a
47
+ non-numeric Code to `nil` so dispatch falls through to `:unknown`.
48
+ - **`docs/logging.md`** — short stub stating that `cgminer_api_client`
49
+ is intentionally silent: no `Logger` module, no structured log
50
+ events. The library raises on failure and returns result objects
51
+ on success; callers (`cgminer_monitor`, `cgminer_manager`, the
52
+ operator CLIs) own the log call sites. Points at
53
+ `cgminer_monitor/docs/log_schema.md` for the cross-repo schema
54
+ contract and names the events (`poll.miner_failed`,
55
+ `poll.unexpected_error`) that surface api_client exception classes.
56
+ - **`bundle-audit` in CI** (`.github/workflows/ci.yml`). New `audit`
57
+ job runs `bundle exec bundle-audit check --update` on every push
58
+ and PR, gating merges on known CVEs in `Gemfile.lock`. Advisory
59
+ DB is refreshed on each run from `rubysec/ruby-advisory-db`. Also
60
+ available locally as `bundle exec rake audit`.
61
+ - **Dependabot config** (`.github/dependabot.yml`). Weekly bump PRs
62
+ for Bundler and GitHub Actions, with `open-pull-requests-limit: 3`
63
+ per ecosystem. `versioning-strategy: lockfile-only` on bundler, so
64
+ Gemfile / gemspec constraints are never auto-widened — humans
65
+ widen `~>` bounds intentionally. Targets `develop` so bumps flow
66
+ through the normal release cycle alongside feature work.
67
+ - **`-v` / `--verbose` flag on the `cgminer_api_client` CLI.** Logs the
68
+ JSON request and raw response to stderr, one line each, with a
69
+ `host:port` prefix so multi-miner fan-out output stays grep-able. The
70
+ formatted result still goes to stdout unchanged. Parsed via
71
+ `OptionParser#permute!`, so the flag works before or after the
72
+ command. Passwords in `addpool`, `setconfig`, `ascset`, and `pgaset`
73
+ are replaced with `[REDACTED]` in the log output (wire bytes are
74
+ unaffected).
75
+ - **`on_wire:` kwarg on `Miner#initialize` and `MinerPool#initialize`.**
76
+ Library-level hook used by the CLI's `-v` flag. Accepts a Proc of
77
+ shape `(direction, host, port, payload)` where direction is
78
+ `:request`, `:response`, or `:response_repaired`. Default `nil` is a
79
+ no-op; library code does not write to stderr itself.
80
+
81
+ ### Changed
82
+ - **`Miner::Commands::Privileged#access_denied?`** now raises
83
+ `CgminerApiClient::ApiError` with message `'access denied'`
84
+ instead of a bare `RuntimeError` with message `'access_denied'`.
85
+ Callers using `rescue CgminerApiClient::Error` or
86
+ `rescue StandardError` are unaffected; only code that specifically
87
+ pattern-matched on `RuntimeError` from a privileged command on an
88
+ unprivileged miner needs to update.
89
+ - **`MinerPool#load_miners!`** now raises `CgminerApiClient::Error`
90
+ (was `RuntimeError`) when `config/miners.yml` is missing. Existing
91
+ `rescue StandardError` clauses still work.
92
+ - Test-support code (FakeCgminer, CgminerFixtures) extracted to the
93
+ shared `cgminer_test_support` gem. Spec references now use
94
+ `CgminerTestSupport::FakeCgminer` and
95
+ `CgminerTestSupport::Fixtures::SUMMARY` etc. `script/fake_cgminer`
96
+ is now a thin shim that delegates to `bundle exec fake_cgminer`;
97
+ operator muscle memory unchanged.
98
+
99
+ ### Fixed
100
+ - **`MinerPool` no longer silently defaults a miners.yml entry
101
+ missing `host` to `CgminerApiClient.default_host`.** A typo'd
102
+ config entry like `{port: 4028}` used to quietly produce a Miner
103
+ pointing at `127.0.0.1`; now raises
104
+ `CgminerApiClient::Error "config/miners.yml: entry N is missing 'host'"`
105
+ on `MinerPool.new`.
106
+
107
+ ## [0.3.0] - 2026-04-07
108
+
109
+ ### Removed
110
+ - Support for Ruby 2.x and Ruby 3.1. The gem now requires Ruby 3.2 or higher.
111
+ - `Miner#query`'s short-circuit on `!available?`. `query` now calls
112
+ `perform_request` directly and lets `ConnectionError` propagate to
113
+ the caller. Previously an unreachable miner caused `query` to
114
+ return `nil` silently.
115
+ - `Miner#available?`'s `force_reload` parameter. The method no
116
+ longer caches, so the parameter is meaningless.
117
+ - `MinerPool#available_miners` and `MinerPool#unavailable_miners`
118
+ `force_reload` parameter. Same reason.
119
+ - The host:port-prefixed stderr warning inside `MinerPool#query`
120
+ (added earlier in the 0.3.0 cycle). Library code should not
121
+ print unsolicited; the error is now structurally available via
122
+ `PoolResult` and the CLI prints it explicitly.
123
+ - `pry` from the development dependencies (it was unused).
124
+ - `.travis.yml` (Travis CI is effectively deprecated for OSS Ruby).
125
+ - `.whitesource` (Mend Bolt for GitHub was sunset; if SCA is wanted
126
+ later, configure Dependabot via `.github/dependabot.yml`).
127
+
128
+ ### Added
129
+ - **`CgminerApiClient::MinerResult`** — immutable value object
130
+ (backed by `Data.define`) wrapping a single per-miner outcome.
131
+ Has `miner`, `value`, `error`, `ok?`, `failed?`, `raise!`,
132
+ plus free `==`, `hash`, `eql?`, `inspect`, `to_h`, and
133
+ `deconstruct_keys` from `Data.define` for pattern matching.
134
+ - **`CgminerApiClient::PoolResult`** — Enumerable wrapper around
135
+ `Array<MinerResult>` returned from every `MinerPool` query.
136
+ Preserves miner order. High-level helpers: `#values`, `#errors`,
137
+ `#successful`, `#failed`, `#all_successful?`, `#any_succeeded?`,
138
+ `#any_failed?`, plus `#[](key)` for lookup by index, Miner
139
+ instance, or `"host:port"` string.
140
+ - `CgminerApiClient::Error`, `CgminerApiClient::ConnectionError`,
141
+ `CgminerApiClient::TimeoutError` (subclass of ConnectionError),
142
+ and `CgminerApiClient::ApiError` exception classes. All subclass
143
+ the base `Error` which itself subclasses `StandardError`, so
144
+ existing rescues keep working.
145
+ - `respond_to_missing?` on `Miner` and `MinerPool` so introspection
146
+ (`respond_to?`, `Object#method`, etc.) reflects the dynamic API
147
+ surface while excluding internal probes (`to_*`, `_*`).
148
+ - `# frozen_string_literal: true` pragma on every Ruby file.
149
+ - `required_ruby_version >= 3.2` in the gemspec.
150
+ - Gemspec metadata (`source_code_uri`, `changelog_uri`,
151
+ `bug_tracker_uri`, `rubygems_mfa_required`).
152
+ - RuboCop with `rubocop-rspec` and `rubocop-rake` plugins,
153
+ integrated into the default Rake task.
154
+ - GitHub Actions CI matrix testing Ruby 3.2, 3.3, 3.4, and 4.0
155
+ (plus `head` as an early-warning, allowed to fail).
156
+ - `.ruby-version` file pinning local development to 4.0.2 (the
157
+ gem itself supports 3.2+; the pin is only for contributors).
158
+ - `CHANGELOG.md` (this file).
159
+ - Unit test coverage brought to 99.66% on `lib/` (was 97.4% before
160
+ the 0.3.0 work). The entire `SocketWithTimeout#open_socket`
161
+ method, `MinerPool#available_miners`, `#unavailable_miners`,
162
+ the thread-rescue branch in `MinerPool#query`, the control-
163
+ character escape path in `Miner#perform_request`, and the new
164
+ `MinerResult` / `PoolResult` value objects all have dedicated
165
+ specs.
166
+ - End-to-end integration test suite at `spec/integration/`.
167
+ `miner_integration_spec.rb` exercises the full request → TCP
168
+ socket → response → parse → result path against a `FakeCgminer`
169
+ server running in a background thread. `cli_spec.rb` spawns
170
+ the real binary via `Open3` and asserts on exit codes,
171
+ stdout/stderr split, and `DEBUG=1` backtrace behavior.
172
+ - `script/fake_cgminer` for manual sandbox testing. Starts the
173
+ same fake cgminer server on a fixed port (default 4028) in the
174
+ foreground, so you can run the CLI against it without hardware.
175
+ Intentionally lives in `script/` rather than `bin/` so it isn't
176
+ packaged with the gem.
177
+
178
+ ### Changed
179
+ - **`MinerPool#query` now returns a `PoolResult`** instead of an
180
+ `Array` with `[]` sentinels for failed miners. Callers iterate
181
+ `MinerResult` instances and explicitly distinguish success from
182
+ failure. Failed miners are no longer confused with successful
183
+ ones returning empty arrays.
184
+ - **`MinerPool` now overrides `summary`, `coin`, `config`,
185
+ `version`, and `check`** to return a `PoolResult` of unwrapped
186
+ hashes. Previously these convenience methods called
187
+ `query(:name)[0]`, which silently returned only the *first*
188
+ miner's hash on multi-miner pools (a pre-existing latent bug).
189
+ - **`Miner#query` now raises `ConnectionError`** instead of
190
+ returning `nil` when a miner is unreachable. Single-Miner
191
+ callers need to rescue; MinerPool callers get the error
192
+ captured in a `MinerResult.failure` automatically.
193
+ - **`Miner#available?` is now a true reachability probe.** No
194
+ cache, narrow rescue list (only `SocketError`,
195
+ `SystemCallError`, and `TimeoutError` — bugs like `ArgumentError`
196
+ propagate). Always re-checks.
197
+ - `SocketWithTimeout` raises `CgminerApiClient::TimeoutError`
198
+ instead of a bare `RuntimeError` on connect timeout.
199
+ - `Miner#perform_request` raises `ConnectionError` (not
200
+ `RuntimeError`) on socket open failure, with a message that
201
+ includes the original error class and message.
202
+ - `Miner#check_status` raises `ApiError` (not `RuntimeError`) for
203
+ cgminer status codes `E` and `F`.
204
+ - **`bin/cgminer_api_client` redesigned**: errors go to stderr
205
+ (not stdout), exit codes follow shell conventions (0 on any
206
+ success, 1 on all-failure, 64/EX_USAGE on unknown command),
207
+ `DEBUG=1` env var prints full backtraces via
208
+ `Exception#full_message`, and output is structured per-miner
209
+ with `host:port:` headers.
210
+ - `YAML.load_file` → `YAML.safe_load_file` for the miners config.
211
+ - `IO.select(nil, [socket], nil, timeout)` →
212
+ `socket.wait_writable(timeout)` in `SocketWithTimeout` —
213
+ Fiber-scheduler compatible.
214
+ - `String#match` → `String#match?` where the result is only used
215
+ as a boolean.
216
+ - `length == 0` → `empty?`, `'%04x' %` → `format`, and other
217
+ small modernizations.
218
+ - Bare `rescue` clauses now specify `StandardError` explicitly.
219
+ - Gemspec file list switched from `git ls-files` to explicit
220
+ `Dir.glob` patterns; `spec/` is no longer packaged in the gem.
221
+ - Bumped minimum versions of `rake`, `rspec`, and `simplecov`.
222
+
223
+ ### Fixed
224
+ - **`Miner#query` parameter escape was a silent no-op for
225
+ backslashes.** The intent was to double literal backslashes so
226
+ they round-trip through cgminer's comma-separated parameter
227
+ syntax, but `gsub('\\', '\\\\')` is parsed as "replace `\` with
228
+ `\`" because in gsub's replacement-string DSL, `\\` denotes a
229
+ single literal backslash. Fixed by switching to
230
+ `gsub('\\') { '\\\\' }` (block form bypasses replacement-string
231
+ interpretation). Verified empirically; locked down by four
232
+ explicit specs.
233
+ - **`Miner::Commands#privileged` mislabeled connection errors as
234
+ access denied.** Previously rescued every `StandardError` and
235
+ returned `false`, which propagated through `access_denied?` to
236
+ raise `'access_denied'` from every privileged command
237
+ (`addpool`, `restart`, `quit`, `save`, `ascset`, etc.) during a
238
+ transient network outage — sending operators chasing phantom
239
+ auth/whitelist bugs. Now rescues only `ApiError` so
240
+ connection-layer failures surface as `ConnectionError` instead.
241
+ - **`MinerPool#summary` / `#coin` / `#config` / `#version` /
242
+ `#check` silently returned only the first miner's hash on
243
+ multi-miner pools.** The inherited `Miner::Commands::ReadOnly`
244
+ methods do `query(:name)[0]` to unwrap cgminer's single-element
245
+ response arrays, which works on a single Miner but drops every
246
+ result except the first on a pool. Fixed by overriding all five
247
+ on `MinerPool` to return a `PoolResult` of unwrapped hashes.
248
+ - **`Miner#available?` permanently cached `false` after any
249
+ transient failure.** Once a brief network blip had marked a
250
+ miner unavailable, the gem refused to talk to it for the
251
+ lifetime of the process. Fixed by dropping the cache entirely.
252
+ - Mismatched indentation in `def privileged` that was producing a
253
+ Ruby parser warning.
254
+
255
+ ## Migration guide: 0.2.x → 0.3.0
256
+
257
+ This release intentionally contains breaking changes to finish
258
+ cleanups that were overdue. The gem is still on 0.x and only
259
+ ~2,600 of 35,000 total downloads are on 0.2.6, so the
260
+ migration surface is small. Here's what to update.
261
+
262
+ ### Ruby 3.2+
263
+
264
+ The gemspec now requires Ruby 3.2. If you're on 3.1 or older, you
265
+ need to upgrade Ruby before you can use this release.
266
+
267
+ ### `MinerPool#query` (and all pool commands) return `PoolResult`
268
+
269
+ ```ruby
270
+ # Before (0.2.x)
271
+ pool.summary.each { |s| puts s[:mhs_av] }
272
+ pool.summary.first[:mhs_av]
273
+
274
+ # After (0.3.0)
275
+ pool.summary.values.each { |s| puts s[:mhs_av] }
276
+ pool.summary.values.first[:mhs_av]
277
+
278
+ # Or iterate per-miner with failure handling:
279
+ pool.summary.each do |result|
280
+ if result.ok?
281
+ puts "#{result.miner.host}: #{result.value[:mhs_av]}"
282
+ else
283
+ warn "#{result.miner.host}: #{result.error.message}"
284
+ end
285
+ end
286
+ ```
287
+
288
+ `PoolResult` includes `Enumerable`, so `.first`, `.map`, `.count`,
289
+ `.find` etc. all work — but now they yield `MinerResult`
290
+ instances, not raw hashes. Use `.values` to get just the
291
+ successful values as an Array.
292
+
293
+ ### `Miner#query` raises `ConnectionError` instead of returning `nil`
294
+
295
+ ```ruby
296
+ # Before (0.2.x)
297
+ result = miner.summary
298
+ return unless result # nil meant unreachable
299
+ puts result[:mhs_av]
300
+
301
+ # After (0.3.0)
302
+ begin
303
+ result = miner.summary
304
+ puts result[:mhs_av]
305
+ rescue CgminerApiClient::ConnectionError => e
306
+ warn "miner unreachable: #{e.message}"
307
+ end
308
+ ```
309
+
310
+ If you're using `MinerPool` instead of `Miner` directly, you
311
+ don't need to change anything — `MinerPool` catches the
312
+ `ConnectionError` per miner and turns it into a
313
+ `MinerResult.failure`.
314
+
315
+ ### `MinerPool#summary` etc. now return every miner, not just the first
316
+
317
+ If you were calling `pool.summary` on a multi-miner pool and
318
+ relying on getting back a single Hash, you were actually being
319
+ bitten by a pre-existing bug (the result was silently dropping
320
+ every miner except the first). Now you get a `PoolResult` with
321
+ one entry per miner. Use `.values.first` if you really want
322
+ just the first miner.
323
+
324
+ ### `force_reload` parameter removed
325
+
326
+ ```ruby
327
+ # Before (0.2.x)
328
+ pool.available_miners(true) # force re-check
329
+ miner.available?(true)
330
+
331
+ # After (0.3.0)
332
+ pool.available_miners # always re-checks
333
+ miner.available?
334
+ ```
335
+
336
+ There's no cache to flush anymore.
337
+
338
+ ### `MinerPool#query` no longer writes to stderr
339
+
340
+ Previously, a failed per-miner query would print
341
+ `[host:port] ErrorClass: message` to stderr as a side effect of
342
+ calling `pool.query`. That was an earlier 0.3.0 addition; it's
343
+ removed because library code shouldn't print. The error is now
344
+ carried structurally in the `MinerResult.failure` inside the
345
+ `PoolResult`, and you can display it however you want. The CLI
346
+ (`bin/cgminer_api_client`) does display it on stderr.
347
+
348
+ ### CLI exit codes changed
349
+
350
+ ```
351
+ # Before (0.2.x)
352
+ cgminer_api_client summary # exit 0 whether or not it worked
353
+ # errors written to stdout
354
+
355
+ # After (0.3.0)
356
+ cgminer_api_client summary # exit 0 if any miner succeeded,
357
+ # 1 if all failed,
358
+ # 64 for unknown command.
359
+ # errors on stderr.
360
+ DEBUG=1 cgminer_api_client summary # also prints full backtraces
361
+ ```
362
+
363
+ If you had a shell pipeline that captured the CLI's stdout
364
+ expecting the whole data blob (including errors), your script
365
+ needs updating. Errors now go to stderr like they should.
366
+
367
+ ### Exception class hierarchy
368
+
369
+ All gem-specific errors now inherit from
370
+ `CgminerApiClient::Error < StandardError`:
371
+
372
+ ```
373
+ CgminerApiClient::Error
374
+ ├── CgminerApiClient::ConnectionError
375
+ │ └── CgminerApiClient::TimeoutError
376
+ └── CgminerApiClient::ApiError
377
+ ```
378
+
379
+ Existing `rescue StandardError` clauses keep working. If you
380
+ want to catch everything the gem raises, `rescue CgminerApiClient::Error`
381
+ now works.
382
+
383
+ ## [0.2.6] - earlier
384
+
385
+ See git history for changes prior to 0.3.0.
data/README.md CHANGED
@@ -1,28 +1,15 @@
1
- # CgminerApiClient [![Codacy Badge](https://api.codacy.com/project/badge/Grade/b59cb27aeae64f7c992f7072c5909788)](https://app.codacy.com/app/justin_28/cgminer_api_client?utm_source=github.com&utm_medium=referral&utm_content=jramos/cgminer_api_client&utm_campaign=Badge_Grade_Dashboard) [![Build Status](https://travis-ci.org/jramos/cgminer_api_client.png?branch=master)](https://travis-ci.org/jramos/cgminer_api_client) [![DepShield Badge](https://depshield.sonatype.org/badges/jramos/cgminer_api_client/depshield.svg)](https://depshield.github.io)
1
+ # CgminerApiClient
2
2
 
3
- A gem that allows sending API commands to a pool of cgminer instances.
3
+ [![CI](https://github.com/jramos/cgminer_api_client/actions/workflows/ci.yml/badge.svg)](https://github.com/jramos/cgminer_api_client/actions/workflows/ci.yml)
4
4
 
5
- ## Requirements
6
-
7
- - Ruby (~> 2.0.0, ~> 2.1.0)
8
- - YAML
9
- - JSON
10
- - Socket
11
- - Thread
12
- - cgminer (~> 3.12.0)
5
+ A gem that allows sending API commands to a pool of [cgminer](https://github.com/ckolivas/cgminer) instances. Ships as both a Ruby library and a CLI (`cgminer_api_client <command>`). Zero runtime dependencies beyond the Ruby standard library.
13
6
 
14
- ## GUI
7
+ ## Requirements
15
8
 
16
- - <https://github.com/jramos/cgminer_manager>
9
+ Ruby 3.2 or higher.
17
10
 
18
11
  ## Installation Options
19
12
 
20
- ### Bundler
21
-
22
- Add the following to your `Gemfile`:
23
-
24
- gem 'cgminer_api_client', '~> 0.2.6'
25
-
26
13
  ### RubyGems
27
14
 
28
15
  $ gem install cgminer_api_client
@@ -32,18 +19,18 @@ Add the following to your `Gemfile`:
32
19
  $ git clone git@github.com:jramos/cgminer_api_client.git
33
20
  $ cd cgminer_api_client
34
21
  $ gem build cgminer_api_client.gemspec
35
- $ gem install cgminer_api_client-0.2.6.gem
22
+ $ gem install cgminer_api_client-<VERSION>.gem
36
23
 
37
24
  ## Configuration
38
25
 
39
26
  Copy [`config/miners.yml.example`](https://github.com/jramos/cgminer_api_client/blob/master/config/miners.yml.example) to `config/miners.yml` and update with the IP addresses (and optional ports and timeouts) of your cgminer instances. E.g.:
40
27
 
41
- # connect to localhost on the default port (4028) with the default timeout (5 seconds)
28
+ # connect to localhost on default port (4028) with default timeout (5 seconds)
42
29
  - host: 127.0.0.1
43
- # connect to 192.168.1.1 on a non-standard port (1234) with a custom timeout (1 second)
30
+ # connect to 192.168.1.1 on port (1234) with custom timeout (3 seconds)
44
31
  - host: 192.168.1.1
45
32
  port: 1234
46
- timeout: 1
33
+ timeout: 3
47
34
 
48
35
  ### Remote API Access
49
36
 
@@ -75,35 +62,122 @@ Restart cgminer:
75
62
 
76
63
  ## Gem Usage
77
64
 
78
- require 'cgminer_api_client'
65
+ ```ruby
66
+ require 'cgminer_api_client'
67
+
68
+ # Change the defaults for any miners whose config doesn't set them.
69
+ CgminerApiClient.config do |config|
70
+ config.default_port = 4028
71
+ config.default_timeout = 3
72
+ end
73
+
74
+ pool = CgminerApiClient::MinerPool.new
75
+ ```
76
+
77
+ ### Pool queries return a `PoolResult`
78
+
79
+ Every pool query returns a `PoolResult` — an `Enumerable` wrapper
80
+ around one `MinerResult` per miner, in pool order. Each
81
+ `MinerResult` is either a success (carrying a parsed value) or a
82
+ failure (carrying the exception). Callers choose how much detail
83
+ they care about.
84
+
85
+ ```ruby
86
+ # Just give me the data, ignore failures:
87
+ pool.summary.values.each do |s|
88
+ puts "hashrate: #{s[:mhs_av]}"
89
+ end
90
+
91
+ # Handle successes and failures explicitly:
92
+ pool.summary.each do |result|
93
+ if result.ok?
94
+ puts "#{result.miner.host}: #{result.value[:mhs_av]}"
95
+ else
96
+ warn "#{result.miner.host}: #{result.error.message}"
97
+ end
98
+ end
99
+
100
+ # Quick checks:
101
+ pool.summary.all_successful? # true if every miner responded
102
+ pool.summary.any_failed? # true if any miner failed
103
+ pool.summary.errors # [<ConnectionError>, ...]
104
+ pool.summary['10.0.0.5:4028'] # lookup by host:port string
105
+ ```
106
+
107
+ ### Single-miner access
108
+
109
+ If you want to talk to one specific miner without the pool
110
+ wrapping, use `Miner` directly. Unreachable miners raise
111
+ `CgminerApiClient::ConnectionError`:
112
+
113
+ ```ruby
114
+ miner = CgminerApiClient::Miner.new('10.0.0.5', 4028)
115
+ begin
116
+ puts miner.summary[:mhs_av]
117
+ rescue CgminerApiClient::ConnectionError => e
118
+ warn "miner unreachable: #{e.message}"
119
+ end
120
+ ```
121
+
122
+ ### Privileged commands
123
+
124
+ Commands like `restart`, `quit`, `save`, `addpool`, `removepool`,
125
+ `ascset`, etc. require privileged API access on the cgminer side.
126
+ They propagate `CgminerApiClient::ApiError` on rejection and
127
+ `CgminerApiClient::ConnectionError` on network failure — two
128
+ distinct conditions, unlike in 0.2.x where they were conflated.
129
+
130
+ ```ruby
131
+ pool.restart # PoolResult of per-miner outcomes
132
+ ```
133
+
134
+ ### Errors
135
+
136
+ All gem-specific errors descend from `CgminerApiClient::Error < StandardError`:
137
+
138
+ - `CgminerApiClient::ConnectionError` — transport-level: the miner
139
+ was unreachable (DNS failure, connection refused, etc.).
140
+ - `CgminerApiClient::TimeoutError` — a `ConnectionError` subclass
141
+ for connect timeouts specifically.
142
+ - `CgminerApiClient::ApiError` — protocol-level: the miner answered
143
+ and returned a `STATUS=E`/`F` response.
144
+
145
+ `rescue CgminerApiClient::Error` catches everything gem-specific.
146
+ `rescue CgminerApiClient::ConnectionError` catches both generic
147
+ transport failures and connect timeouts. A `MinerPool` query never
148
+ raises per-miner errors — they land on the corresponding
149
+ `MinerResult.failure` inside the returned `PoolResult`.
79
150
 
80
- # change the default timeout and port for the miners
81
- CgminerApiClient.config do |config|
82
- config.default_port = 4023
83
- config.default_timeout = 3
84
- end
151
+ ## CLI Usage
85
152
 
86
- pool = CgminerApiClient::MinerPool.new
153
+ API commands can be sent to your miner pool from the command line.
87
154
 
88
- # run 'devs' on each miner in the pool; returns an array of response hashes
89
- devices = pool.devs
155
+ $ cgminer_api_client <command> (<arguments>)
90
156
 
91
- # run 'summary' on each miner in the pool; returns an array of response hashes
92
- summaries = pool.summary
157
+ ### Exit Codes and Streams
93
158
 
94
- # run 'devs' on available miners; returns an array of response hashes
95
- pool.available_miners.collect do |miner|
96
- miner.devs
97
- end
159
+ - exit `0` at least one miner's command succeeded.
160
+ - exit `1` — every miner failed, or a top-level exception bubbled up.
161
+ - exit `64` — unknown command or missing command argument (`EX_USAGE`).
98
162
 
99
- # restart the pool
100
- pool.restart
163
+ Per-miner responses are printed to stdout with a `host:port:` header.
164
+ Per-miner errors are printed to stderr as
165
+ `host:port: ErrorClass: message`. Set `DEBUG=1` to also print full
166
+ backtraces for any top-level exception:
101
167
 
102
- ## CLI Usage
168
+ $ DEBUG=1 cgminer_api_client summary
103
169
 
104
- API commands can be sent to your miner pool from the command line.
170
+ Pass `-v` / `--verbose` to log the JSON request and raw response to
171
+ stderr for wire-level debugging. Each line carries a `host:port`
172
+ prefix so fan-out across multiple miners stays grep-able:
105
173
 
106
- $ cgminer_api_client <command> (<arguments>)
174
+ $ cgminer_api_client -v summary
175
+ >>> 10.0.0.1:4028 {"command":"summary"}
176
+ <<< 10.0.0.1:4028 {"STATUS":[{"STATUS":"S",...}],"SUMMARY":[...]}
177
+
178
+ Password-bearing arguments to `addpool`, `setconfig`, `ascset`, and
179
+ `pgaset` are replaced with `[REDACTED]` in the log output (the real
180
+ value is still sent on the wire).
107
181
 
108
182
  ### Commands & Arguments
109
183
 
@@ -169,6 +243,13 @@ The following privileged miner and pool commands are currently available:
169
243
 
170
244
  Any cgminer API commands not explictly defined above are implemented using `method_missing`. A complete list of available API commands and options can be found in the [cgminer API-README](https://github.com/ckolivas/cgminer/blob/master/API-README).
171
245
 
246
+ ## Further Reading
247
+
248
+ - [`CHANGELOG.md`](CHANGELOG.md) — release history and the 0.2.x → 0.3.0 migration guide.
249
+ - [`AGENTS.md`](AGENTS.md) — context for AI coding assistants; also a useful conventions-and-extension guide for human contributors.
250
+ - [`docs/`](docs/) — topic-split deep dives on architecture, components, interfaces, data models, workflows, and dependencies. Start with [`docs/index.md`](docs/index.md).
251
+ - [cgminer API-README](https://github.com/ckolivas/cgminer/blob/master/API-README) — upstream documentation for the JSON API surface this gem wraps.
252
+
172
253
  ## Contributing
173
254
 
174
255
  1. Fork it ( <https://github.com/jramos/cgminer_api_client/fork> )
@@ -181,7 +262,7 @@ Any cgminer API commands not explictly defined above are implemented using `meth
181
262
 
182
263
  If you find this gem useful, please consider donating.
183
264
 
184
- BTC: `18HFFqZv2KJMHPNwPes839PJd5GZc4cT3U`
265
+ BTC: `bc1q00genlpcpcglgd4rezqcurf4t4taz0acmm9vea`
185
266
 
186
267
  ## License
187
268