pray-cli 1.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.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +47 -0
  3. data/LICENSE.md +21 -0
  4. data/README.md +125 -0
  5. data/SECURITY.md +28 -0
  6. data/bin/polyrun +108 -0
  7. data/bin/pray +26 -0
  8. data/lib/pray/archive.rb +68 -0
  9. data/lib/pray/auth_client.rb +97 -0
  10. data/lib/pray/cli/commands/auth.rb +42 -0
  11. data/lib/pray/cli/commands/distribution.rb +17 -0
  12. data/lib/pray/cli/commands/init.rb +62 -0
  13. data/lib/pray/cli/commands/meta.rb +15 -0
  14. data/lib/pray/cli/commands/packages.rb +107 -0
  15. data/lib/pray/cli/commands/trust.rb +73 -0
  16. data/lib/pray/cli/commands/workflow.rb +126 -0
  17. data/lib/pray/cli/help.rb +168 -0
  18. data/lib/pray/cli/helpers.rb +166 -0
  19. data/lib/pray/cli/parse.rb +136 -0
  20. data/lib/pray/cli/parse_auth.rb +91 -0
  21. data/lib/pray/cli/parse_trust.rb +152 -0
  22. data/lib/pray/cli/suggest.rb +57 -0
  23. data/lib/pray/cli.rb +117 -0
  24. data/lib/pray/confess.rb +113 -0
  25. data/lib/pray/config.rb +52 -0
  26. data/lib/pray/constraint.rb +80 -0
  27. data/lib/pray/destination.rb +158 -0
  28. data/lib/pray/dotenv.rb +46 -0
  29. data/lib/pray/environment.rb +41 -0
  30. data/lib/pray/error.rb +55 -0
  31. data/lib/pray/format_manifest.rb +255 -0
  32. data/lib/pray/format_serialize.rb +135 -0
  33. data/lib/pray/git_sources.rb +228 -0
  34. data/lib/pray/hashing.rb +59 -0
  35. data/lib/pray/invocation.rb +110 -0
  36. data/lib/pray/literal.rb +382 -0
  37. data/lib/pray/lockfile.rb +259 -0
  38. data/lib/pray/lockfile_serialize.rb +125 -0
  39. data/lib/pray/manifest.rb +126 -0
  40. data/lib/pray/manifest_formatter.rb +56 -0
  41. data/lib/pray/manifest_json.rb +128 -0
  42. data/lib/pray/manifest_parser.rb +152 -0
  43. data/lib/pray/manifest_parser_blocks.rb +216 -0
  44. data/lib/pray/manifest_parser_helpers.rb +208 -0
  45. data/lib/pray/materialize.rb +120 -0
  46. data/lib/pray/package_spec.rb +245 -0
  47. data/lib/pray/path_safety.rb +41 -0
  48. data/lib/pray/plan.rb +83 -0
  49. data/lib/pray/project_context.rb +67 -0
  50. data/lib/pray/publish.rb +162 -0
  51. data/lib/pray/registry.rb +355 -0
  52. data/lib/pray/render.rb +360 -0
  53. data/lib/pray/resolve.rb +396 -0
  54. data/lib/pray/resolve_context.rb +19 -0
  55. data/lib/pray/serve.rb +130 -0
  56. data/lib/pray/serve_federation.rb +109 -0
  57. data/lib/pray/session.rb +90 -0
  58. data/lib/pray/ssh_agent.rb +115 -0
  59. data/lib/pray/statement_surface.rb +262 -0
  60. data/lib/pray/substitute.rb +50 -0
  61. data/lib/pray/sync.rb +219 -0
  62. data/lib/pray/terminal.rb +30 -0
  63. data/lib/pray/trust.rb +201 -0
  64. data/lib/pray/trust_feed.rb +110 -0
  65. data/lib/pray/trust_ops.rb +202 -0
  66. data/lib/pray/verify.rb +257 -0
  67. data/lib/pray/version.rb +6 -0
  68. data/lib/pray-cli.rb +4 -0
  69. data/lib/pray.rb +46 -0
  70. data/pray-cli.gemspec +48 -0
  71. metadata +187 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e9a9ce52fe79c346f5ea5bf9516fb6ad813da69028fcf27c4fe166946fec77fc
