portage-cli 0.6.0 → 0.7.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/CHANGELOG.md +188 -0
- data/README.md +117 -6
- data/lib/portage/cli/buy.rb +467 -48
- data/lib/portage/cli/buyer_context.rb +42 -0
- data/lib/portage/cli/checkout_handoff.rb +70 -0
- data/lib/portage/cli/confidence_check.rb +127 -0
- data/lib/portage/cli/config.rb +52 -0
- data/lib/portage/cli/decisions.rb +72 -0
- data/lib/portage/cli/doctor.rb +17 -0
- data/lib/portage/cli/find.rb +10 -9
- data/lib/portage/cli/generate/agent_profile.rb +66 -6
- data/lib/portage/cli/history.rb +23 -6
- data/lib/portage/cli/notifier.rb +73 -0
- data/lib/portage/cli/setting.rb +44 -0
- data/lib/portage/cli/version.rb +1 -1
- data/lib/portage/cli.rb +145 -19
- metadata +27 -8
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Portage
|
|
2
|
+
module Cli
|
|
3
|
+
# How portage-cli resolves a standing preference, in one place
|
|
4
|
+
# (docs/plans/checkout-handoff-delivery.md open decision #1): a
|
|
5
|
+
# per-invocation override (a flag) beats its PORTAGE_* env var, which
|
|
6
|
+
# beats its ~/.portage/config.json key (Config). CheckoutHandoff,
|
|
7
|
+
# Notifier, Buy and ConfidenceCheck all read their settings through it.
|
|
8
|
+
#
|
|
9
|
+
# nil or a blank string counts as unset at every level, so an exported
|
|
10
|
+
# but empty `PORTAGE_AUTO_OPEN_CHECKOUT=` falls through to config.json
|
|
11
|
+
# rather than silently meaning "off".
|
|
12
|
+
module Setting
|
|
13
|
+
TRUE_VALUES = %w[1 true yes].freeze
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
# @param override [Object, nil] the flag's value, nil when not passed.
|
|
18
|
+
# @param env [String, nil] the env var's name.
|
|
19
|
+
# @param config [Config, nil]
|
|
20
|
+
# @param config_key [String, nil]
|
|
21
|
+
# @return [Object, nil] the value from the first level that's set.
|
|
22
|
+
def resolve(override: nil, env: nil, config: nil, config_key: nil)
|
|
23
|
+
return override if set?(override)
|
|
24
|
+
|
|
25
|
+
from_env = env && ENV.fetch(env, nil)
|
|
26
|
+
return from_env if set?(from_env)
|
|
27
|
+
|
|
28
|
+
from_config = config_key && config&.get(config_key)
|
|
29
|
+
from_config if set?(from_config)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# The same precedence, read as yes/no: true, or a string in
|
|
33
|
+
# TRUE_VALUES (any case). Anything else, unset included, is no.
|
|
34
|
+
def flag?(**)
|
|
35
|
+
TRUE_VALUES.include?(resolve(**).to_s.strip.downcase)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def set?(value)
|
|
39
|
+
!(value.nil? || (value.is_a?(String) && value.strip.empty?))
|
|
40
|
+
end
|
|
41
|
+
private_class_method :set?
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/portage/cli/version.rb
CHANGED
data/lib/portage/cli.rb
CHANGED
|
@@ -3,6 +3,7 @@ require "json"
|
|
|
3
3
|
|
|
4
4
|
require_relative "cli/version"
|
|
5
5
|
require_relative "cli/shipping_profile"
|
|
6
|
+
require_relative "cli/buyer_context"
|
|
6
7
|
require_relative "cli/catalog_products"
|
|
7
8
|
require_relative "cli/buy"
|
|
8
9
|
require_relative "cli/find"
|
|
@@ -21,7 +22,9 @@ module Portage
|
|
|
21
22
|
module Cli
|
|
22
23
|
USAGE = <<~USAGE.freeze
|
|
23
24
|
usage: portage buy <url> --query "..." [--qty N] [--payment-token TOKEN]
|
|
24
|
-
[--product-id ID] [--yes] [--dry-run]
|
|
25
|
+
[--product-id ID] [--yes] [--dry-run]
|
|
26
|
+
[--auto-open|--no-auto-open] [--notify-webhook URL]
|
|
27
|
+
[--decision-backend jev|laya] [--min-confidence N] [--json]
|
|
25
28
|
portage buy --query "..." [--store URL] [--max-price N] [--limit N] ...
|
|
26
29
|
portage find --query "..." [--max-price N] [--limit N] [--json]
|
|
27
30
|
portage compare <url> --product-id ID [--id VALUE ...] [--results N]
|
|
@@ -41,13 +44,16 @@ module Portage
|
|
|
41
44
|
[--velocity-count N --velocity-window-seconds N]
|
|
42
45
|
[--allow HOST ...] [--clear-allowlist]
|
|
43
46
|
portage doctor [--require FILE] [--adapter CLASS_NAME] [--json]
|
|
47
|
+
portage configure [--require FILE] [--adapter CLASS_NAME] [--json] (alias for doctor)
|
|
48
|
+
portage setup [--require FILE] [--adapter CLASS_NAME] [--json] (alias for doctor)
|
|
44
49
|
portage generate adapter NAME [--dir DIR]
|
|
45
50
|
portage generate agent-profile [--out FILE] [--key-out FILE] [--rotate]
|
|
46
51
|
USAGE
|
|
47
52
|
|
|
48
53
|
COMMANDS = { "buy" => :run_buy, "find" => :run_find, "compare" => :run_compare,
|
|
49
54
|
"history" => :run_history, "payment" => :run_payment, "policy" => :run_policy,
|
|
50
|
-
"doctor" => :run_doctor, "
|
|
55
|
+
"doctor" => :run_doctor, "configure" => :run_doctor, "setup" => :run_doctor,
|
|
56
|
+
"generate" => :run_generate }.freeze
|
|
51
57
|
|
|
52
58
|
# @param argv [Array<String>]
|
|
53
59
|
# @return [Integer] process exit code
|
|
@@ -67,13 +73,18 @@ module Portage
|
|
|
67
73
|
|
|
68
74
|
json = options.delete(:json)
|
|
69
75
|
report = Find.new(**options).call
|
|
70
|
-
|
|
71
|
-
message: report[:message])
|
|
76
|
+
record_find(report)
|
|
72
77
|
puts json ? JSON.pretty_generate(report) : format_find(report)
|
|
73
78
|
report[:offers].any? ? 0 : 1
|
|
74
79
|
end
|
|
75
80
|
private_class_method :run_find
|
|
76
81
|
|
|
82
|
+
def self.record_find(report)
|
|
83
|
+
History.new.record_search(query: report[:query], offer_count: report[:offers].length,
|
|
84
|
+
message: report[:message])
|
|
85
|
+
end
|
|
86
|
+
private_class_method :record_find
|
|
87
|
+
|
|
77
88
|
def self.parse_find_options(argv)
|
|
78
89
|
opts = {}
|
|
79
90
|
find_option_parser(opts).parse!(argv)
|
|
@@ -153,6 +164,8 @@ module Portage
|
|
|
153
164
|
return 1 unless parsed
|
|
154
165
|
|
|
155
166
|
url = parsed[:buy][:url] || parsed[:store]
|
|
167
|
+
parsed[:confidence_check] = confidence_check(parsed, url)
|
|
168
|
+
return 1 unless parsed[:confidence_check]
|
|
156
169
|
return execute_buy(parsed, url) if url
|
|
157
170
|
|
|
158
171
|
buy_from_search(parsed)
|
|
@@ -166,6 +179,7 @@ module Portage
|
|
|
166
179
|
# interactive pick has to name it. Piped/CI runs list the offers and stop.
|
|
167
180
|
def self.buy_from_search(parsed)
|
|
168
181
|
report = Find.new(**parsed[:find]).call
|
|
182
|
+
record_find(report)
|
|
169
183
|
offer = pick_offer(report, parsed[:json])
|
|
170
184
|
return report[:offers].any? ? 0 : 1 unless offer
|
|
171
185
|
|
|
@@ -192,36 +206,84 @@ module Portage
|
|
|
192
206
|
private_class_method :prompt_for_offer
|
|
193
207
|
|
|
194
208
|
def self.execute_buy(parsed, url, product_id: nil)
|
|
195
|
-
options = parsed[:buy].merge(url: url)
|
|
209
|
+
options = parsed[:buy].merge(url: url, confidence_check: parsed[:confidence_check])
|
|
196
210
|
options[:product_id] ||= product_id
|
|
197
211
|
report = Buy.new(**options).call
|
|
198
|
-
|
|
212
|
+
record_buy(report, options[:query])
|
|
199
213
|
puts parsed[:json] ? JSON.pretty_generate(report) : format_report(report)
|
|
200
214
|
report[:checkout] || report[:browse] ? 0 : 1
|
|
201
215
|
end
|
|
202
216
|
private_class_method :execute_buy
|
|
203
217
|
|
|
204
|
-
#
|
|
205
|
-
#
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
218
|
+
# Built before the buy starts (and before the search, when there's no
|
|
219
|
+
# URL), so a bad --min-confidence, or a bad PORTAGE_MIN_CONFIDENCE with
|
|
220
|
+
# a backend enabled, stops the run up front rather than after a
|
|
221
|
+
# checkout already exists.
|
|
222
|
+
def self.confidence_check(parsed, url)
|
|
223
|
+
ConfidenceCheck.new(**parsed[:confidence])
|
|
224
|
+
rescue ArgumentError => e
|
|
225
|
+
invalid_buy_option(e.message, url: url, json: parsed[:json])
|
|
226
|
+
end
|
|
227
|
+
private_class_method :confidence_check
|
|
228
|
+
|
|
229
|
+
# A buy refused before it started. Under --json that's a report like
|
|
230
|
+
# any other, with outcome `invalid_option`, so an agent loop reading
|
|
231
|
+
# stdout gets JSON rather than nothing and a line on stderr.
|
|
232
|
+
# @return [nil]
|
|
233
|
+
def self.invalid_buy_option(message, url:, json:)
|
|
234
|
+
unless json
|
|
235
|
+
warn message
|
|
236
|
+
return nil
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
puts JSON.pretty_generate(url: url, checkout_url: nil, products: [], warnings: [], source: "none",
|
|
240
|
+
outcome: "invalid_option", browse: false, checkout: false, message: message)
|
|
241
|
+
nil
|
|
242
|
+
end
|
|
243
|
+
private_class_method :invalid_buy_option
|
|
244
|
+
|
|
245
|
+
# A buy that created a checkout is a purchase entry, whatever its
|
|
246
|
+
# outcome. One that never got that far (no match, browse-only, dead end,
|
|
247
|
+
# a store or adapter error) is a search at that store, so "what did I
|
|
248
|
+
# already buy" never lists a checkout that doesn't exist.
|
|
249
|
+
def self.record_buy(report, query)
|
|
250
|
+
history = History.new
|
|
251
|
+
unless report[:checkout_id]
|
|
252
|
+
return history.record_search(query: query, url: report[:url], offer_count: report[:products].length,
|
|
253
|
+
message: report[:message])
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
history.record_purchase(
|
|
257
|
+
url: report[:url], query: query, outcome: report[:outcome], source: report[:source],
|
|
258
|
+
checkout_id: report[:checkout_id], checkout_status: report[:checkout_status],
|
|
259
|
+
checkout_url: report[:checkout_url], total: report_total(report), currency: report[:currency],
|
|
260
|
+
items: Array(report[:items]).map { |item| item.transform_keys(&:to_s) }, message: report[:message]
|
|
211
261
|
)
|
|
212
262
|
end
|
|
213
|
-
private_class_method :
|
|
263
|
+
private_class_method :record_buy
|
|
214
264
|
|
|
265
|
+
def self.report_total(report)
|
|
266
|
+
Portage::Ucp::Support::Totals.amount(report[:totals])
|
|
267
|
+
end
|
|
268
|
+
private_class_method :report_total
|
|
269
|
+
|
|
270
|
+
# A flag OptionParser can't read (`--min-confidence high`, `--qty two`,
|
|
271
|
+
# an unknown flag) is refused the same way as an out-of-range
|
|
272
|
+
# threshold. `--json` is looked for up front, since parsing stops at
|
|
273
|
+
# the bad flag and may never reach it.
|
|
215
274
|
def self.parse_buy_options(argv)
|
|
216
275
|
url = argv.first && !argv.first.start_with?("-") ? argv.shift : nil
|
|
217
276
|
buy = { url: url, qty: 1, yes: false, dry_run: false }
|
|
218
|
-
parsed = { buy: buy, find: {} }
|
|
277
|
+
parsed = { buy: buy, find: {}, confidence: {} }
|
|
278
|
+
json = argv.include?("--json")
|
|
219
279
|
buy_option_parser(buy, parsed).parse!(argv)
|
|
220
280
|
buy[:query] ||= ""
|
|
221
281
|
return parsed if url || !buy[:query].strip.empty?
|
|
222
282
|
|
|
223
283
|
warn USAGE
|
|
224
284
|
nil
|
|
285
|
+
rescue OptionParser::ParseError => e
|
|
286
|
+
invalid_buy_option(e.message, url: url, json: json)
|
|
225
287
|
end
|
|
226
288
|
private_class_method :parse_buy_options
|
|
227
289
|
|
|
@@ -232,12 +294,23 @@ module Portage
|
|
|
232
294
|
parser.on("--product-id ID") { |v| buy[:product_id] = v }
|
|
233
295
|
parser.on("--yes") { buy[:yes] = true }
|
|
234
296
|
parser.on("--dry-run") { buy[:dry_run] = true }
|
|
297
|
+
parser.on("--[no-]auto-open") { |v| buy[:auto_open] = v }
|
|
298
|
+
parser.on("--notify-webhook URL") { |v| buy[:notify_webhook] = v }
|
|
235
299
|
parser.on("--json") { parsed[:json] = true }
|
|
236
300
|
add_search_options(parser, buy, parsed)
|
|
301
|
+
add_confidence_options(parser, parsed[:confidence])
|
|
237
302
|
end
|
|
238
303
|
end
|
|
239
304
|
private_class_method :buy_option_parser
|
|
240
305
|
|
|
306
|
+
# The opt-in confidence gate in front of a `--yes` completion (see
|
|
307
|
+
# ConfidenceCheck) — both default to their PORTAGE_* env vars.
|
|
308
|
+
def self.add_confidence_options(parser, confidence)
|
|
309
|
+
parser.on("--decision-backend NAME") { |v| confidence[:backend] = v }
|
|
310
|
+
parser.on("--min-confidence N", Float) { |v| confidence[:threshold] = v }
|
|
311
|
+
end
|
|
312
|
+
private_class_method :add_confidence_options
|
|
313
|
+
|
|
241
314
|
# `--query` feeds both halves: it's the store search when there's no URL
|
|
242
315
|
# and the catalog search once a store is settled, so it's registered once
|
|
243
316
|
# here rather than twice on the same parser.
|
|
@@ -306,13 +379,30 @@ module Portage
|
|
|
306
379
|
end
|
|
307
380
|
private_class_method :format_history
|
|
308
381
|
|
|
382
|
+
# `outcome` first, since it's what the entry is for. Entries recorded
|
|
383
|
+
# before `outcome` existed fall back to their checkout_status/message.
|
|
309
384
|
def self.history_purchase_line(entry)
|
|
310
|
-
|
|
385
|
+
items = Array(entry["items"]).map { |item| item_label(item) }.join(", ")
|
|
386
|
+
[
|
|
387
|
+
"#{Time.at(entry['at'])} — #{entry['outcome'] || entry['checkout_status'] || entry['message']}",
|
|
388
|
+
"#{entry['url']} (#{entry['query']})", (items unless items.empty?),
|
|
389
|
+
(format_amount(entry["total"], entry["currency"]) if entry["total"]),
|
|
390
|
+
(entry["checkout_url"] unless entry["outcome"] == "purchased")
|
|
391
|
+
].compact.join(" — ")
|
|
311
392
|
end
|
|
312
393
|
private_class_method :history_purchase_line
|
|
313
394
|
|
|
395
|
+
# Takes a report's symbol-keyed item or a history entry's string-keyed
|
|
396
|
+
# one.
|
|
397
|
+
def self.item_label(item)
|
|
398
|
+
item = item.transform_keys(&:to_s)
|
|
399
|
+
"#{item['title'] || item['id']} x#{item['quantity']}"
|
|
400
|
+
end
|
|
401
|
+
private_class_method :item_label
|
|
402
|
+
|
|
314
403
|
def self.history_search_line(entry)
|
|
315
|
-
|
|
404
|
+
where = entry["url"] ? " at #{entry['url']}" : ""
|
|
405
|
+
"#{Time.at(entry['at'])} — \"#{entry['query']}\"#{where} — #{entry['offer_count']} result(s)"
|
|
316
406
|
end
|
|
317
407
|
private_class_method :history_search_line
|
|
318
408
|
|
|
@@ -594,20 +684,51 @@ module Portage
|
|
|
594
684
|
puts "Wrote #{result[:profile_path]} (kid #{result[:kid]})"
|
|
595
685
|
puts "Wrote private key to #{result[:private_key_path]} — keep this out of version control " \
|
|
596
686
|
"and off the machine that serves the public profile"
|
|
687
|
+
puts "Next: commit #{result[:profile_path]}, then, once it's on main, run " \
|
|
688
|
+
"`bundle exec rake agent_profile:purge` from the repo root — see docs/agent-profile.md."
|
|
597
689
|
0
|
|
598
690
|
end
|
|
599
691
|
private_class_method :run_generate_agent_profile
|
|
600
692
|
|
|
601
693
|
# --- output ---
|
|
602
694
|
|
|
695
|
+
# The `[outcome]` tag leads so a caller reading text, not --json, has the
|
|
696
|
+
# same value to branch on that the JSON report carries.
|
|
603
697
|
def self.format_report(report)
|
|
604
|
-
lines = ["#{report[:message]} (source: #{report[:source]})"]
|
|
698
|
+
lines = ["[#{report[:outcome]}] #{report[:message]} (source: #{report[:source]})"]
|
|
605
699
|
report[:products].each { |p| lines << " - #{product_line(p)}" }
|
|
700
|
+
lines.concat(format_checkout(report))
|
|
606
701
|
lines << " checkout: #{report[:checkout_url]}" if report[:checkout_url]
|
|
702
|
+
lines.concat(format_handoff(report[:handoff])) if report[:handoff]
|
|
703
|
+
lines.concat(format_decisions(report[:decisions])) if report[:decisions]&.any?
|
|
607
704
|
lines.join("\n")
|
|
608
705
|
end
|
|
609
706
|
private_class_method :format_report
|
|
610
707
|
|
|
708
|
+
# What the checkout holds, as opposed to the search results above it,
|
|
709
|
+
# and where it differs from the request.
|
|
710
|
+
def self.format_checkout(report)
|
|
711
|
+
lines = Array(report[:items]).map { |item| " in checkout: #{item_label(item)}" }
|
|
712
|
+
total = report_total(report)
|
|
713
|
+
lines << " total: #{format_amount(total, report[:currency])}" if total
|
|
714
|
+
lines + Array(report[:warnings]).map { |w| " warning: #{w}" }
|
|
715
|
+
end
|
|
716
|
+
private_class_method :format_checkout
|
|
717
|
+
|
|
718
|
+
def self.format_decisions(decisions)
|
|
719
|
+
decisions.map do |name, verdict|
|
|
720
|
+
" decision #{name}: #{verdict.compact.map { |key, value| "#{key}=#{value}" }.join(' ')}"
|
|
721
|
+
end
|
|
722
|
+
end
|
|
723
|
+
private_class_method :format_decisions
|
|
724
|
+
|
|
725
|
+
def self.format_handoff(handoff)
|
|
726
|
+
lines = [" opened in browser: #{handoff[:opened]}", " notified: #{handoff[:notified]}"]
|
|
727
|
+
lines << " notify error: #{handoff[:notify_error]}" if handoff[:notify_error]
|
|
728
|
+
lines
|
|
729
|
+
end
|
|
730
|
+
private_class_method :format_handoff
|
|
731
|
+
|
|
611
732
|
def self.product_line(product)
|
|
612
733
|
product.respond_to?(:title) ? "#{product.id}: #{product.title}" : "#{product['id']}: #{product['title']}"
|
|
613
734
|
end
|
|
@@ -644,8 +765,13 @@ module Portage
|
|
|
644
765
|
def self.format_price(offer)
|
|
645
766
|
return "price n/a" unless offer[:amount]
|
|
646
767
|
|
|
647
|
-
|
|
768
|
+
format_amount(offer[:amount], offer[:currency])
|
|
648
769
|
end
|
|
649
770
|
private_class_method :format_price
|
|
771
|
+
|
|
772
|
+
def self.format_amount(amount, currency)
|
|
773
|
+
"#{format('%.2f', amount / 100.0)}#{" #{currency}" if currency}"
|
|
774
|
+
end
|
|
775
|
+
private_class_method :format_amount
|
|
650
776
|
end
|
|
651
777
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: portage-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tom Whitbread
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: exe
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-09-23 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: portage-ucp
|
|
@@ -15,28 +16,34 @@ dependencies:
|
|
|
15
16
|
requirements:
|
|
16
17
|
- - "~>"
|
|
17
18
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '0.
|
|
19
|
+
version: '0.9'
|
|
19
20
|
type: :runtime
|
|
20
21
|
prerelease: false
|
|
21
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
23
|
requirements:
|
|
23
24
|
- - "~>"
|
|
24
25
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '0.
|
|
26
|
+
version: '0.9'
|
|
26
27
|
- !ruby/object:Gem::Dependency
|
|
27
28
|
name: portage-ucp-client
|
|
28
29
|
requirement: !ruby/object:Gem::Requirement
|
|
29
30
|
requirements:
|
|
30
31
|
- - "~>"
|
|
31
32
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '0.
|
|
33
|
+
version: '0.6'
|
|
34
|
+
- - ">="
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: 0.6.2
|
|
33
37
|
type: :runtime
|
|
34
38
|
prerelease: false
|
|
35
39
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
40
|
requirements:
|
|
37
41
|
- - "~>"
|
|
38
42
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '0.
|
|
43
|
+
version: '0.6'
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 0.6.2
|
|
40
47
|
- !ruby/object:Gem::Dependency
|
|
41
48
|
name: portage-ucp-journal
|
|
42
49
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -118,7 +125,10 @@ description: 'Ships the `portage` executable. `portage buy <url>` tries native U
|
|
|
118
125
|
no SERP scraping. Depends on portage-ucp (for platform detection via Resolver),
|
|
119
126
|
portage-ucp-client (for the actual buy calls), and portage-ucp-journal (for `portage-console`''s
|
|
120
127
|
read-only view of local purchase/transaction/order state); no single adapter gem
|
|
121
|
-
is a hard dependency.
|
|
128
|
+
is a hard dependency. Ranking, escalation and the spend-policy check use portage-ucp
|
|
129
|
+
core''s rules; portage-ucp-decision is optional and only the opt-in confidence gate
|
|
130
|
+
needs it.'
|
|
131
|
+
email:
|
|
122
132
|
executables:
|
|
123
133
|
- portage
|
|
124
134
|
- portage-console
|
|
@@ -132,20 +142,27 @@ files:
|
|
|
132
142
|
- exe/portage-console
|
|
133
143
|
- lib/portage/cli.rb
|
|
134
144
|
- lib/portage/cli/buy.rb
|
|
145
|
+
- lib/portage/cli/buyer_context.rb
|
|
135
146
|
- lib/portage/cli/catalog_products.rb
|
|
147
|
+
- lib/portage/cli/checkout_handoff.rb
|
|
136
148
|
- lib/portage/cli/compare.rb
|
|
149
|
+
- lib/portage/cli/confidence_check.rb
|
|
150
|
+
- lib/portage/cli/config.rb
|
|
137
151
|
- lib/portage/cli/console.rb
|
|
152
|
+
- lib/portage/cli/decisions.rb
|
|
138
153
|
- lib/portage/cli/doctor.rb
|
|
139
154
|
- lib/portage/cli/find.rb
|
|
140
155
|
- lib/portage/cli/generate/adapter.rb
|
|
141
156
|
- lib/portage/cli/generate/agent_profile.rb
|
|
142
157
|
- lib/portage/cli/history.rb
|
|
158
|
+
- lib/portage/cli/notifier.rb
|
|
143
159
|
- lib/portage/cli/payment_methods.rb
|
|
144
160
|
- lib/portage/cli/payment_methods/env_backend.rb
|
|
145
161
|
- lib/portage/cli/payment_methods/keychain_backend.rb
|
|
146
162
|
- lib/portage/cli/payment_methods/secret_service_backend.rb
|
|
147
163
|
- lib/portage/cli/probe_cache.rb
|
|
148
164
|
- lib/portage/cli/search_backends.rb
|
|
165
|
+
- lib/portage/cli/setting.rb
|
|
149
166
|
- lib/portage/cli/shipping_profile.rb
|
|
150
167
|
- lib/portage/cli/version.rb
|
|
151
168
|
homepage: https://github.com/tomtom87/Portage/tree/main/portage-cli
|
|
@@ -155,6 +172,7 @@ metadata:
|
|
|
155
172
|
source_code_uri: https://github.com/tomtom87/Portage/tree/main/portage-cli
|
|
156
173
|
changelog_uri: https://github.com/tomtom87/Portage/blob/main/portage-cli/CHANGELOG.md
|
|
157
174
|
rubygems_mfa_required: 'true'
|
|
175
|
+
post_install_message:
|
|
158
176
|
rdoc_options: []
|
|
159
177
|
require_paths:
|
|
160
178
|
- lib
|
|
@@ -169,7 +187,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
169
187
|
- !ruby/object:Gem::Version
|
|
170
188
|
version: '0'
|
|
171
189
|
requirements: []
|
|
172
|
-
rubygems_version:
|
|
190
|
+
rubygems_version: 3.5.22
|
|
191
|
+
signing_key:
|
|
173
192
|
specification_version: 4
|
|
174
193
|
summary: portage — one CLI command to buy from any store, native UCP or not
|
|
175
194
|
test_files: []
|