Class: Xshellz::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/xshellz/client.rb

Overview

Bearer-authenticated JSON client for the xShellz control plane (+https://api.xshellz.com/v1+) with typed error mapping.

Defined Under Namespace

Classes: Response

Constant Summary collapse

DEFAULT_API_URL =
"https://api.xshellz.com/v1"
API_KEY_ENV =
"XSHELLZ_API_KEY"
API_URL_ENV =
"XSHELLZ_API_URL"
QUOTA_FRAGMENTS =

403 message fragments emitted by the control plane's guard chain. All guards abort(403, message); quota/entitlement are distinguished by message text.

[
  "agent shell limit", # "You've reached your plan's agent shell limit (N)."
  "plan does not include agent shells" # entitlement gate
].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_key: nil, api_url: nil, http: nil) ⇒ Client

Returns a new instance of Client.



53
54
55
56
57
# File 'lib/xshellz/client.rb', line 53

def initialize(api_key: nil, api_url: nil, http: nil)
  @api_key = self.class.resolve_api_key(api_key)
  @api_url = self.class.resolve_api_url(api_url)
  @http = http || NetHttpAdapter.new
end

Instance Attribute Details

#api_urlObject (readonly)

Returns the value of attribute api_url.



51
52
53
# File 'lib/xshellz/client.rb', line 51

def api_url
  @api_url
end

Class Method Details

.resolve_api_key(api_key = nil) ⇒ Object

Resolve the API key (explicit argument > environment) or raise a helpful AuthError.

Raises:



61
62
63
64
65
66
67
68
69
70
# File 'lib/xshellz/client.rb', line 61

def self.resolve_api_key(api_key = nil)
  key = (api_key || ENV[API_KEY_ENV] || "").strip
  return key unless key.empty?

  raise AuthError,
        "No xShellz API key found. Pass api_key: ... or set the " \
        "#{API_KEY_ENV} environment variable. Create a personal access " \
        "token with `read` and `write` scopes from your xShellz dashboard " \
        "(Settings -> API tokens) or via POST /v1/auth/tokens."
end

.resolve_api_url(api_url = nil) ⇒ Object

Resolve the API base URL (explicit argument > environment > default), with no trailing slash.



74
75
76
77
78
79
# File 'lib/xshellz/client.rb', line 74

def self.resolve_api_url(api_url = nil)
  url = api_url || ENV[API_URL_ENV] || DEFAULT_API_URL
  url = url.strip
  url = DEFAULT_API_URL if url.empty?
  url.sub(%r{/+\z}, "")
end

Instance Method Details

#delete(path) ⇒ Object



93
94
95
# File 'lib/xshellz/client.rb', line 93

def delete(path)
  request("DELETE", path)
end

#get(path) ⇒ Object



81
82
83
# File 'lib/xshellz/client.rb', line 81

def get(path)
  request("GET", path)
end

#post(path, body = nil) ⇒ Object



85
86
87
# File 'lib/xshellz/client.rb', line 85

def post(path, body = nil)
  request("POST", path, body)
end

#put(path, body = nil) ⇒ Object



89
90
91
# File 'lib/xshellz/client.rb', line 89

def put(path, body = nil)
  request("PUT", path, body)
end

#request(method, path, body = nil) ⇒ Object

Issue a request; return the parsed JSON body or raise a typed error.



98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/xshellz/client.rb', line 98

def request(method, path, body = nil)
  headers = {
    "Authorization" => "Bearer #{@api_key}",
    "Accept" => "application/json",
    "User-Agent" => "xshellz-ruby/#{VERSION}"
  }
  headers["Content-Type"] = "application/json" unless body.nil?

  response = @http.call(method, "#{@api_url}#{path}", headers, body.nil? ? nil : JSON.generate(body))
  raise map_error(response) if response.status >= 400
  return nil if response.body.nil? || response.body.empty?

  JSON.parse(response.body)
end