4
+ data.tar.gz: 39062dc10eee030334edda7c10a9a71749a93c887cf2483f218ab7a9ceefcb68
5
+ SHA512:
6
+ metadata.gz: 8097c2dab4ec198b55d8fc8803ed602f82fa6afef6096602b539f25b501b597da0bcd53108de519422b2216b624d0060e21020cb7abf531e700eaaa5a83eeac5
7
+ data.tar.gz: f8bde377e7a44de1aeec8591c92e4f478e2758386067585ac17c07afbb0af71946c37dbd7f6a58b4035ced2e6141f32ea2cd20372f0eafe195c1075ea43d3a37
data/CHANGELOG.md ADDED
@@ -0,0 +1,47 @@
1
+ # CHANGELOG
2
+
3
+ ## Unreleased
4
+
5
+ ## 1.6.0 (2026-07-29)
6
+
7
+ - Run tests with polyrun parallel RSpec, coverage gate, Makefile lint/test, and RBS validate.
8
+ - Implement HTTP `login` (passkey and ssh-agent), `confess`, and `sync`.
9
+ - Implement full `trust` CLI against `trust.toml` (list/show/add-key/remove-key/set-*/import-*/check).
10
+ - Serve federation discovery, sync index/package, and confession submit over HTTP.
11
+
12
+ ## 1.5.2 (2026-07-29)
13
+
14
+ - Put the Prayfile positioning line into the gem summary for RubyGems search.
15
+
16
+ ## 1.5.1 (2026-07-29)
17
+
18
+ - Add `rake build` / `rake release` for gem packaging and RubyGems push.
19
+ - Rework gem summary, description, and README opener to match Prayfile positioning.
20
+
21
+ ## 1.5.0 (2026-07-27)
22
+
23
+ - Add `((pray:symbol))` templating with project-wide `pray do` / `template do` symbol maps.
24
+ - Substitute symbols in rendered fragments, local embeds, and UTF-8 provisioned files.
25
+ - Accept Gemfile-like Ruby surface sugar: `{…}` blocks, top-level `;`, and optional call parentheses.
26
+ - Accept `pray` / `use` / `include` / `package` as package declaration aliases of `agent`.
27
+ - Parse and apply `compose`, `tree`, and `file:` destinations with role-based export selection and scoped render/provision.
28
+ - Exercise the shared `testdata/shared` Prayfile corpus alongside Rust and TypeScript.
29
+ - Rewrite Prayfile with `pray format` (`fmt`) to the recommended compose/tree/file destination DSL.
30
+
31
+ ## 1.1.0 (2026-07-14)
32
+
33
+ - Add environment-aware rendering with `group` blocks and `--env` or `PRAY_ENV`.
34
+ - Add global `--path` and `--file-path` flags with `PRAY_PATH`, `PRAY_FILE_PATH`, and project `.env` support.
35
+ - Record the selected environment in `Prayfile.lock`.
36
+ - Improve CLI help with grouped commands, per-command help, and suggestions for unknown commands.
37
+ - Add `--no-input` to skip interactive prompts.
38
+ - Honor `PRAY_NO_COLOR` and `NO_COLOR` for plain terminal output.
39
+ - Refresh git distribution caches on install when a locked revision is missing locally.
40
+
41
+ ## 1.0.0 (2026-07-13)
42
+
43
+ - Initial RubyGems release of `pray-cli`.
44
+ - Resolve local path packages and git distribution sources.
45
+ - Publish to local distribution roots and serve over HTTP.
46
+ - Install, update, render, verify, and drift workflows with `Prayfile.lock`.
47
+ - Git distribution integration tests for install, locked revision, and update.
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 pray contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # pray-cli
2
+
3
+ Ruby `pray` CLI and library for [Prayfile](https://pray.kisko.dev): a package manager for the language placed before inference.
4
+
5
+ Resolve, lock, render, verify, and publish shared instructions, policies, templates, and related input as versioned packages. Consumes path, git, and registry distribution points. The installed executable is `pray`.
6
+
7
+ **Website:** [pray.kisko.dev](https://pray.kisko.dev)
8
+
9
+ **Maintainer:** Andrei Makarov ([contact@kiskolabs.com](mailto:contact@kiskolabs.com))
10
+
11
+ **Repository:** [kiskolabs/pray](https://github.com/kiskolabs/pray)
12
+
13
+ **Community docs:** [CHANGELOG.md](CHANGELOG.md) · [LICENSE.md](LICENSE.md) · [SECURITY.md](SECURITY.md)
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ gem install pray-cli
19
+ ```
20
+
21
+ From this repository:
22
+
23
+ ```bash
24
+ cd rubygems/pray-cli
25
+ bundle install
26
+ bundle exec pray version
27
+ ```
28
+
29
+ ## Commands
30
+
31
+ Core workflow:
32
+
33
+ ```bash
34
+ pray init
35
+ pray install
36
+ pray plan
37
+ pray verify
38
+ pray drift
39
+ pray manifest
40
+ ```
41
+
42
+ Package management:
43
+
44
+ ```bash
45
+ pray add sample/base "~> 1.0" --path packages/base
46
+ pray remove sample/base
47
+ pray update
48
+ pray unlock sample/base
49
+ pray list
50
+ pray tree
51
+ pray explain sample/base
52
+ pray outdated
53
+ ```
54
+
55
+ Package authoring:
56
+
57
+ ```bash
58
+ pray prayer init
59
+ pray package
60
+ ```
61
+
62
+ Auth, trust, and federation:
63
+
64
+ ```bash
65
+ pray login --server URL --email EMAIL --passkey-key PATH --credential-id ID
66
+ pray confess sample/base --accepted
67
+ pray sync --root PATH --peer URL
68
+ pray trust list
69
+ pray trust show
70
+ pray trust add-key KEY --match-prefix PREFIX
71
+ pray trust check [SOURCE]
72
+ ```
73
+
74
+ Maintenance:
75
+
76
+ ```bash
77
+ pray render
78
+ pray format
79
+ pray clean
80
+ ```
81
+
82
+ Deferred in this release (clear unsupported errors): `pray_ssh` sources, `serve --stdio`, confess/sync over `pray+ssh`, trust `--host-key` for `pray+ssh`.
83
+
84
+ Git sources clone into `.pray/cache/git`, discover distribution roots at `v1/packages` or `prayers/v1/packages`, and resolve packages through registry metadata.
85
+
86
+ Local publish writes `v1/index.json`, package metadata, and `.praypkg` artifacts. `serve` exposes the distribution tree over HTTP, including federation discovery and sync endpoints.
87
+
88
+ ## Layout
89
+
90
+ ```
91
+ lib/pray/
92
+ literal.rb Prayfile and prayspec literal parser
93
+ manifest.rb Prayfile parser and model
94
+ package_spec.rb prayspec parser
95
+ lockfile.rb Prayfile.lock read/write
96
+ resolve.rb dependency resolution
97
+ render.rb target rendering and skill materialization
98
+ verify.rb verify and drift checks
99
+ registry.rb HTTP and local registry resolution
100
+ archive.rb .praypkg archive read/write (requires tar and zstd)
101
+ cli.rb command dispatch
102
+ ```
103
+
104
+ ## Tests
105
+
106
+ ```bash
107
+ make lint
108
+ make test
109
+ ```
110
+
111
+ Coverage-gated parallel run:
112
+
113
+ ```bash
114
+ POLYRUN_COVERAGE=1 bundle exec polyrun parallel-rspec --workers 5 --merge-failures
115
+ ```
116
+
117
+ From the repository root:
118
+
119
+ ```bash
120
+ make ruby-test
121
+ ```
122
+
123
+ ## Compatibility
124
+
125
+ Manifest hashing, lockfile fields, pray markers, and tree hashes match the Rust reference for path-based projects. Registry resolution uses HTTP or a local distribution tree with `v1/packages/*.json` metadata.
data/SECURITY.md ADDED
@@ -0,0 +1,28 @@
1
+ # SECURITY
2
+
3
+ ## Reporting a Vulnerability
4
+
5
+ Do NOT open a public GitHub issue for security vulnerabilities.
6
+
7
+ Email security details to: security@kiskolabs.com
8
+
9
+ Include: description, steps to reproduce, potential impact, and suggested fix (if available).
10
+
11
+ ### Response Timeline
12
+
13
+ - We will acknowledge receipt of your report
14
+ - We will provide an initial assessment
15
+ - We will keep you informed of our progress and resolution timeline
16
+
17
+ ### Disclosure Policy
18
+
19
+ - We will work with you to understand and resolve the issue
20
+ - We will credit you for the discovery (unless you prefer to remain anonymous)
21
+ - We will publish a security advisory after the vulnerability is patched
22
+ - We will coordinate public disclosure with you
23
+
24
+ ## Automation Security
25
+
26
+ * Context Isolation: It is strictly forbidden to include production credentials, API keys, or Personally Identifiable Information (PII) in prompts sent to third-party LLMs or automation services.
27
+
28
+ * Supply Chain: All automated dependencies must be verified.
data/bin/polyrun ADDED
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env bash
2
+ # Delegates to `bundle exec polyrun` with repo config.
3
+ # - No args → same as `polyrun` with no subcommand (auto parallel RSpec from polyrun.yml / spec/**/*_spec.rb).
4
+ # - Path-like argv (spec files, globs, dirs) → polyrun implicit parallel (path-only argv).
5
+ # - Named subcommands (plan, config, parallel-rspec, ci-shard-rspec, …) → pass through.
6
+ # - Otherwise (RSpec flags first, e.g. --format) → `start --workers N -- bundle exec rspec "$@"`.
7
+ # Globals: -c/--config, -v/--verbose, -h/--help, --help (same as bundle exec polyrun).
8
+ # Workers: POLYRUN_WORKERS (default 5, max 10, min 1) — read by polyrun run-shards/start.
9
+ set -eo pipefail
10
+ ROOT="$(cd "$(dirname "$0")/.." && pwd)"
11
+ cd "$ROOT"
12
+
13
+ W="${POLYRUN_WORKERS:-5}"
14
+ case "$W" in ''|*[!0-9]*) W=5 ;; esac
15
+ if [ "$W" -gt 10 ]; then W=10; fi
16
+ if [ "$W" -lt 1 ]; then W=1; fi
17
+
18
+ CONFIG="${POLYRUN_CONFIG:-polyrun.yml}"
19
+ polyrun_prefix=()
20
+ have_config=0
21
+
22
+ while [ $# -gt 0 ]; do
23
+ case "$1" in
24
+ -c|--config)
25
+ polyrun_prefix+=("$1" "$2")
26
+ CONFIG="$2"
27
+ have_config=1
28
+ shift 2
29
+ ;;
30
+ -v|--verbose)
31
+ polyrun_prefix+=("$1")
32
+ shift
33
+ ;;
34
+ -h|--help)
35
+ if [ "$have_config" -eq 0 ]; then
36
+ exec bundle exec polyrun "${polyrun_prefix[@]}" -c "$CONFIG" -h
37
+ else
38
+ exec bundle exec polyrun "${polyrun_prefix[@]}" -h
39
+ fi
40
+ ;;
41
+ *)
42
+ break
43
+ ;;
44
+ esac
45
+ done
46
+
47
+ if [ "${1:-}" = "--version" ]; then
48
+ if [ "$have_config" -eq 0 ]; then
49
+ exec bundle exec polyrun "${polyrun_prefix[@]}" -c "$CONFIG" version
50
+ else
51
+ exec bundle exec polyrun "${polyrun_prefix[@]}" version
52
+ fi
53
+ fi
54
+
55
+ is_polyrun_command() {
56
+ case "$1" in
57
+ plan|prepare|merge-coverage|report-coverage|report-junit|report-timing|env|config|merge-timing|\
58
+ db:setup-template|db:setup-shard|db:clone-shards|run-shards|parallel-rspec|start|build-paths|\
59
+ ci-shard-run|ci-shard-rspec|init|queue|quick|help|version)
60
+ return 0 ;;
61
+ *)
62
+ return 1 ;;
63
+ esac
64
+ }
65
+
66
+ # True if argv should use polyrun implicit path parallel (any path-like token).
67
+ implicit_path_in_argv() {
68
+ for a in "$@"; do
69
+ case "$a" in
70
+ -*) [ "$a" = "-" ] || continue ;;
71
+ esac
72
+ case "$a" in
73
+ *.rb|spec/*|test/*|./|/*|*[\*\?[]*) return 0 ;;
74
+ esac
75
+ [ -e "$a" ] && return 0
76
+ done
77
+ return 1
78
+ }
79
+
80
+ run_polyrun() {
81
+ if [ "$have_config" -eq 0 ]; then
82
+ exec bundle exec polyrun "${polyrun_prefix[@]}" -c "$CONFIG" "$@"
83
+ else
84
+ exec bundle exec polyrun "${polyrun_prefix[@]}" "$@"
85
+ fi
86
+ }
87
+
88
+ exec_parallel_rspec() {
89
+ if [ "$have_config" -eq 0 ]; then
90
+ exec bundle exec polyrun "${polyrun_prefix[@]}" -c "$CONFIG" start --workers "$W" -- bundle exec rspec "$@"
91
+ else
92
+ exec bundle exec polyrun "${polyrun_prefix[@]}" start --workers "$W" -- bundle exec rspec "$@"
93
+ fi
94
+ }
95
+
96
+ if [ $# -eq 0 ]; then
97
+ run_polyrun
98
+ fi
99
+
100
+ if is_polyrun_command "$1"; then
101
+ run_polyrun "$@"
102
+ fi
103
+
104
+ if implicit_path_in_argv "$@"; then
105
+ run_polyrun "$@"
106
+ fi
107
+
108
+ exec_parallel_rspec "$@"
data/bin/pray ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ Encoding.default_external = Encoding::UTF_8
5
+ Encoding.default_internal = Encoding::UTF_8
6
+
7
+ gemfile = File.expand_path("../Gemfile", __dir__)
8
+ if File.exist?(gemfile)
9
+ ENV["BUNDLE_GEMFILE"] ||= gemfile
10
+ require "bundler/setup"
11
+ end
12
+
13
+ require_relative "../lib/pray-cli"
14
+
15
+ exit_code = 0
16
+ begin
17
+ Pray::CLI.run(ARGV)
18
+ rescue Pray::Error => error
19
+ warn error
20
+ exit_code = error.exit_code
21
+ rescue => error
22
+ warn error
23
+ exit_code = 1
24
+ end
25
+
26
+ exit exit_code
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "open3"
5
+ require "fileutils"
6
+ require "tempfile"
7
+
8
+ module Pray
9
+ module Archive
10
+ module_function
11
+
12
+ def build_package_archive_bytes(package)
13
+ prayspec_path = Resolve.find_prayspec_file(package.root)
14
+ prayspec_name = File.basename(prayspec_path)
15
+ metadata = package_metadata_json(package)
16
+
17
+ Dir.mktmpdir("pray-package-") do |staging|
18
+ File.write(File.join(staging, "metadata.json"), metadata)
19
+ File.write(File.join(staging, prayspec_name), File.binread(prayspec_path))
20
+ package.spec.files.each do |file|
21
+ destination = File.join(staging, file)
22
+ FileUtils.mkdir_p(File.dirname(destination))
23
+ File.binwrite(destination, File.binread(File.join(package.root, file)))
24
+ end
25
+
26
+ tar_bytes, status = Open3.capture2("tar", "-cf", "-", "-C", staging, ".")
27
+ raise Error.integrity("failed to build package tar archive") unless status.success?
28
+
29
+ zstd_bytes, status = Open3.capture2("zstd", "-q", "-c", stdin_data: tar_bytes)
30
+ unless status.success?
31
+ raise Error.unsupported("zstd is required to build package archives")
32
+ end
33
+
34
+ zstd_bytes
35
+ end
36
+ end
37
+
38
+ def write_package_archive(package, output_path)
39
+ FileUtils.mkdir_p(File.dirname(output_path))
40
+ File.binwrite(output_path, build_package_archive_bytes(package))
41
+ end
42
+
43
+ def unpack_praypkg(artifact_bytes, output_directory)
44
+ FileUtils.mkdir_p(output_directory)
45
+ tar_bytes, status = Open3.capture2("zstd", "-d", "-q", "-c", stdin_data: artifact_bytes)
46
+ unless status.success?
47
+ raise Error.unsupported("zstd is required to unpack package archives")
48
+ end
49
+
50
+ _stdout, _stderr, status = Open3.capture3("tar", "-xf", "-", "-C", output_directory, stdin_data: tar_bytes)
51
+ raise Error.integrity("failed to unpack package archive") unless status.success?
52
+ end
53
+
54
+ def package_archive_path(package_name, version)
55
+ slug = package_name.tr("/", "-")
56
+ File.join(".pray", "packages", "#{slug}-#{version}.praypkg")
57
+ end
58
+
59
+ def package_metadata_json(package)
60
+ JSON.generate(
61
+ "name" => package.spec.name,
62
+ "version" => package.spec.version,
63
+ "tree_hash" => package.tree_hash,
64
+ "exports" => package.selected_exports
65
+ )
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "base64"
5
+ require "openssl"
6
+ require_relative "session"
7
+ require_relative "ssh_agent"
8
+
9
+ module Pray
10
+ module AuthClient
11
+ module_function
12
+
13
+ def login_with_passkey(server_url, credential_id, private_key_path, session_root, email:)
14
+ challenge = post_json(
15
+ "#{trim_slash(server_url)}/v1/auth/passkeys/challenge",
16
+ {"credential_id" => credential_id}
17
+ )
18
+ seed = File.binread(private_key_path)
19
+ unless seed.bytesize == 32
20
+ raise Error.unsupported("passkey private key must be 32 raw bytes")
21
+ end
22
+
23
+ signature = Base64.strict_encode64(sign_ed25519(seed, challenge["challenge"].to_s))
24
+ response = post_json(
25
+ "#{trim_slash(server_url)}/v1/auth/passkeys/login",
26
+ {
27
+ "credential_id" => credential_id,
28
+ "challenge_id" => challenge["challenge_id"],
29
+ "signature" => signature
30
+ }
31
+ )
32
+ persist_login(server_url, response, email, "passkey", nil, session_root)
33
+ end
34
+
35
+ def login_with_ssh_agent(server_url, public_key_path, session_root, email:)
36
+ public_key = File.read(public_key_path).strip
37
+ challenge = post_json(
38
+ "#{trim_slash(server_url)}/v1/auth/ssh-keys/challenge",
39
+ {"public_key" => public_key}
40
+ )
41
+ signature = SshAgent.sign(public_key, challenge["challenge"].to_s)
42
+ response = post_json(
43
+ "#{trim_slash(server_url)}/v1/auth/ssh-keys/login",
44
+ {
45
+ "public_key" => public_key,
46
+ "challenge_id" => challenge["challenge_id"],
47
+ "signature" => signature
48
+ }
49
+ )
50
+ persist_login(
51
+ server_url, response, email, "ssh_key", challenge["fingerprint"], session_root
52
+ )
53
+ end
54
+
55
+ def persist_login(server_url, response, email, kind, fingerprint, session_root)
56
+ response_email = response["email"].to_s
57
+ unless response_email == email
58
+ raise Error.resolution(
59
+ "login email mismatch: expected #{email}, got #{response_email}"
60
+ )
61
+ end
62
+
63
+ Session.persist(
64
+ session_root,
65
+ SessionFile.new(
66
+ server_url: server_url,
67
+ email: response_email,
68
+ token: response["token"].to_s,
69
+ kind: kind,
70
+ signer_fingerprint: fingerprint
71
+ )
72
+ )
73
+ end
74
+
75
+ def sign_ed25519(seed, message)
76
+ unless OpenSSL::PKey.respond_to?(:generate_key)
77
+ raise Error.unsupported("OpenSSL Ed25519 support is required for passkey login")
78
+ end
79
+
80
+ key = OpenSSL::PKey.new_raw_private_key("ED25519", seed)
81
+ key.sign(nil, message)
82
+ rescue OpenSSL::PKey::PKeyError => error
83
+ raise Error.unsupported("OpenSSL Ed25519 signing failed: #{error.message}")
84
+ end
85
+
86
+ def post_json(url, body)
87
+ response = Registry.http_post(url, "application/json", JSON.generate(body))
88
+ JSON.parse(response)
89
+ rescue JSON::ParserError => error
90
+ raise Error.parse("auth response", error.message)
91
+ end
92
+
93
+ def trim_slash(value)
94
+ value.to_s.sub(%r{/+\z}, "")
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pray
4
+ module CLI
5
+ def login_command(servers:, email:, mode:, passkey_key: nil, credential_id: nil, public_key: nil)
6
+ session_root = Dir.pwd
7
+ servers.each do |server_url|
8
+ session = case mode
9
+ when :passkey
10
+ AuthClient.login_with_passkey(
11
+ server_url, credential_id, passkey_key, session_root, email: email
12
+ )
13
+ when :ssh_agent
14
+ AuthClient.login_with_ssh_agent(
15
+ server_url, public_key, session_root, email: email
16
+ )
17
+ else
18
+ raise Error.unsupported("unknown login mode: #{mode}")
19
+ end
20
+ puts "logged in as #{session.email} via #{session.kind} on #{server_url}"
21
+ end
22
+ end
23
+
24
+ def confess_command(package:, from_lock:, version:, accepted:, rejected:, note:, url:)
25
+ Confess.submit(
26
+ package: package, from_lock: from_lock, version: version,
27
+ accepted: accepted, rejected: rejected, note: note, url: url
28
+ )
29
+ end
30
+
31
+ def sync_command(root:, peers:)
32
+ peer_sources = if peers.empty?
33
+ Sync.load_sync_peers(root).map { |peer| peer["url"] }
34
+ else
35
+ peers
36
+ end
37
+ summary = Sync.synchronize_registry(root, peer_sources)
38
+ puts "Synchronized #{summary[:packages]} package(s) from #{summary[:peers]} peer(s); " \
39
+ "learned #{summary[:known_peers]} peer(s)"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pray
4
+ module CLI
5
+ def publish_command(roots:, servers:)
6
+ project = resolve_current_project
7
+ roots.each { |root| Publish.publish_to_root(project, root) }
8
+ servers.each { |server| Publish.publish_to_server(project, server) }
9
+ end
10
+
11
+ def serve_command(root:, host:, port:, stdio:)
12
+ raise Error.unsupported("serve --stdio is not implemented yet in pray-cli Ruby") if stdio
13
+
14
+ Serve.run_server(root: root, host: host, port: port)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "fileutils"
5
+
6
+ module Pray
7
+ module CLI
8
+ def manifest_command
9
+ manifest = Pray.parse_manifest(Pray.read_manifest_text(manifest_path))
10
+ puts JSON.pretty_generate(ManifestJson.manifest_fields(manifest.canonicalized))
11
+ end
12
+
13
+ def init_command(targets)
14
+ path = manifest_path
15
+ raise Error.manifest("Prayfile already exists") if File.exist?(path)
16
+
17
+ targets = ["tool_a"] if targets.empty?
18
+ lines = ['prayfile "1"']
19
+ targets.each do |target|
20
+ output = default_output_for_target(target)
21
+ lines << "target :#{target} do"
22
+ lines << " output \"#{output}.md\""
23
+ lines << "end"
24
+ end
25
+ File.write(path, "#{lines.join("\n")}\n")
26
+ end
27
+
28
+ def prayer_init_command
29
+ root = Dir.pwd
30
+ package_name = File.basename(root)
31
+ package_name = "prayer-package" if package_name.strip.empty?
32
+ prayspec_path = File.join(root, "#{package_name}.prayspec")
33
+ raise Error.manifest("package spec already exists: #{prayspec_path}") if File.exist?(prayspec_path)
34
+
35
+ File.write(
36
+ prayspec_path,
37
+ <<~SPEC
38
+ Package::Specification.new do |spec|
39
+ spec.name = "#{package_name}"
40
+ spec.version = "0.1.0"
41
+ spec.summary = "Prayer package"
42
+ spec.files = []
43
+ end
44
+ SPEC
45
+ )
46
+ end
47
+
48
+ def repo_init_command
49
+ distribution_root = repo_distribution_root(Dir.pwd)
50
+ index_path = File.join(distribution_root, "v1", "index.json")
51
+ raise Error.manifest("distribution repo already exists") if File.exist?(index_path)
52
+
53
+ FileUtils.mkdir_p(File.join(distribution_root, "v1", "packages"))
54
+ FileUtils.mkdir_p(File.join(distribution_root, "v1", "artifacts"))
55
+ Publish.write_registry_index(distribution_root, RegistryIndex.new)
56
+ end
57
+
58
+ def repo_distribution_root(root)
59
+ (File.basename(root) == "prayers") ? root : File.join(root, "prayers")
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pray
4
+ module CLI
5
+ def version_command
6
+ puts "pray #{Pray::VERSION}"
7
+ end
8
+
9
+ def clean_command
10
+ remove_path_if_exists(".pray/cache")
11
+ remove_path_if_exists(".pray/vendor")
12
+ remove_path_if_exists(".pray/state.json")
13
+ end
14
+ end
15
+ end