portage-cli 0.5.1 → 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/CHANGELOG.md +33 -0
- data/lib/portage/cli/buy.rb +61 -5
- data/lib/portage/cli/find.rb +16 -1
- data/lib/portage/cli/generate/agent_profile.rb +142 -0
- data/lib/portage/cli/version.rb +1 -1
- data/lib/portage/cli.rb +35 -3
- metadata +8 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4acb63ece20e24b7d43048315145a96e15ded1c4e5b0cf83c3f045603a033af6
|
|
4
|
+
data.tar.gz: 536057c80badcd44ee1be90679fd7f9233adb3d5a1c07b5b11a12201fbae5701
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 54bc26bf74fe96c2a2154062ab41619e10bf046da1cc06f067fc1f0f7eaec9c29ff6ab461e9cf2907ede4084805b8718f65557e5d4c3c79c486650eea10c1075
|
|
7
|
+
data.tar.gz: deb45f093ce769ebae837bad5af628c8970aac76b6741ea8b083393b45038451a455ece2d603671c3d2884e3974858941ff6c29d96affd7bd3bbcc89f6a689d9
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,39 @@ All notable changes to this project are documented here. Format loosely follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/); this project is
|
|
5
5
|
pre-1.0, so APIs may still shift between minor versions.
|
|
6
6
|
|
|
7
|
+
## [0.6.0] - 2026-09-17
|
|
8
|
+
|
|
9
|
+
- Fixed `buy`/`find` crashing with a raw `Faraday::UnprocessableContentError`
|
|
10
|
+
against a real UCP store, once manifest parsing succeeded — the actual
|
|
11
|
+
`search_catalog`/`create_checkout` calls were still built in the wrong
|
|
12
|
+
wire shape (see `portage-ucp-client` 0.4.0). Both commands
|
|
13
|
+
now report a clear, actionable message instead: set `PORTAGE_AGENT_PROFILE`
|
|
14
|
+
when it's missing, or surface the store's rejection cleanly when it's set
|
|
15
|
+
but not accepted.
|
|
16
|
+
- Added `PORTAGE_AGENT_PROFILE` — required for `find`/`buy` against a real,
|
|
17
|
+
external UCP store (not your own store via an adapter). No default; see
|
|
18
|
+
`.env.example`.
|
|
19
|
+
- Fixed `buy` sending a catalog product's own id as the purchasable line
|
|
20
|
+
item — correct for a backend where "the product" and "the thing you add
|
|
21
|
+
to a cart" share one id, but wrong for Shopify (confirmed live: every
|
|
22
|
+
`portage buy` against a real Shopify test store failed with "Invalid id",
|
|
23
|
+
since Storefront's cart takes a `ProductVariant` GID, not the parent
|
|
24
|
+
`Product` GID `search_catalog` returns as `id`). `Buy#full_buy` and
|
|
25
|
+
`#redirect_checkout` now build `line_items` from a product's first/
|
|
26
|
+
default variant id when one exists, falling back to the product id
|
|
27
|
+
otherwise; `--product-id` matching (`#product_id_of`) is unchanged, since
|
|
28
|
+
it still needs to match the catalog-level id `find`/`compare` show the
|
|
29
|
+
caller.
|
|
30
|
+
- Added `portage generate agent-profile`, which writes a real UCP
|
|
31
|
+
agent-identity document (the JSON a store fetches from the
|
|
32
|
+
`meta.ucp-agent.profile` URL to decide whether to answer at all).
|
|
33
|
+
`portage-cli`'s own generated profile is checked in at
|
|
34
|
+
`agent-profile/agent-profile.json` and published to a stable URL by
|
|
35
|
+
`.github/workflows/publish-agent-profile.yml`, which is what
|
|
36
|
+
`PORTAGE_AGENT_PROFILE` defaults to pointing at.
|
|
37
|
+
- Widens the `portage-ucp` pin to `~> 0.8` and the `portage-ucp-client` pin
|
|
38
|
+
to `~> 0.4` so this gem installs alongside the 0.8.0-line releases.
|
|
39
|
+
|
|
7
40
|
## [0.5.1] - 2026-09-16
|
|
8
41
|
|
|
9
42
|
- No behavior change — 0.5.0 was built and pushed with `gem build` run from
|
data/lib/portage/cli/buy.rb
CHANGED
|
@@ -61,6 +61,14 @@ module Portage
|
|
|
61
61
|
|
|
62
62
|
def discover(url)
|
|
63
63
|
Portage::Ucp::Client.discover(url.to_s)
|
|
64
|
+
rescue Portage::Ucp::Client::ManifestShapeError => e
|
|
65
|
+
# The store *is* running UCP — this client just couldn't parse its
|
|
66
|
+
# manifest. Distinct from a genuine 404/unreachable host below:
|
|
67
|
+
# falling through silently there would hide a bug in this gem behind
|
|
68
|
+
# the same "no automated path" message a store with no UCP support
|
|
69
|
+
# gets, so this warns instead.
|
|
70
|
+
warn "portage: #{url} serves a UCP manifest this client couldn't parse (#{e.message})"
|
|
71
|
+
nil
|
|
64
72
|
rescue Portage::Ucp::Client::DiscoveryError
|
|
65
73
|
nil
|
|
66
74
|
end
|
|
@@ -71,6 +79,30 @@ module Portage
|
|
|
71
79
|
else
|
|
72
80
|
catalog_only(session)
|
|
73
81
|
end
|
|
82
|
+
rescue Portage::Ucp::Client::MissingAgentProfileError, Portage::Ucp::Client::UnsupportedWireShapeError,
|
|
83
|
+
MCP::Client::RequestHandlerError => e
|
|
84
|
+
native_flow_error_report(e)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def native_flow_error_report(error)
|
|
88
|
+
case error
|
|
89
|
+
when Portage::Ucp::Client::MissingAgentProfileError
|
|
90
|
+
build_report(source: "native_ucp", browse: false, checkout: false,
|
|
91
|
+
message: "Set PORTAGE_AGENT_PROFILE to a URL that describes this agent — " \
|
|
92
|
+
"#{@uri} verifies it before answering any UCP call.")
|
|
93
|
+
when Portage::Ucp::Client::UnsupportedWireShapeError
|
|
94
|
+
build_report(source: "native_ucp", browse: true, checkout: false,
|
|
95
|
+
message: "Can't complete checkout on #{@uri} yet: #{error.message}")
|
|
96
|
+
else
|
|
97
|
+
# MCP::Client::RequestHandlerError doesn't retain the server's JSON
|
|
98
|
+
# error body on this path, so this can't quote the server's own
|
|
99
|
+
# explanation — a common cause is PORTAGE_AGENT_PROFILE not
|
|
100
|
+
# pointing at a real, JSON agent-profile document the store's UCP
|
|
101
|
+
# endpoint accepts.
|
|
102
|
+
build_report(source: "native_ucp", browse: false, checkout: false,
|
|
103
|
+
message: "#{@uri} rejected the request (#{error.message}) — if PORTAGE_AGENT_PROFILE " \
|
|
104
|
+
"is set, check it points at a real agent-profile document the store accepts.")
|
|
105
|
+
end
|
|
74
106
|
end
|
|
75
107
|
|
|
76
108
|
def catalog_only(session)
|
|
@@ -156,8 +188,9 @@ module Portage
|
|
|
156
188
|
def redirect_checkout(adapter, products)
|
|
157
189
|
return nil if products.empty? || !Portage::Ucp::Capabilities::CHECKOUT.advertised_for?(adapter)
|
|
158
190
|
|
|
159
|
-
|
|
160
|
-
|
|
191
|
+
item_id = products.first.variants&.first&.id || products.first.id
|
|
192
|
+
adapter.create_checkout(line_items: [{ product_id: item_id, quantity: @qty }],
|
|
193
|
+
idempotency_key: "portage-buy-#{item_id}")
|
|
161
194
|
rescue StandardError
|
|
162
195
|
nil
|
|
163
196
|
end
|
|
@@ -178,8 +211,9 @@ module Portage
|
|
|
178
211
|
message: no_match_message)
|
|
179
212
|
end
|
|
180
213
|
|
|
181
|
-
checkout = session.create_checkout(line_items: [{ product_id:
|
|
182
|
-
fulfillment: requested_fulfillment(fulfillment_adapter)
|
|
214
|
+
checkout = session.create_checkout(line_items: [{ product_id: line_item_id_of(product), quantity: @qty }],
|
|
215
|
+
fulfillment: requested_fulfillment(fulfillment_adapter),
|
|
216
|
+
meta: agent_meta)
|
|
183
217
|
checkout = select_cheapest_shipping(session, checkout) if fulfillment_adapter
|
|
184
218
|
finish_checkout(session, source, products, checkout)
|
|
185
219
|
end
|
|
@@ -264,6 +298,20 @@ module Portage
|
|
|
264
298
|
product["id"]
|
|
265
299
|
end
|
|
266
300
|
|
|
301
|
+
# `create_checkout`'s `line_items[].product_id` is the conformance
|
|
302
|
+
# kit's overloaded name (portage-ucp/lib/portage/ucp/rspec.rb) for
|
|
303
|
+
# "whatever id this adapter's cart actually takes" — for a backend
|
|
304
|
+
# where a product's variants have their own id (confirmed live on
|
|
305
|
+
# Shopify: a ProductVariant GID, distinct from the parent Product
|
|
306
|
+
# GID), that's the first/default variant, not the catalog id
|
|
307
|
+
# #product_id_of returns for display/--product-id matching. A
|
|
308
|
+
# product with no variants (or a backend that doesn't distinguish
|
|
309
|
+
# the two) falls back to the product id unchanged (see
|
|
310
|
+
# docs/design-log.md §41).
|
|
311
|
+
def line_item_id_of(product)
|
|
312
|
+
product["variants"]&.first&.dig("id") || product_id_of(product)
|
|
313
|
+
end
|
|
314
|
+
|
|
267
315
|
def finish_checkout(session, source, products, checkout)
|
|
268
316
|
status = checkout["status"]
|
|
269
317
|
return escalation_report(source, products, checkout) if status == "requires_escalation"
|
|
@@ -314,7 +362,15 @@ module Portage
|
|
|
314
362
|
end
|
|
315
363
|
|
|
316
364
|
def safe_search(session)
|
|
317
|
-
CatalogProducts.from(session.search_catalog(query: @query, limit: 10))
|
|
365
|
+
CatalogProducts.from(session.search_catalog(query: @query, limit: 10, meta: agent_meta))
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Real UCP servers fetch this URL to verify the caller's identity
|
|
369
|
+
# before answering any call (see Transports::Http) — the own-store
|
|
370
|
+
# loopback path ignores it harmlessly, so it's cheapest to always pass
|
|
371
|
+
# it rather than branch on which transport `session` happens to be.
|
|
372
|
+
def agent_meta
|
|
373
|
+
{ agent_profile: ENV.fetch("PORTAGE_AGENT_PROFILE", nil) }
|
|
318
374
|
end
|
|
319
375
|
|
|
320
376
|
# --- Homepage fetch (used by both the manifest-not-found path and the
|
data/lib/portage/cli/find.rb
CHANGED
|
@@ -46,6 +46,10 @@ module Portage
|
|
|
46
46
|
offers = rank(stores.flat_map { |store| offers_for(store) })
|
|
47
47
|
report(candidates: candidates, stores: stores.map { |s| s.slice(:origin, :source, :checkout) },
|
|
48
48
|
offers: offers, message: summary(candidates, stores, offers))
|
|
49
|
+
rescue Portage::Ucp::Client::MissingAgentProfileError
|
|
50
|
+
report(candidates: candidates, stores: stores.map { |s| s.slice(:origin, :source, :checkout) },
|
|
51
|
+
message: "Set PORTAGE_AGENT_PROFILE to a URL that describes this agent — each store " \
|
|
52
|
+
"verifies it before answering a catalog search.")
|
|
49
53
|
end
|
|
50
54
|
|
|
51
55
|
private
|
|
@@ -135,12 +139,23 @@ module Portage
|
|
|
135
139
|
# --- Step 3: ask the survivors what they stock ---
|
|
136
140
|
|
|
137
141
|
def offers_for(store)
|
|
138
|
-
products = CatalogProducts.from(
|
|
142
|
+
products = CatalogProducts.from(
|
|
143
|
+
store[:session].search_catalog(query: @query, limit: PER_STORE_RESULTS, meta: agent_meta)
|
|
144
|
+
)
|
|
139
145
|
products.filter_map { |product| offer(store, product) }
|
|
146
|
+
rescue Portage::Ucp::Client::MissingAgentProfileError
|
|
147
|
+
raise
|
|
140
148
|
rescue StandardError
|
|
141
149
|
[]
|
|
142
150
|
end
|
|
143
151
|
|
|
152
|
+
# Real UCP servers fetch this URL to verify the caller's identity
|
|
153
|
+
# before answering any call (see Transports::Http) — the own-store
|
|
154
|
+
# loopback path ignores it harmlessly.
|
|
155
|
+
def agent_meta
|
|
156
|
+
{ agent_profile: ENV.fetch("PORTAGE_AGENT_PROFILE", nil) }
|
|
157
|
+
end
|
|
158
|
+
|
|
144
159
|
def offer(store, product)
|
|
145
160
|
amount, currency = price_of(product)
|
|
146
161
|
return nil if @max_price && amount && amount > @max_price
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
require "openssl"
|
|
2
|
+
require "digest"
|
|
3
|
+
require "base64"
|
|
4
|
+
require "json"
|
|
5
|
+
require "fileutils"
|
|
6
|
+
require "portage/ucp"
|
|
7
|
+
|
|
8
|
+
module Portage
|
|
9
|
+
module Cli
|
|
10
|
+
module Generate
|
|
11
|
+
# Generates and (re)publishes portage-cli's own UCP agent-identity
|
|
12
|
+
# profile document — the `meta.ucp-agent.profile` URL real UCP servers
|
|
13
|
+
# fetch and validate before answering any catalog/cart/checkout call
|
|
14
|
+
# (confirmed live against Shopify's 2026-08-25 rollout: an unreachable
|
|
15
|
+
# or malformed profile 422s with `profile_unreachable`/`profile_malformed`
|
|
16
|
+
# before the request's shape is even considered). This class only
|
|
17
|
+
# produces the document and its signing key; hosting it at a stable,
|
|
18
|
+
# public URL (HTTPS, no redirects, `Cache-Control: public, max-age>=60`
|
|
19
|
+
# — see docs/agent-profile.md) is a separate, one-time infra step.
|
|
20
|
+
#
|
|
21
|
+
# Deliberately NOT Portage::Ucp::Manifest, despite the surface
|
|
22
|
+
# similarity: Manifest builds the *business's* /.well-known/ucp
|
|
23
|
+
# document and nests `signing_keys` inside its own "ucp" envelope
|
|
24
|
+
# (portage-ucp/lib/portage/ucp/manifest.rb); the UCP spec's agent
|
|
25
|
+
# profile is a different document describing the *agent* calling in,
|
|
26
|
+
# and puts `signing_keys` as a sibling of "ucp" at the document root.
|
|
27
|
+
# Same-looking key material, structurally different document — not
|
|
28
|
+
# interchangeable, so this doesn't subclass or reuse Manifest.
|
|
29
|
+
#
|
|
30
|
+
# `Portage::Ucp::Manifest::UCP_VERSION` is reused as-is (not
|
|
31
|
+
# redeclared) so the two documents can never drift to different spec
|
|
32
|
+
# versions by accident.
|
|
33
|
+
#
|
|
34
|
+
# Rotation is the future-proofing this exists for: re-running with
|
|
35
|
+
# `rotate: true` keeps every key already published (so a request
|
|
36
|
+
# signed under an older `kid` keeps verifying while callers migrate)
|
|
37
|
+
# and adds one freshly generated key alongside them. Nothing here ever
|
|
38
|
+
# drops a key — retiring one is a deliberate, separate edit once
|
|
39
|
+
# nothing signs with it any more.
|
|
40
|
+
class AgentProfile
|
|
41
|
+
UCP_VERSION = Portage::Ucp::Manifest::UCP_VERSION
|
|
42
|
+
|
|
43
|
+
Key = Struct.new(:kid, :jwk, :private_pem, keyword_init: true)
|
|
44
|
+
|
|
45
|
+
# @param out [String] path to write the public profile JSON document
|
|
46
|
+
# @param key_out [String] path to write the new private key's PEM —
|
|
47
|
+
# caller's responsibility to keep this out of version control
|
|
48
|
+
# @param rotate [Boolean] keep existing signing_keys from `out` (if
|
|
49
|
+
# it already exists) and add a new one, instead of replacing them
|
|
50
|
+
# @return [Hash] { profile_path:, private_key_path:, kid: } — the
|
|
51
|
+
# kid of the newly generated key
|
|
52
|
+
def self.generate(out:, key_out:, rotate: false)
|
|
53
|
+
new(out: out, key_out: key_out, rotate: rotate).generate
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def initialize(out:, key_out:, rotate: false)
|
|
57
|
+
@out = out
|
|
58
|
+
@key_out = key_out
|
|
59
|
+
@rotate = rotate
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def generate
|
|
63
|
+
new_key = generate_key
|
|
64
|
+
doc = build_document(carried_forward_keys + [new_key.jwk])
|
|
65
|
+
|
|
66
|
+
write_profile(doc)
|
|
67
|
+
write_private_key(new_key.private_pem)
|
|
68
|
+
|
|
69
|
+
{ profile_path: @out, private_key_path: @key_out, kid: new_key.kid }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def carried_forward_keys
|
|
75
|
+
return [] unless @rotate && File.exist?(@out)
|
|
76
|
+
|
|
77
|
+
JSON.parse(File.read(@out)).fetch("signing_keys", [])
|
|
78
|
+
rescue JSON::ParserError
|
|
79
|
+
[]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def build_document(signing_keys)
|
|
83
|
+
{
|
|
84
|
+
"ucp" => {
|
|
85
|
+
"version" => UCP_VERSION,
|
|
86
|
+
"services" => {},
|
|
87
|
+
"capabilities" => {},
|
|
88
|
+
"payment_handlers" => {}
|
|
89
|
+
},
|
|
90
|
+
"signing_keys" => signing_keys
|
|
91
|
+
}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def write_profile(doc)
|
|
95
|
+
FileUtils.mkdir_p(File.dirname(@out))
|
|
96
|
+
File.write(@out, "#{JSON.pretty_generate(doc)}\n")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def write_private_key(pem)
|
|
100
|
+
FileUtils.mkdir_p(File.dirname(@key_out))
|
|
101
|
+
File.write(@key_out, pem)
|
|
102
|
+
File.chmod(0o600, @key_out)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# A JWK's `kid` is derived from the key material itself (RFC 7638
|
|
106
|
+
# thumbprint) rather than assigned, so it can't drift from the key
|
|
107
|
+
# it names — a caller can't accidentally publish a profile where a
|
|
108
|
+
# `kid` points at the wrong entry.
|
|
109
|
+
def generate_key
|
|
110
|
+
pkey = OpenSSL::PKey::EC.generate("prime256v1")
|
|
111
|
+
x_b64, y_b64 = coordinates(pkey)
|
|
112
|
+
kid = thumbprint(x_b64, y_b64)
|
|
113
|
+
|
|
114
|
+
jwk = { "kid" => kid, "kty" => "EC", "crv" => "P-256", "x" => x_b64, "y" => y_b64,
|
|
115
|
+
"use" => "sig", "alg" => "ES256" }
|
|
116
|
+
|
|
117
|
+
Key.new(kid: kid, jwk: jwk, private_pem: pkey.to_pem)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Raw uncompressed EC point encoding: 0x04 || x (32 bytes) || y (32
|
|
121
|
+
# bytes) for P-256 — see Portage::Ucp::Security::Signature::CURVES,
|
|
122
|
+
# which decodes the same layout in reverse when verifying.
|
|
123
|
+
def coordinates(pkey)
|
|
124
|
+
octets = pkey.public_key.to_bn.to_s(2)
|
|
125
|
+
coord_bytes = 32
|
|
126
|
+
x = octets[1, coord_bytes]
|
|
127
|
+
y = octets[1 + coord_bytes, coord_bytes]
|
|
128
|
+
[url_b64(x), url_b64(y)]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def thumbprint(x_b64, y_b64)
|
|
132
|
+
canonical = JSON.generate({ "crv" => "P-256", "kty" => "EC", "x" => x_b64, "y" => y_b64 })
|
|
133
|
+
url_b64(Digest::SHA256.digest(canonical))
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def url_b64(bytes)
|
|
137
|
+
Base64.urlsafe_encode64(bytes, padding: false)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
data/lib/portage/cli/version.rb
CHANGED
data/lib/portage/cli.rb
CHANGED
|
@@ -11,6 +11,7 @@ require_relative "cli/history"
|
|
|
11
11
|
require_relative "cli/payment_methods"
|
|
12
12
|
require_relative "cli/doctor"
|
|
13
13
|
require_relative "cli/generate/adapter"
|
|
14
|
+
require_relative "cli/generate/agent_profile"
|
|
14
15
|
|
|
15
16
|
module Portage
|
|
16
17
|
# `portage` — the single command-line entrypoint for acting as a shopper's
|
|
@@ -41,6 +42,7 @@ module Portage
|
|
|
41
42
|
[--allow HOST ...] [--clear-allowlist]
|
|
42
43
|
portage doctor [--require FILE] [--adapter CLASS_NAME] [--json]
|
|
43
44
|
portage generate adapter NAME [--dir DIR]
|
|
45
|
+
portage generate agent-profile [--out FILE] [--key-out FILE] [--rotate]
|
|
44
46
|
USAGE
|
|
45
47
|
|
|
46
48
|
COMMANDS = { "buy" => :run_buy, "find" => :run_find, "compare" => :run_compare,
|
|
@@ -552,8 +554,20 @@ module Portage
|
|
|
552
554
|
# --- generate ---
|
|
553
555
|
|
|
554
556
|
def self.run_generate(argv)
|
|
555
|
-
kind,
|
|
556
|
-
|
|
557
|
+
kind, *rest = argv
|
|
558
|
+
case kind
|
|
559
|
+
when "adapter" then run_generate_adapter(rest)
|
|
560
|
+
when "agent-profile" then run_generate_agent_profile(rest)
|
|
561
|
+
else
|
|
562
|
+
warn USAGE
|
|
563
|
+
1
|
|
564
|
+
end
|
|
565
|
+
end
|
|
566
|
+
private_class_method :run_generate
|
|
567
|
+
|
|
568
|
+
def self.run_generate_adapter(rest)
|
|
569
|
+
name, *rest = rest
|
|
570
|
+
unless name
|
|
557
571
|
warn USAGE
|
|
558
572
|
return 1
|
|
559
573
|
end
|
|
@@ -564,7 +578,25 @@ module Portage
|
|
|
564
578
|
puts "Scaffolded #{path}/"
|
|
565
579
|
0
|
|
566
580
|
end
|
|
567
|
-
private_class_method :
|
|
581
|
+
private_class_method :run_generate_adapter
|
|
582
|
+
|
|
583
|
+
def self.run_generate_agent_profile(rest)
|
|
584
|
+
out = "agent-profile.json"
|
|
585
|
+
key_out = "agent-profile.key.pem"
|
|
586
|
+
rotate = false
|
|
587
|
+
OptionParser.new do |parser|
|
|
588
|
+
parser.on("--out FILE") { |v| out = v }
|
|
589
|
+
parser.on("--key-out FILE") { |v| key_out = v }
|
|
590
|
+
parser.on("--rotate") { rotate = true }
|
|
591
|
+
end.parse!(rest)
|
|
592
|
+
|
|
593
|
+
result = Generate::AgentProfile.generate(out: out, key_out: key_out, rotate: rotate)
|
|
594
|
+
puts "Wrote #{result[:profile_path]} (kid #{result[:kid]})"
|
|
595
|
+
puts "Wrote private key to #{result[:private_key_path]} — keep this out of version control " \
|
|
596
|
+
"and off the machine that serves the public profile"
|
|
597
|
+
0
|
|
598
|
+
end
|
|
599
|
+
private_class_method :run_generate_agent_profile
|
|
568
600
|
|
|
569
601
|
# --- output ---
|
|
570
602
|
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: portage-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Tom Whitbread
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: portage-ucp
|
|
@@ -16,28 +15,28 @@ dependencies:
|
|
|
16
15
|
requirements:
|
|
17
16
|
- - "~>"
|
|
18
17
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '0.
|
|
18
|
+
version: '0.8'
|
|
20
19
|
type: :runtime
|
|
21
20
|
prerelease: false
|
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
22
|
requirements:
|
|
24
23
|
- - "~>"
|
|
25
24
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '0.
|
|
25
|
+
version: '0.8'
|
|
27
26
|
- !ruby/object:Gem::Dependency
|
|
28
27
|
name: portage-ucp-client
|
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
|
30
29
|
requirements:
|
|
31
30
|
- - "~>"
|
|
32
31
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '0.
|
|
32
|
+
version: '0.4'
|
|
34
33
|
type: :runtime
|
|
35
34
|
prerelease: false
|
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
36
|
requirements:
|
|
38
37
|
- - "~>"
|
|
39
38
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '0.
|
|
39
|
+
version: '0.4'
|
|
41
40
|
- !ruby/object:Gem::Dependency
|
|
42
41
|
name: portage-ucp-journal
|
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -120,7 +119,6 @@ description: 'Ships the `portage` executable. `portage buy <url>` tries native U
|
|
|
120
119
|
portage-ucp-client (for the actual buy calls), and portage-ucp-journal (for `portage-console`''s
|
|
121
120
|
read-only view of local purchase/transaction/order state); no single adapter gem
|
|
122
121
|
is a hard dependency.'
|
|
123
|
-
email:
|
|
124
122
|
executables:
|
|
125
123
|
- portage
|
|
126
124
|
- portage-console
|
|
@@ -140,6 +138,7 @@ files:
|
|
|
140
138
|
- lib/portage/cli/doctor.rb
|
|
141
139
|
- lib/portage/cli/find.rb
|
|
142
140
|
- lib/portage/cli/generate/adapter.rb
|
|
141
|
+
- lib/portage/cli/generate/agent_profile.rb
|
|
143
142
|
- lib/portage/cli/history.rb
|
|
144
143
|
- lib/portage/cli/payment_methods.rb
|
|
145
144
|
- lib/portage/cli/payment_methods/env_backend.rb
|
|
@@ -156,7 +155,6 @@ metadata:
|
|
|
156
155
|
source_code_uri: https://github.com/tomtom87/Portage/tree/main/portage-cli
|
|
157
156
|
changelog_uri: https://github.com/tomtom87/Portage/blob/main/portage-cli/CHANGELOG.md
|
|
158
157
|
rubygems_mfa_required: 'true'
|
|
159
|
-
post_install_message:
|
|
160
158
|
rdoc_options: []
|
|
161
159
|
require_paths:
|
|
162
160
|
- lib
|
|
@@ -171,8 +169,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
171
169
|
- !ruby/object:Gem::Version
|
|
172
170
|
version: '0'
|
|
173
171
|
requirements: []
|
|
174
|
-
rubygems_version:
|
|
175
|
-
signing_key:
|
|
172
|
+
rubygems_version: 4.0.21
|
|
176
173
|
specification_version: 4
|
|
177
174
|
summary: portage — one CLI command to buy from any store, native UCP or not
|
|
178
175
|
test_files: []
|