portage-cli 0.7.5 → 0.9.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 +616 -0
- data/README.md +302 -5
- data/known-stores/categories.yml +1263 -0
- data/lib/portage/cli/agent_profile_url.rb +30 -0
- data/lib/portage/cli/approval_policy.rb +56 -0
- data/lib/portage/cli/approve.rb +135 -0
- data/lib/portage/cli/browser_import/categorize.rb +59 -0
- data/lib/portage/cli/browser_import/confirm.rb +35 -0
- data/lib/portage/cli/browser_import/domains.rb +47 -0
- data/lib/portage/cli/browser_import/filter.rb +91 -0
- data/lib/portage/cli/browser_import/importer.rb +248 -0
- data/lib/portage/cli/browser_import/plist_xml.rb +72 -0
- data/lib/portage/cli/browser_import/prober.rb +60 -0
- data/lib/portage/cli/browser_import/profiles.rb +114 -0
- data/lib/portage/cli/browser_import/readers.rb +179 -0
- data/lib/portage/cli/browser_import/saver.rb +62 -0
- data/lib/portage/cli/browser_import/sqlite.rb +68 -0
- data/lib/portage/cli/browser_import.rb +23 -0
- data/lib/portage/cli/browser_opener.rb +38 -0
- data/lib/portage/cli/browser_profile/allowlist.rb +40 -0
- data/lib/portage/cli/browser_profile/bridge.rb +120 -0
- data/lib/portage/cli/browser_profile/browsers.rb +69 -0
- data/lib/portage/cli/browser_profile/cdp.rb +67 -0
- data/lib/portage/cli/browser_profile/cdp_socket.rb +186 -0
- data/lib/portage/cli/browser_profile/errors.rb +26 -0
- data/lib/portage/cli/browser_profile/launcher.rb +34 -0
- data/lib/portage/cli/browser_profile/profile.rb +93 -0
- data/lib/portage/cli/browser_profile.rb +25 -0
- data/lib/portage/cli/buy.rb +559 -29
- data/lib/portage/cli/checkout_handoff.rb +5 -24
- data/lib/portage/cli/classifier.rb +158 -0
- data/lib/portage/cli/compare.rb +3 -0
- data/lib/portage/cli/doctor.rb +155 -1
- data/lib/portage/cli/dot_env.rb +55 -0
- data/lib/portage/cli/find.rb +103 -12
- data/lib/portage/cli/handoff_agents.rb +186 -0
- data/lib/portage/cli/handoff_only.rb +94 -0
- data/lib/portage/cli/handoff_reconciler.rb +15 -1
- data/lib/portage/cli/handoff_target.rb +61 -0
- data/lib/portage/cli/history.rb +47 -2
- data/lib/portage/cli/human_prompt.rb +118 -0
- data/lib/portage/cli/index/builder.rb +335 -0
- data/lib/portage/cli/index/exporter.rb +91 -0
- data/lib/portage/cli/index/known_cache.rb +155 -0
- data/lib/portage/cli/index/product_store.rb +101 -0
- data/lib/portage/cli/index/sources/browser.rb +31 -0
- data/lib/portage/cli/index/sources/shopify_catalog.rb +82 -0
- data/lib/portage/cli/index/sources/stores_file.rb +58 -0
- data/lib/portage/cli/index/sources/webmcp_sweep.rb +29 -0
- data/lib/portage/cli/index/sources/wikidata.rb +95 -0
- data/lib/portage/cli/index/sources.rb +44 -0
- data/lib/portage/cli/index/store.rb +109 -0
- data/lib/portage/cli/index.rb +20 -0
- data/lib/portage/cli/known_stores_url.rb +15 -0
- data/lib/portage/cli/money.rb +18 -0
- data/lib/portage/cli/offer_choice.rb +38 -0
- data/lib/portage/cli/offer_sources.rb +460 -0
- data/lib/portage/cli/payment_methods.rb +24 -3
- data/lib/portage/cli/pick.rb +167 -0
- data/lib/portage/cli/product_page.rb +84 -0
- data/lib/portage/cli/quotes.rb +82 -0
- data/lib/portage/cli/search_backends.rb +337 -12
- data/lib/portage/cli/setup_wizard/prompt.rb +67 -0
- data/lib/portage/cli/setup_wizard/steps/agent_profile.rb +60 -0
- data/lib/portage/cli/setup_wizard/steps/browser_import.rb +29 -0
- data/lib/portage/cli/setup_wizard/steps/handoff.rb +100 -0
- data/lib/portage/cli/setup_wizard/steps/index_build.rb +31 -0
- data/lib/portage/cli/setup_wizard/steps/policy.rb +60 -0
- data/lib/portage/cli/setup_wizard/steps/retailer_keys.rb +55 -0
- data/lib/portage/cli/setup_wizard/steps/search_keys.rb +54 -0
- data/lib/portage/cli/setup_wizard/steps/shipping.rb +51 -0
- data/lib/portage/cli/setup_wizard.rb +74 -0
- data/lib/portage/cli/version.rb +1 -1
- data/lib/portage/cli/webmcp.rb +10 -3
- data/lib/portage/cli/webmcp_autofill_confirm.rb +38 -0
- data/lib/portage/cli/webmcp_autofill_fields.rb +60 -0
- data/lib/portage/cli/webmcp_autofill_mode.rb +39 -0
- data/lib/portage/cli/webmcp_mapping_confirm.rb +68 -0
- data/lib/portage/cli/webmcp_mappings.rb +84 -0
- data/lib/portage/cli.rb +930 -43
- metadata +67 -2
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Portage
|
|
2
|
+
module Cli
|
|
3
|
+
module BrowserProfile
|
|
4
|
+
# Base for everything this feature raises — a caller (cli.rb, Buy)
|
|
5
|
+
# can rescue this one class instead of naming each subclass.
|
|
6
|
+
class Error < StandardError; end
|
|
7
|
+
|
|
8
|
+
# `Browsers.binary_for` found nothing installed for the requested
|
|
9
|
+
# browser.
|
|
10
|
+
class BrowserNotFoundError < Error; end
|
|
11
|
+
|
|
12
|
+
# The profile process never answered its own CDP endpoint within the
|
|
13
|
+
# launch wait — a slow machine, a browser that failed to start, or a
|
|
14
|
+
# port already held by something else.
|
|
15
|
+
class LaunchError < Error; end
|
|
16
|
+
|
|
17
|
+
# docs/plans/buy-skill-and-local-browser.md Phase 6: driving stayed
|
|
18
|
+
# inside the allowed domains (the store being bought from, plus its
|
|
19
|
+
# checkout host once known) or this stopped the run rather than
|
|
20
|
+
# continue on an unexpected host. Never rescued into "try again
|
|
21
|
+
# somewhere else" — the whole point is that nothing here recovers by
|
|
22
|
+
# itself.
|
|
23
|
+
class DomainNotAllowedError < Error; end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module Portage
|
|
2
|
+
module Cli
|
|
3
|
+
module BrowserProfile
|
|
4
|
+
# The one place `Process.spawn` is called from this feature —
|
|
5
|
+
# injectable (`spawn:`) so Profile's own specs never start a real
|
|
6
|
+
# browser (docs/plans/buy-skill-and-local-browser.md Phase 6's own
|
|
7
|
+
# "specs must not launch a real browser" rule).
|
|
8
|
+
#
|
|
9
|
+
# Flags: `--user-data-dir` is always the dedicated Portage profile
|
|
10
|
+
# directory (never the browser's default one — Chrome 136+ refuses
|
|
11
|
+
# remote debugging on the default profile anyway, and this never
|
|
12
|
+
# tries), `--remote-debugging-port` is this profile's own fixed
|
|
13
|
+
# port, and `--no-first-run`/`--no-default-browser-check` keep the
|
|
14
|
+
# launch from popping up onboarding UI the first time. Detached
|
|
15
|
+
# (its own process group, stdout/stderr discarded) so `portage
|
|
16
|
+
# browser profile open` returns immediately rather than blocking on
|
|
17
|
+
# the browser's own lifetime.
|
|
18
|
+
class Launcher
|
|
19
|
+
def initialize(spawn: ->(*args) { Process.spawn(*args) })
|
|
20
|
+
@spawn = spawn
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def launch(binary:, profile_dir:, port:, url: nil)
|
|
24
|
+
argv = [binary, "--user-data-dir=#{profile_dir}", "--remote-debugging-port=#{port}",
|
|
25
|
+
"--no-first-run", "--no-default-browser-check"]
|
|
26
|
+
argv << url if url
|
|
27
|
+
pid = @spawn.call(*argv, %i[out err] => File::NULL, pgroup: true)
|
|
28
|
+
Process.detach(pid)
|
|
29
|
+
pid
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require_relative "browsers"
|
|
3
|
+
require_relative "cdp"
|
|
4
|
+
require_relative "errors"
|
|
5
|
+
require_relative "launcher"
|
|
6
|
+
|
|
7
|
+
module Portage
|
|
8
|
+
module Cli
|
|
9
|
+
module BrowserProfile
|
|
10
|
+
# `portage browser profile init|open|status` (docs/plans/
|
|
11
|
+
# buy-skill-and-local-browser.md Phase 6): a dedicated Chromium
|
|
12
|
+
# profile directory under ~/.portage/browser/<browser>/profile,
|
|
13
|
+
# launched with remote debugging bound to that profile only —
|
|
14
|
+
# never the browser's own default profile, and never a directory
|
|
15
|
+
# this class didn't create itself. The user signs into their
|
|
16
|
+
# shopping sites in this profile once; nothing here ever reads a
|
|
17
|
+
# credential, cookie or autofill store from it (that's Chromium's
|
|
18
|
+
# own job once the shopper is looking at the page).
|
|
19
|
+
class Profile
|
|
20
|
+
ROOT = File.join(Dir.home, ".portage", "browser").freeze
|
|
21
|
+
DEFAULT_PORT = 9223
|
|
22
|
+
|
|
23
|
+
attr_reader :browser, :port, :dir
|
|
24
|
+
|
|
25
|
+
def initialize(browser: "chrome", root: ROOT, port: DEFAULT_PORT, launcher: Launcher.new, cdp: Cdp,
|
|
26
|
+
poll_attempts: 20, poll_interval: 0.25, sleep_fn: ->(s) { sleep s })
|
|
27
|
+
raise ArgumentError, "unsupported browser #{browser.inspect} (want one of #{Browsers::CHROMIUM})" unless
|
|
28
|
+
Browsers::CHROMIUM.include?(browser)
|
|
29
|
+
|
|
30
|
+
@browser = browser
|
|
31
|
+
@dir = File.join(root, browser, "profile")
|
|
32
|
+
@port = port
|
|
33
|
+
@launcher = launcher
|
|
34
|
+
@cdp = cdp
|
|
35
|
+
@poll_attempts = poll_attempts
|
|
36
|
+
@poll_interval = poll_interval
|
|
37
|
+
@sleep_fn = sleep_fn
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Never launches anything — just makes sure the dedicated profile
|
|
41
|
+
# directory exists, so `portage browser profile open` always has
|
|
42
|
+
# somewhere of its own to point `--user-data-dir` at.
|
|
43
|
+
def init!
|
|
44
|
+
FileUtils.mkdir_p(@dir)
|
|
45
|
+
{ browser: @browser, dir: @dir, port: @port, created: true }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @return [Hash] `running:` plus, when running, whatever
|
|
49
|
+
# `/json/version` reports (Browser, protocolVersion, …) — no
|
|
50
|
+
# HTTP request is ever made to anything but 127.0.0.1:`port`.
|
|
51
|
+
def status
|
|
52
|
+
version = @cdp.version(port: @port)
|
|
53
|
+
base = { running: !version.nil?, browser: @browser, dir: @dir, port: @port }
|
|
54
|
+
version ? base.merge(version) : base
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Launches the browser if it isn't already running on this
|
|
58
|
+
# profile's port, then either opens a new tab at `url` or hands
|
|
59
|
+
# back the first existing tab. Idempotent: calling this against an
|
|
60
|
+
# already-running profile just attaches.
|
|
61
|
+
def open!(url: nil)
|
|
62
|
+
launch! unless status[:running]
|
|
63
|
+
attach(url)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def launch!
|
|
69
|
+
binary = Browsers.binary_for(@browser)
|
|
70
|
+
raise BrowserNotFoundError, "#{@browser} isn't installed on this machine" unless binary
|
|
71
|
+
|
|
72
|
+
FileUtils.mkdir_p(@dir)
|
|
73
|
+
@launcher.launch(binary: binary, profile_dir: @dir, port: @port)
|
|
74
|
+
wait_until_running!
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def wait_until_running!
|
|
78
|
+
@poll_attempts.times do
|
|
79
|
+
return true if status[:running]
|
|
80
|
+
|
|
81
|
+
@sleep_fn.call(@poll_interval)
|
|
82
|
+
end
|
|
83
|
+
raise LaunchError, "#{@browser} didn't answer its remote-debugging port (#{@port}) in time"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def attach(url)
|
|
87
|
+
target = url ? @cdp.new_tab(port: @port, url: url) : @cdp.list(port: @port).first
|
|
88
|
+
{ running: true, browser: @browser, dir: @dir, port: @port, target: target }
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require_relative "browser_profile/errors"
|
|
2
|
+
require_relative "browser_profile/browsers"
|
|
3
|
+
require_relative "browser_profile/cdp"
|
|
4
|
+
require_relative "browser_profile/cdp_socket"
|
|
5
|
+
require_relative "browser_profile/allowlist"
|
|
6
|
+
require_relative "browser_profile/bridge"
|
|
7
|
+
require_relative "browser_profile/launcher"
|
|
8
|
+
require_relative "browser_profile/profile"
|
|
9
|
+
|
|
10
|
+
module Portage
|
|
11
|
+
module Cli
|
|
12
|
+
# `portage browser profile init|open|status` — the Portage browser
|
|
13
|
+
# profile (docs/plans/buy-skill-and-local-browser.md Phase 6, Tier B).
|
|
14
|
+
# A dedicated Chromium-family profile directory under
|
|
15
|
+
# ~/.portage/browser/, never the browser's own default profile, that
|
|
16
|
+
# `portage buy --handoff-target profile` drives as a WebMCP bridge
|
|
17
|
+
# (see Bridge) limited to a domain allowlist (Allowlist) — the store
|
|
18
|
+
# being bought from, plus its checkout host once known. The user signs
|
|
19
|
+
# into their shopping sites in it once; Portage never reads a
|
|
20
|
+
# credential/cookie/autofill store from it, never touches a payment
|
|
21
|
+
# field, and never clicks pay.
|
|
22
|
+
module BrowserProfile
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|