xapixctl 1.1.2 → 1.2.4
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/.github/workflows/cd.yaml +17 -0
- data/.ruby-version +1 -1
- data/Gemfile.lock +55 -37
- data/lib/xapixctl.rb +0 -3
- data/lib/xapixctl/base_cli.rb +47 -35
- data/lib/xapixctl/cli.rb +51 -66
- data/lib/xapixctl/connector_cli.rb +49 -0
- data/lib/xapixctl/phoenix_client.rb +37 -259
- data/lib/xapixctl/phoenix_client/connection.rb +50 -0
- data/lib/xapixctl/phoenix_client/organization_connection.rb +61 -0
- data/lib/xapixctl/phoenix_client/project_connection.rb +184 -0
- data/lib/xapixctl/phoenix_client/result_handler.rb +35 -0
- data/lib/xapixctl/preview_cli.rb +7 -19
- data/lib/xapixctl/sync_cli.rb +241 -0
- data/lib/xapixctl/titan_cli.rb +286 -0
- data/lib/xapixctl/util.rb +42 -0
- data/lib/xapixctl/version.rb +1 -1
- data/xapixctl.gemspec +12 -4
- metadata +83 -12
@@ -0,0 +1,286 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'xapixctl/base_cli'
|
4
|
+
|
5
|
+
module Xapixctl
|
6
|
+
class TitanCli < BaseCli
|
7
|
+
DEFAULT_METHODS = {
|
8
|
+
"predict" => :predict,
|
9
|
+
"performance" => :performance,
|
10
|
+
}.freeze
|
11
|
+
|
12
|
+
option :schema_import, desc: "Resource id of an existing Schema import"
|
13
|
+
option :data, desc: "JSON encoded data for predict API", default: "[[1,2,3]]"
|
14
|
+
desc "service URL", "build a service for the deployed model"
|
15
|
+
long_desc <<-LONGDESC
|
16
|
+
`xapixctl titan service URL` will build a ML service around a deployed ML model.
|
17
|
+
|
18
|
+
We expect the following of the deployed ML model:
|
19
|
+
|
20
|
+
- There should be a "POST /predict" endpoint. Use --data="JSON" to specify an example dataset the model expects.
|
21
|
+
|
22
|
+
- If there is a "GET /performance" endpoint, it'll be made available. It's expected to return a JSON object with the properties 'accuracy', 'precision', 'recall', and 'min_acc_threshold'.
|
23
|
+
|
24
|
+
Examples:
|
25
|
+
\x5> $ xapixctl titan service https://services.demo.akoios.com/ai-model-name -p xapix/ml-project
|
26
|
+
LONGDESC
|
27
|
+
def service(akoios_url)
|
28
|
+
url = URI.parse(akoios_url)
|
29
|
+
schema = JSON.parse(RestClient.get(File.join(url.to_s, 'spec'), params: { host: url.hostname }, headers: { accept: :json }))
|
30
|
+
patch_schema(schema)
|
31
|
+
connector_refs = import_swagger(File.basename(url.path), schema)
|
32
|
+
say "\n== Onboarding Connectors", :bold
|
33
|
+
connectors = match_connectors_to_action(connector_refs)
|
34
|
+
if connectors.empty?
|
35
|
+
warn "\nNo valid connectors for ML service detected, not building service."
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
say "\n== Building Service", :bold
|
39
|
+
service_doc = build_service(schema.dig('info', 'title'), connectors)
|
40
|
+
res = prj_connection.apply(service_doc)
|
41
|
+
say "\ncreated / updated service #{res.first}"
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def patch_schema(schema)
|
47
|
+
predict_schema = schema.dig('paths', '/predict', 'post')
|
48
|
+
if predict_schema
|
49
|
+
predict_data = JSON.parse(options[:data]) rescue {}
|
50
|
+
predict_schema['operationId'] = 'predict'
|
51
|
+
predict_schema['parameters'].each do |param|
|
52
|
+
if param['name'] == 'json' && param['in'] == 'body'
|
53
|
+
param['schema']['properties'] = { "data" => extract_schema(predict_data) }
|
54
|
+
param['schema']['example'] = { "data" => predict_data }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
performane_schema = schema.dig('paths', '/performance', 'get')
|
60
|
+
if performane_schema
|
61
|
+
performane_schema['operationId'] = 'performance'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def import_swagger(filename, schema)
|
66
|
+
Tempfile.create([filename, '.json']) do |f|
|
67
|
+
f.write(schema.to_json)
|
68
|
+
f.rewind
|
69
|
+
|
70
|
+
if options[:schema_import]
|
71
|
+
result = prj_connection.update_schema_import(options[:schema_import], f)
|
72
|
+
say "updated #{result.dig('resource', 'kind')} #{result.dig('resource', 'id')}"
|
73
|
+
else
|
74
|
+
result = prj_connection.add_schema_import(f)
|
75
|
+
say "created #{result.dig('resource', 'kind')} #{result.dig('resource', 'id')}"
|
76
|
+
end
|
77
|
+
result.dig('schema_import', 'updated_resources')
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def match_connectors_to_action(connector_refs)
|
82
|
+
connector_refs.map do |connector_ref|
|
83
|
+
connector = prj_connection.resource(connector_ref['kind'], connector_ref['id'])
|
84
|
+
action = DEFAULT_METHODS[connector.dig('definition', 'name')]
|
85
|
+
say "\n#{connector['kind']} #{connector.dig('definition', 'name')} -> "
|
86
|
+
if action
|
87
|
+
say "#{action} action"
|
88
|
+
updated_connector = update_connector_with_preview(connector)
|
89
|
+
[action, updated_connector] if updated_connector
|
90
|
+
else
|
91
|
+
say "no action type detected, ignoring"
|
92
|
+
nil
|
93
|
+
end
|
94
|
+
end.compact
|
95
|
+
end
|
96
|
+
|
97
|
+
def update_connector_with_preview(connector)
|
98
|
+
say "fetching preview for #{connector['kind']} #{connector.dig('definition', 'name')}..."
|
99
|
+
preview_details = prj_connection.data_source_preview(connector.dig('metadata', 'id'))
|
100
|
+
preview = preview_details.dig('preview', 'sample')
|
101
|
+
say "got a #{preview['status']} response: #{preview['body']}"
|
102
|
+
if preview['status'] != 200
|
103
|
+
say "unexpected status, please check data or model"
|
104
|
+
elsif yes?("Does this look alright?", :bold)
|
105
|
+
res = prj_connection.accept_data_source_preview(connector.dig('metadata', 'id'))
|
106
|
+
return res.dig('data_source', 'resource_description')
|
107
|
+
end
|
108
|
+
nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def extract_schema(data_sample)
|
112
|
+
case data_sample
|
113
|
+
when Array
|
114
|
+
{ type: 'array', items: extract_schema(data_sample[0]) }
|
115
|
+
when Hash
|
116
|
+
{ type: 'object', properties: data_sample.transform_values { |v| extract_schema(v) } }
|
117
|
+
when Numeric
|
118
|
+
{ type: 'number' }
|
119
|
+
else
|
120
|
+
{}
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def build_service(title, connectors)
|
125
|
+
{
|
126
|
+
version: 'v1',
|
127
|
+
kind: 'Service',
|
128
|
+
metadata: { id: title.parameterize },
|
129
|
+
definition: {
|
130
|
+
name: title.humanize,
|
131
|
+
actions: connectors.map { |action, connector| build_service_action(action, connector) }
|
132
|
+
}
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
def build_service_action(action_type, connector)
|
137
|
+
{
|
138
|
+
name: action_type,
|
139
|
+
parameter_schema: parameter_schema(action_type, connector),
|
140
|
+
result_schema: result_schema(action_type),
|
141
|
+
pipeline: { units: pipeline_units(action_type, connector) }
|
142
|
+
}
|
143
|
+
end
|
144
|
+
|
145
|
+
def parameter_schema(action_type, connector)
|
146
|
+
case action_type
|
147
|
+
when :predict
|
148
|
+
{ type: 'object', properties: {
|
149
|
+
data: connector.dig('definition', 'parameter_schema', 'properties', 'body', 'properties', 'data'),
|
150
|
+
} }
|
151
|
+
when :performance
|
152
|
+
{ type: 'object', properties: {} }
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def result_schema(action_type)
|
157
|
+
case action_type
|
158
|
+
when :predict
|
159
|
+
{ type: 'object', properties: {
|
160
|
+
prediction: { type: 'object', properties: { percent: { type: 'number' }, raw: { type: 'number' } } },
|
161
|
+
success: { type: 'boolean' },
|
162
|
+
error: { type: 'string' }
|
163
|
+
} }
|
164
|
+
when :performance
|
165
|
+
{ type: 'object', properties: {
|
166
|
+
performance: {
|
167
|
+
type: 'object', properties: {
|
168
|
+
accuracy: { type: 'number' },
|
169
|
+
precision: { type: 'number' },
|
170
|
+
recall: { type: 'number' },
|
171
|
+
min_acc_threshold: { type: 'number' }
|
172
|
+
}
|
173
|
+
}
|
174
|
+
} }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def pipeline_units(action_type, connector)
|
179
|
+
case action_type
|
180
|
+
when :predict
|
181
|
+
[entry_unit, predict_unit(connector), predict_result_unit]
|
182
|
+
when :performance
|
183
|
+
[entry_unit, performance_unit(connector), performance_result_unit]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def entry_unit
|
188
|
+
{ version: 'v2', kind: 'Unit/Entry',
|
189
|
+
metadata: { id: 'entry' },
|
190
|
+
definition: { name: 'Entry' } }
|
191
|
+
end
|
192
|
+
|
193
|
+
def predict_unit(connector)
|
194
|
+
leafs = extract_leafs(connector.dig('definition', 'parameter_schema', 'properties', 'body'))
|
195
|
+
{ version: 'v2', kind: 'Unit/DataSource',
|
196
|
+
metadata: { id: 'predict' },
|
197
|
+
definition: {
|
198
|
+
name: 'Predict',
|
199
|
+
inputs: ['entry'],
|
200
|
+
data_source: connector.dig('metadata', 'id'),
|
201
|
+
formulas: leafs.map { |leaf|
|
202
|
+
{ ref: leaf[:node]['$id'], formula: (['', 'entry'] + leaf[:path]).join('.') }
|
203
|
+
}
|
204
|
+
} }
|
205
|
+
end
|
206
|
+
|
207
|
+
def predict_result_unit
|
208
|
+
{ version: 'v2', kind: 'Unit/Result',
|
209
|
+
metadata: { id: 'result' },
|
210
|
+
definition: {
|
211
|
+
name: 'Result',
|
212
|
+
inputs: ['predict'],
|
213
|
+
formulas: [{
|
214
|
+
ref: "#prediction.percent",
|
215
|
+
formula: "IF(.predict.status = 200, ROUND(100*coerce.to-float(.predict.body), 2))"
|
216
|
+
}, {
|
217
|
+
ref: "#prediction.raw",
|
218
|
+
formula: "IF(.predict.status = 200, coerce.to-float(.predict.body))"
|
219
|
+
}, {
|
220
|
+
ref: "#success",
|
221
|
+
formula: ".predict.status = 200"
|
222
|
+
}, {
|
223
|
+
ref: "#error",
|
224
|
+
formula: "IF(.predict.status <> 200, 'Model not trained!')"
|
225
|
+
}],
|
226
|
+
parameter_sample: {
|
227
|
+
"prediction" => { "percent" => 51.12, "raw" => 0.5112131 },
|
228
|
+
"success" => true,
|
229
|
+
"error" => nil
|
230
|
+
}
|
231
|
+
} }
|
232
|
+
end
|
233
|
+
|
234
|
+
def performance_unit(connector)
|
235
|
+
{ version: 'v2', kind: 'Unit/DataSource',
|
236
|
+
metadata: { id: 'performance' },
|
237
|
+
definition: {
|
238
|
+
name: 'Performance',
|
239
|
+
inputs: ['entry'],
|
240
|
+
data_source: connector.dig('metadata', 'id')
|
241
|
+
} }
|
242
|
+
end
|
243
|
+
|
244
|
+
def performance_result_unit
|
245
|
+
{ version: 'v2', kind: 'Unit/Result',
|
246
|
+
metadata: { id: 'result' },
|
247
|
+
definition: {
|
248
|
+
name: 'Result',
|
249
|
+
inputs: ['performance'],
|
250
|
+
formulas: [{
|
251
|
+
ref: "#performance.recall",
|
252
|
+
formula: "WITH(data, JSON.DECODE(REGEXREPLACE(performance.body, \"'\", \"\\\"\")), .data.recall)"
|
253
|
+
}, {
|
254
|
+
ref: "#performance.accuracy",
|
255
|
+
formula: "WITH(data, JSON.DECODE(REGEXREPLACE(performance.body, \"'\", \"\\\"\")), .data.accuracy)"
|
256
|
+
}, {
|
257
|
+
ref: "#performance.precision",
|
258
|
+
formula: "WITH(data, JSON.DECODE(REGEXREPLACE(performance.body, \"'\", \"\\\"\")), .data.precision)"
|
259
|
+
}, {
|
260
|
+
ref: "#performance.min_acc_threshold",
|
261
|
+
formula: "WITH(data, JSON.DECODE(REGEXREPLACE(performance.body, \"'\", \"\\\"\")), .data.min_acc_threshold)"
|
262
|
+
}],
|
263
|
+
parameter_sample: {
|
264
|
+
"performance" => {
|
265
|
+
"recall" => 0.8184713375796179,
|
266
|
+
"accuracy" => 0.9807847896440129,
|
267
|
+
"precision" => 0.8711864406779661,
|
268
|
+
"min_acc_threshold" => 0.84,
|
269
|
+
}
|
270
|
+
}
|
271
|
+
} }
|
272
|
+
end
|
273
|
+
|
274
|
+
def extract_leafs(schema, current_path = [])
|
275
|
+
return unless schema
|
276
|
+
case schema['type']
|
277
|
+
when 'object'
|
278
|
+
schema['properties'].flat_map { |key, sub_schema| extract_leafs(sub_schema, current_path + [key]) }.compact
|
279
|
+
when 'array'
|
280
|
+
extract_leafs(schema['items'], current_path + [:[]])
|
281
|
+
else
|
282
|
+
{ path: current_path, node: schema }
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module Xapixctl
|
6
|
+
module Util
|
7
|
+
extend self
|
8
|
+
|
9
|
+
class InvalidDocumentStructureError < StandardError
|
10
|
+
def initialize(file)
|
11
|
+
super("#{file} has invalid document structure")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
DOCUMENT_STRUCTURE = %w[version kind metadata definition].freeze
|
16
|
+
|
17
|
+
def resources_from_file(filename, ignore_missing: false)
|
18
|
+
load_files(filename, ignore_missing) do |actual_file, yaml_string|
|
19
|
+
yaml_string.split(/^---\s*\n/).map { |yml| Psych.safe_load(yml) }.compact.each do |doc|
|
20
|
+
raise InvalidDocumentStructureError, actual_file unless (DOCUMENT_STRUCTURE - doc.keys.map(&:to_s)).empty?
|
21
|
+
yield doc
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_files(filename, ignore_missing)
|
27
|
+
if filename == '-'
|
28
|
+
yield 'STDIN', $stdin.read
|
29
|
+
else
|
30
|
+
pn = filename.is_a?(Pathname) ? filename : Pathname.new(filename)
|
31
|
+
if pn.directory?
|
32
|
+
pn.glob(["**/*.yaml", "**/*.yml"]).sort.each { |dpn| yield dpn.to_s, dpn.read }
|
33
|
+
elsif pn.exist?
|
34
|
+
yield pn.to_s, pn.read
|
35
|
+
elsif !ignore_missing
|
36
|
+
warn "file not found: #{filename}"
|
37
|
+
exit 1
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/xapixctl/version.rb
CHANGED
data/xapixctl.gemspec
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
lib = File.expand_path("lib", __dir__)
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
5
|
require "xapixctl/version"
|
@@ -24,13 +26,19 @@ Gem::Specification.new do |spec|
|
|
24
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
25
27
|
spec.require_paths = ["lib"]
|
26
28
|
|
29
|
+
spec.required_ruby_version = '>= 2.6'
|
30
|
+
|
27
31
|
spec.add_dependency "activesupport", ">= 5.2.3", "< 6.0.0"
|
32
|
+
spec.add_dependency "hashdiff", ">= 1.0.1", "< 1.2.0"
|
28
33
|
spec.add_dependency "rest-client", ">= 2.1.0", "< 3.0.0"
|
29
|
-
spec.add_dependency "thor", ">= 1.0.0", "< 1.
|
34
|
+
spec.add_dependency "thor", ">= 1.0.0", "< 1.2.0"
|
30
35
|
|
31
|
-
spec.add_development_dependency "bundler", "~> 1.
|
36
|
+
spec.add_development_dependency "bundler", "~> 2.1.4"
|
32
37
|
spec.add_development_dependency "rake", "~> 13.0"
|
33
38
|
spec.add_development_dependency "relaxed-rubocop", "~> 2.5"
|
34
|
-
spec.add_development_dependency "rspec", "~> 3.0"
|
35
|
-
spec.add_development_dependency "rubocop", "~> 0
|
39
|
+
spec.add_development_dependency "rspec", "~> 3.10.0"
|
40
|
+
spec.add_development_dependency "rubocop", "~> 1.11.0"
|
41
|
+
spec.add_development_dependency "rubocop-rake", "~> 0.5.1"
|
42
|
+
spec.add_development_dependency "rubocop-rspec", "~> 2.2.0"
|
43
|
+
spec.add_development_dependency "webmock", "~> 3.11.0"
|
36
44
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xapixctl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Michael Reinsch
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-06-30 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -30,6 +30,26 @@ dependencies:
|
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 6.0.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: hashdiff
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 1.0.1
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.2.0
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 1.0.1
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.2.0
|
33
53
|
- !ruby/object:Gem::Dependency
|
34
54
|
name: rest-client
|
35
55
|
requirement: !ruby/object:Gem::Requirement
|
@@ -59,7 +79,7 @@ dependencies:
|
|
59
79
|
version: 1.0.0
|
60
80
|
- - "<"
|
61
81
|
- !ruby/object:Gem::Version
|
62
|
-
version: 1.
|
82
|
+
version: 1.2.0
|
63
83
|
type: :runtime
|
64
84
|
prerelease: false
|
65
85
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -69,21 +89,21 @@ dependencies:
|
|
69
89
|
version: 1.0.0
|
70
90
|
- - "<"
|
71
91
|
- !ruby/object:Gem::Version
|
72
|
-
version: 1.
|
92
|
+
version: 1.2.0
|
73
93
|
- !ruby/object:Gem::Dependency
|
74
94
|
name: bundler
|
75
95
|
requirement: !ruby/object:Gem::Requirement
|
76
96
|
requirements:
|
77
97
|
- - "~>"
|
78
98
|
- !ruby/object:Gem::Version
|
79
|
-
version: 1.
|
99
|
+
version: 2.1.4
|
80
100
|
type: :development
|
81
101
|
prerelease: false
|
82
102
|
version_requirements: !ruby/object:Gem::Requirement
|
83
103
|
requirements:
|
84
104
|
- - "~>"
|
85
105
|
- !ruby/object:Gem::Version
|
86
|
-
version: 1.
|
106
|
+
version: 2.1.4
|
87
107
|
- !ruby/object:Gem::Dependency
|
88
108
|
name: rake
|
89
109
|
requirement: !ruby/object:Gem::Requirement
|
@@ -118,28 +138,70 @@ dependencies:
|
|
118
138
|
requirements:
|
119
139
|
- - "~>"
|
120
140
|
- !ruby/object:Gem::Version
|
121
|
-
version:
|
141
|
+
version: 3.10.0
|
122
142
|
type: :development
|
123
143
|
prerelease: false
|
124
144
|
version_requirements: !ruby/object:Gem::Requirement
|
125
145
|
requirements:
|
126
146
|
- - "~>"
|
127
147
|
- !ruby/object:Gem::Version
|
128
|
-
version:
|
148
|
+
version: 3.10.0
|
129
149
|
- !ruby/object:Gem::Dependency
|
130
150
|
name: rubocop
|
131
151
|
requirement: !ruby/object:Gem::Requirement
|
132
152
|
requirements:
|
133
153
|
- - "~>"
|
134
154
|
- !ruby/object:Gem::Version
|
135
|
-
version:
|
155
|
+
version: 1.11.0
|
136
156
|
type: :development
|
137
157
|
prerelease: false
|
138
158
|
version_requirements: !ruby/object:Gem::Requirement
|
139
159
|
requirements:
|
140
160
|
- - "~>"
|
141
161
|
- !ruby/object:Gem::Version
|
142
|
-
version:
|
162
|
+
version: 1.11.0
|
163
|
+
- !ruby/object:Gem::Dependency
|
164
|
+
name: rubocop-rake
|
165
|
+
requirement: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - "~>"
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: 0.5.1
|
170
|
+
type: :development
|
171
|
+
prerelease: false
|
172
|
+
version_requirements: !ruby/object:Gem::Requirement
|
173
|
+
requirements:
|
174
|
+
- - "~>"
|
175
|
+
- !ruby/object:Gem::Version
|
176
|
+
version: 0.5.1
|
177
|
+
- !ruby/object:Gem::Dependency
|
178
|
+
name: rubocop-rspec
|
179
|
+
requirement: !ruby/object:Gem::Requirement
|
180
|
+
requirements:
|
181
|
+
- - "~>"
|
182
|
+
- !ruby/object:Gem::Version
|
183
|
+
version: 2.2.0
|
184
|
+
type: :development
|
185
|
+
prerelease: false
|
186
|
+
version_requirements: !ruby/object:Gem::Requirement
|
187
|
+
requirements:
|
188
|
+
- - "~>"
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
version: 2.2.0
|
191
|
+
- !ruby/object:Gem::Dependency
|
192
|
+
name: webmock
|
193
|
+
requirement: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - "~>"
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: 3.11.0
|
198
|
+
type: :development
|
199
|
+
prerelease: false
|
200
|
+
version_requirements: !ruby/object:Gem::Requirement
|
201
|
+
requirements:
|
202
|
+
- - "~>"
|
203
|
+
- !ruby/object:Gem::Version
|
204
|
+
version: 3.11.0
|
143
205
|
description:
|
144
206
|
email:
|
145
207
|
- michael@xapix.io
|
@@ -148,6 +210,7 @@ executables:
|
|
148
210
|
extensions: []
|
149
211
|
extra_rdoc_files: []
|
150
212
|
files:
|
213
|
+
- ".github/workflows/cd.yaml"
|
151
214
|
- ".gitignore"
|
152
215
|
- ".rspec"
|
153
216
|
- ".rubocop.yml"
|
@@ -163,8 +226,16 @@ files:
|
|
163
226
|
- lib/xapixctl.rb
|
164
227
|
- lib/xapixctl/base_cli.rb
|
165
228
|
- lib/xapixctl/cli.rb
|
229
|
+
- lib/xapixctl/connector_cli.rb
|
166
230
|
- lib/xapixctl/phoenix_client.rb
|
231
|
+
- lib/xapixctl/phoenix_client/connection.rb
|
232
|
+
- lib/xapixctl/phoenix_client/organization_connection.rb
|
233
|
+
- lib/xapixctl/phoenix_client/project_connection.rb
|
234
|
+
- lib/xapixctl/phoenix_client/result_handler.rb
|
167
235
|
- lib/xapixctl/preview_cli.rb
|
236
|
+
- lib/xapixctl/sync_cli.rb
|
237
|
+
- lib/xapixctl/titan_cli.rb
|
238
|
+
- lib/xapixctl/util.rb
|
168
239
|
- lib/xapixctl/version.rb
|
169
240
|
- xapixctl.gemspec
|
170
241
|
homepage: https://github.com/xapix-io/xapixctl
|
@@ -181,14 +252,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
181
252
|
requirements:
|
182
253
|
- - ">="
|
183
254
|
- !ruby/object:Gem::Version
|
184
|
-
version: '
|
255
|
+
version: '2.6'
|
185
256
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
186
257
|
requirements:
|
187
258
|
- - ">="
|
188
259
|
- !ruby/object:Gem::Version
|
189
260
|
version: '0'
|
190
261
|
requirements: []
|
191
|
-
rubygems_version: 3.0.
|
262
|
+
rubygems_version: 3.0.9
|
192
263
|
signing_key:
|
193
264
|
specification_version: 4
|
194
265
|
summary: xapix client library and command line tool
|