aspera-cli 4.26.1 → 4.27.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 (145) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +300 -22
  4. data/CONTRIBUTING.md +100 -12
  5. data/bin/ascli +5 -5
  6. data/docs/README.md +10757 -0
  7. data/docs/test-mcp-with-ai.md +202 -0
  8. data/lib/aspera/agent/base.rb +20 -6
  9. data/lib/aspera/agent/connect.rb +53 -16
  10. data/lib/aspera/agent/desktop.rb +52 -17
  11. data/lib/aspera/agent/direct.rb +27 -25
  12. data/lib/aspera/agent/factory.rb +4 -0
  13. data/lib/aspera/agent/httpgw.rb +2 -2
  14. data/lib/aspera/agent/node.rb +34 -2
  15. data/lib/aspera/agent/transferd.rb +39 -3
  16. data/lib/aspera/api/alee.rb +1 -1
  17. data/lib/aspera/api/aoc.rb +56 -51
  18. data/lib/aspera/api/cos_node.rb +2 -2
  19. data/lib/aspera/api/faspex.rb +22 -20
  20. data/lib/aspera/api/httpgw.rb +4 -4
  21. data/lib/aspera/api/node.rb +33 -30
  22. data/lib/aspera/api/queries/bss_subscription_account.graphql +18 -0
  23. data/lib/aspera/api/queries/bss_subscription_usage.graphql +18 -0
  24. data/lib/aspera/ascmd.rb +25 -23
  25. data/lib/aspera/ascp/installation.rb +43 -40
  26. data/lib/aspera/ascp/management.rb +2 -3
  27. data/lib/aspera/cli/ascp_actions.rb +155 -0
  28. data/lib/aspera/cli/async_transfer_store.rb +81 -0
  29. data/lib/aspera/cli/bootstrapper.rb +197 -0
  30. data/lib/aspera/cli/command_registry.rb +145 -0
  31. data/lib/aspera/cli/command_spec.rb +138 -0
  32. data/lib/aspera/cli/context.rb +71 -0
  33. data/lib/aspera/cli/error.rb +16 -0
  34. data/lib/aspera/cli/extended_value.rb +5 -3
  35. data/lib/aspera/cli/formatter.rb +122 -209
  36. data/lib/aspera/cli/gem_checker.rb +65 -0
  37. data/lib/aspera/cli/hints.rb +2 -2
  38. data/lib/aspera/cli/http.rb +218 -0
  39. data/lib/aspera/cli/info.rb +2 -0
  40. data/lib/aspera/cli/mailer.rb +97 -0
  41. data/lib/aspera/cli/mcp_tool.rb +198 -0
  42. data/lib/aspera/cli/options.schema.yaml +364 -7
  43. data/lib/aspera/cli/{manager.rb → parser.rb} +337 -144
  44. data/lib/aspera/cli/plugins/alee.rb +20 -22
  45. data/lib/aspera/cli/plugins/aoc.rb +1173 -849
  46. data/lib/aspera/cli/plugins/ats.rb +200 -161
  47. data/lib/aspera/cli/plugins/base.rb +470 -107
  48. data/lib/aspera/cli/plugins/basic_auth.rb +14 -4
  49. data/lib/aspera/cli/plugins/config.rb +434 -1032
  50. data/lib/aspera/cli/plugins/console.rb +106 -64
  51. data/lib/aspera/cli/plugins/cos.rb +44 -32
  52. data/lib/aspera/cli/plugins/factory.rb +7 -4
  53. data/lib/aspera/cli/plugins/faspex.rb +296 -259
  54. data/lib/aspera/cli/plugins/faspex5.rb +592 -344
  55. data/lib/aspera/cli/plugins/faspio.rb +49 -51
  56. data/lib/aspera/cli/plugins/httpgw.rb +18 -25
  57. data/lib/aspera/cli/plugins/mcp.rb +279 -0
  58. data/lib/aspera/cli/plugins/node.rb +1001 -797
  59. data/lib/aspera/cli/plugins/oauth.rb +7 -10
  60. data/lib/aspera/cli/plugins/orchestrator.rb +111 -139
  61. data/lib/aspera/cli/plugins/preview.rb +183 -142
  62. data/lib/aspera/cli/plugins/server.rb +125 -80
  63. data/lib/aspera/cli/plugins/shares.rb +301 -107
  64. data/lib/aspera/cli/preset_actions.rb +139 -0
  65. data/lib/aspera/cli/preset_manager.rb +236 -0
  66. data/lib/aspera/cli/result.rb +360 -0
  67. data/lib/aspera/cli/runner.rb +473 -0
  68. data/lib/aspera/cli/secret_finder.rb +40 -0
  69. data/lib/aspera/cli/special_values.rb +1 -0
  70. data/lib/aspera/cli/sync_actions.rb +84 -56
  71. data/lib/aspera/cli/terminal_formatter.rb +65 -0
  72. data/lib/aspera/cli/transfer_actions.rb +83 -0
  73. data/lib/aspera/cli/transfer_agent.rb +116 -51
  74. data/lib/aspera/cli/transfer_progress.rb +9 -9
  75. data/lib/aspera/cli/vault_manager.rb +57 -0
  76. data/lib/aspera/cli/version.rb +1 -1
  77. data/lib/aspera/cli/wizard.rb +26 -26
  78. data/lib/aspera/command_line_builder.rb +23 -23
  79. data/lib/aspera/data_repository.rb +6 -1
  80. data/lib/aspera/dot_container.rb +5 -5
  81. data/lib/aspera/environment.rb +39 -13
  82. data/lib/aspera/exec_spec.rb +13 -0
  83. data/lib/aspera/faspex_gw.rb +2 -2
  84. data/lib/aspera/faspex_postproc.rb +4 -3
  85. data/lib/aspera/graphql.rb +35 -0
  86. data/lib/aspera/hash_ext.rb +6 -0
  87. data/lib/aspera/json_rpc/client.rb +62 -0
  88. data/lib/aspera/json_rpc/version.rb +7 -0
  89. data/lib/aspera/keychain/base.rb +1 -1
  90. data/lib/aspera/keychain/encrypted_hash.rb +2 -2
  91. data/lib/aspera/keychain/factory.rb +4 -4
  92. data/lib/aspera/keychain/macos_security.rb +4 -4
  93. data/lib/aspera/link_header.rb +82 -0
  94. data/lib/aspera/log.rb +23 -5
  95. data/lib/aspera/markdown.rb +85 -2
  96. data/lib/aspera/nagios.rb +2 -2
  97. data/lib/aspera/node_simulator.rb +3 -1
  98. data/lib/aspera/oauth/base.rb +10 -10
  99. data/lib/aspera/oauth/boot.rb +43 -0
  100. data/lib/aspera/oauth/factory.rb +41 -15
  101. data/lib/aspera/oauth/jwt.rb +4 -4
  102. data/lib/aspera/oauth/web.rb +4 -4
  103. data/lib/aspera/oauth.rb +1 -0
  104. data/lib/aspera/persistency_action_once.rb +10 -10
  105. data/lib/aspera/persistency_folder.rb +10 -3
  106. data/lib/aspera/preview/file_types.rb +7 -3
  107. data/lib/aspera/preview/generator.rb +3 -3
  108. data/lib/aspera/preview/utils.rb +1 -1
  109. data/lib/aspera/products/connect.rb +4 -1
  110. data/lib/aspera/products/other.rb +1 -1
  111. data/lib/aspera/products/transferd.rb +3 -3
  112. data/lib/aspera/proxy_auto_config.rb +10 -9
  113. data/lib/aspera/rest.rb +18 -40
  114. data/lib/aspera/rest_error_analyzer.rb +7 -3
  115. data/lib/aspera/rest_errors_aspera.rb +0 -10
  116. data/lib/aspera/rest_list.rb +13 -6
  117. data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +14 -4
  118. data/lib/aspera/schema/IBM Aspera on Cloud API-0.2.6-enhanced.yaml +2730 -648
  119. data/lib/aspera/schema/async_tables.yaml +361 -0
  120. data/lib/aspera/schema/documentation.rb +57 -26
  121. data/lib/aspera/schema/reader.rb +60 -11
  122. data/lib/aspera/schema/registry.rb +50 -10
  123. data/lib/aspera/secret_hider.rb +11 -2
  124. data/lib/aspera/ssl.rb +4 -4
  125. data/lib/aspera/sync/conf.schema.yaml +2 -2
  126. data/lib/aspera/sync/database.rb +14 -10
  127. data/lib/aspera/sync/operations.rb +15 -18
  128. data/lib/aspera/temp_file_manager.rb +5 -1
  129. data/lib/aspera/timer_limiter.rb +1 -1
  130. data/lib/aspera/transfer/faux_file.rb +24 -11
  131. data/lib/aspera/transfer/parameters.rb +27 -25
  132. data/lib/aspera/transfer/result.rb +74 -0
  133. data/lib/aspera/transfer/resumer.rb +7 -7
  134. data/lib/aspera/transfer/spec.rb +18 -1
  135. data/lib/aspera/transfer/spec.schema.yaml +26 -8
  136. data/lib/aspera/transfer/uri.rb +1 -1
  137. data/lib/aspera/uri_reader.rb +2 -2
  138. data/lib/aspera/web_auth.rb +2 -2
  139. data/lib/aspera/web_server_simple.rb +8 -5
  140. data/lib/aspera/yaml.rb +2 -1
  141. data.tar.gz.sig +0 -0
  142. metadata +45 -16
  143. metadata.gz.sig +0 -0
  144. data/lib/aspera/cli/main.rb +0 -480
  145. data/lib/aspera/json_rpc.rb +0 -51
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aspera
4
+ module Cli
5
+ # Declares a positional argument consumed by a command.
6
+ # Declaration order in a command's `arguments:` array defines parsing order.
7
+ # Mandatory arguments must come before optional ones.
8
+ #
9
+ # @!attribute name [Symbol] Name used in help and error messages
10
+ # @!attribute description [String] User-facing description
11
+ # @!attribute type [Class, Array<Class>, :identifier] Validated type; :identifier triggers instance_identifier
12
+ # @!attribute mandatory [Boolean] Default true; optional args must come after all mandatory ones
13
+ # @!attribute multiple [Boolean, String] true: consume all remaining; String: consume until named marker
14
+ # @!attribute default [Object, nil] Default value when mandatory: false and no argument provided
15
+ # @!attribute schema [String, nil] JSON schema name for validation and --help introspection
16
+ # @!attribute bulk [Boolean] When true, wraps read+loop for bulk mode (Array if --bulk yes)
17
+ # @!attribute lookup [Symbol, nil] Instance method name for percent-selector resolution (only used when type: :identifier)
18
+ # @!attribute allowed [Array<Symbol>, nil] Allowed Symbol values; when set, type is forced to Symbol and accept_list is applied
19
+ # @!attribute interactive [Boolean] When true, sets ask_missing_mandatory before resolving so interactive prompting is triggered when no CLI args are provided
20
+ ArgumentSpec = Struct.new(
21
+ :name,
22
+ :description,
23
+ :type,
24
+ :mandatory,
25
+ :multiple,
26
+ :default,
27
+ :schema,
28
+ :bulk,
29
+ :lookup,
30
+ :allowed,
31
+ :interactive,
32
+ keyword_init: true
33
+ ) do
34
+ def initialize(**kwargs)
35
+ kwargs[:mandatory] = true if kwargs[:mandatory].nil?
36
+ kwargs[:multiple] = false if kwargs[:multiple].nil?
37
+ kwargs[:bulk] = false if kwargs[:bulk].nil?
38
+ kwargs[:interactive] = false if kwargs[:interactive].nil?
39
+ super
40
+ end
41
+ end
42
+
43
+ # Declares an option referenced by name from command declarations.
44
+ # Mirrors the existing `options.declare` call but associates the option with
45
+ # the command(s) that use it.
46
+ #
47
+ # @!attribute name [Symbol] Option name (same symbol used in options.declare)
48
+ # @!attribute description [String, nil] User-facing description; nil derives it from schema:
49
+ # @!attribute allowed [Array, nil] Allowed values (forwarded to options.declare)
50
+ # @!attribute default [Object, nil] Default value
51
+ # @!attribute short [String, nil] Single-character short form (e.g. 'x')
52
+ # @!attribute handler [Symbol, Hash, nil]
53
+ # - Symbol: resolved to {o: <plugin instance>, m: <symbol>} at runtime (Category B)
54
+ # - Hash: {o: <object>, m: <method>} used as-is (Category A: singletons / class constants)
55
+ # - nil: option stores its value locally (no delegation)
56
+ # @!attribute deprecation [String, nil] Forwarded to options.declare as deprecation:
57
+ # @!attribute schema [String, nil] JSON schema name; also derives description when nil
58
+ OptionSpec = Struct.new(
59
+ :name,
60
+ :description,
61
+ :allowed,
62
+ :default,
63
+ :short,
64
+ :handler, # kept as-is: this is the option accessor delegation, not a command action
65
+ :deprecation,
66
+ :schema,
67
+ keyword_init: true
68
+ )
69
+
70
+ # Declares a single command node in the flat registry.
71
+ #
72
+ # @!attribute id [Symbol] Unique identifier within its parent's namespace
73
+ # @!attribute parent [Symbol, Array<Symbol>, nil] Full path to parent; nil for root commands
74
+ # @!attribute description [String] User-facing help text
75
+ # @!attribute options [Array<Symbol>] Option names consumed by this command
76
+ # @!attribute arguments [Array<ArgumentSpec>] Positional arguments, in order.
77
+ # The first ArgumentSpec with type: :identifier is treated as the instance
78
+ # identifier for intermediate nodes (consumed in Phase A) and leaf nodes.
79
+ # @!attribute action [Symbol, Proc, nil] Instance method (Symbol) or inline block (Proc) called when this is a leaf command
80
+ # @!attribute setup [Symbol, nil] Instance method called before dispatching to children; returns Hash merged into ctx
81
+ # @!attribute delegates_to [Symbol, Array<Symbol>, nil] Re-enter the command tree at this path
82
+ # @!attribute delegate_instance [Symbol, nil] Instance method returning a different plugin object
83
+ # @!attribute aliases [Array<Symbol>, nil] Alternative names accepted for this command (each resolves to this command's id)
84
+ # @!attribute entity_execute [Hash, nil] Shorthand: expand to Base#entity_execute with these parameters
85
+ # @!attribute transfer_paths [:send, :receive, nil] File-list resolution delegated to TransferAgent; mutually exclusive with arguments
86
+ # @!attribute condition [Symbol, nil] Instance method returning Boolean; if false command is hidden from dispatch
87
+ # @!attribute query_schema [String, nil] Schema path for --query help; when set, the runner hints `--query=help`
88
+ CommandSpec = Struct.new(
89
+ :id,
90
+ :parent,
91
+ :description,
92
+ :options,
93
+ :arguments,
94
+ :action,
95
+ :setup,
96
+ :delegates_to,
97
+ :delegate_instance,
98
+ :aliases,
99
+ :entity_execute,
100
+ :transfer_paths,
101
+ :condition,
102
+ :query_schema,
103
+ keyword_init: true
104
+ ) do
105
+ def initialize(**kwargs)
106
+ # Coerce each element of arguments: from Hash to ArgumentSpec if needed
107
+ if kwargs[:arguments]
108
+ kwargs[:arguments] = kwargs[:arguments].map do |a|
109
+ a.is_a?(Hash) ? ArgumentSpec.new(**a) : a
110
+ end
111
+ end
112
+ super
113
+ end
114
+
115
+ class << self
116
+ # Derive the implicit action method name from a path array.
117
+ # e.g. [:admin, :user, :list] -> :action_admin_user_list
118
+ # @param path [Array<Symbol>]
119
+ # @return [Symbol]
120
+ def action_method(path)
121
+ :"action_#{path.join('_')}"
122
+ end
123
+ end
124
+
125
+ # Compute the full path as Array<Symbol> from parent + id.
126
+ # @return [Array<Symbol>]
127
+ def full_path
128
+ Array(parent) + [id]
129
+ end
130
+
131
+ # Derive the implicit action method name from the full path.
132
+ # @return [Symbol]
133
+ def action_method_name
134
+ self.class.action_method(full_path)
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'aspera/assert'
4
+ require 'aspera/cli/mailer'
5
+ require 'aspera/cli/secret_finder'
6
+
7
+ module Aspera
8
+ module Cli
9
+ # Global objects shared with plugins
10
+ class Context
11
+ # @type [Array<Symbol>]
12
+ # Members that must be non-nil after bootstrap (validated in #validate)
13
+ MEMBERS = %i[options transfer config formatter persistency man_header presets http_config main_folder mailer secret_finder].freeze
14
+ # @!attribute [rw] options
15
+ # @return [Parser] the command line options manager
16
+ # @!attribute [rw] transfer
17
+ # @return [TransferAgent] the transfer agent, used by transfer plugins
18
+ # @!attribute [rw] config
19
+ # @return [Plugins::Config] the configuration plugin, used by plugins to get configuration values and presets
20
+ # @!attribute [rw] formatter
21
+ # @return [Formatter] the formatter, used by plugins to display results and messages
22
+ # @!attribute [rw] persistency
23
+ # @return [Object] # whatever the type is
24
+ # @!attribute [rw] man_header
25
+ # @return [Boolean] whether to display the manual header in plugin help
26
+ # @!attribute [rw] presets
27
+ # @return [PresetManager] manages the YAML config file and preset resolution
28
+ # @!attribute [rw] http_config
29
+ # @return [Http] manages HTTP/S and TLS runtime options
30
+ # @!attribute [rw] mailer
31
+ # @return [Mailer] sends emails (SMTP) via ERB templates
32
+ # @!attribute [rw] secret_finder
33
+ # @return [SecretFinder] resolves secrets for url+username pairs
34
+ attr_accessor(*MEMBERS)
35
+ # Optional: nil when progress bar is disabled
36
+ attr_accessor :progress_bar
37
+ # Optional: nil when no PAC script is configured
38
+ attr_accessor :pac_executor
39
+
40
+ # Initialize all members to nil, so that they are defined and can be validated later
41
+ # @return [nil]
42
+ def initialize
43
+ MEMBERS.each{ |i| instance_variable_set(:"@#{i}", nil)}
44
+ @progress_bar = nil
45
+ @pac_executor = nil
46
+ end
47
+
48
+ # Validate that all mandatory members are non-nil (detect bootstrap bugs)
49
+ # @raise [Aspera::AssertionError] if any member is not set
50
+ # @return [nil]
51
+ def validate
52
+ MEMBERS.each do |i|
53
+ Aspera.assert(instance_variable_defined?(:"@#{i}")){"context member @#{i} is not defined"}
54
+ Aspera.assert(!instance_variable_get(:"@#{i}").nil?){"context member @#{i} is nil"}
55
+ end
56
+ end
57
+
58
+ # Check if the context is in manual-only mode
59
+ # @return [Boolean] true if in manual-only mode
60
+ def only_manual?
61
+ transfer.eql?(:only_manual)
62
+ end
63
+
64
+ # Set the context to manual-only mode
65
+ # @return [Symbol] :only_manual
66
+ def only_manual!
67
+ @transfer = :only_manual
68
+ end
69
+ end
70
+ end
71
+ end
@@ -4,9 +4,25 @@ module Aspera
4
4
  module Cli
5
5
  # CLI base exception
6
6
  class Error < StandardError; end
7
+
8
+ # Raised by dispatch_from_registry when --help is encountered mid-dispatch.
9
+ # Carries the plugin instance (with #help_path set) so the runner can display
10
+ # context-sensitive help without unwinding through a full rescue chain.
11
+ class HelpRequest < StandardError
12
+ # @return [Plugins::Base]
13
+ attr_reader :plugin
14
+
15
+ def initialize(plugin)
16
+ super('help requested')
17
+ @plugin = plugin
18
+ end
19
+ end
20
+
7
21
  # Raised when an unexpected argument is provided.
8
22
  class BadArgument < Error; end
23
+
9
24
  class MissingArgument < Error; end
25
+
10
26
  class NoSuchElement < Error; end
11
27
 
12
28
  # Raised when a lookup for a specific entity fails to return exactly one result.
@@ -3,6 +3,7 @@
3
3
  # cspell:ignore csvt jsonpp stdbin
4
4
  require 'aspera/uri_reader'
5
5
  require 'aspera/environment'
6
+ require 'aspera/yaml'
6
7
  require 'aspera/log'
7
8
  require 'aspera/assert'
8
9
  require 'aspera/cli/error'
@@ -86,6 +87,7 @@ module Aspera
86
87
  # Base handlers
87
88
  # Other handlers can be set using `on`
88
89
  # e.g. `preset` is reader in config plugin
90
+ # @type [Hash{Symbol => Proc}]
89
91
  @handlers = {
90
92
  val: lambda{ |i| i},
91
93
  base64: lambda{ |i| Base64.decode64(i)},
@@ -100,10 +102,10 @@ module Aspera
100
102
  path: lambda{ |i| File.expand_path(i)},
101
103
  re: lambda{ |i| Regexp.new(i, Regexp::MULTILINE)},
102
104
  ruby: lambda{ |i| Environment.secure_eval(i, __FILE__, __LINE__)},
103
- s: lambda{ |i| i.to_s},
105
+ s: lambda(&:to_s),
104
106
  secret: lambda{ |i| prompt = i.empty? ? 'secret' : i; $stdin.getpass("#{prompt}> ")}, # rubocop:disable Style/Semicolon
105
107
  stdin: lambda{ |i| ExtendedValue.read_stdin(i)},
106
- yaml: lambda{ |i| YAML.load(i)},
108
+ yaml: lambda{ |i| YAML.safe_load(i)},
107
109
  zlib: lambda{ |i| Zlib::Inflate.inflate(i)},
108
110
  extend: lambda{ |i| ExtendedValue.instance.evaluate_extend(i)}
109
111
  }
@@ -137,7 +139,7 @@ module Aspera
137
139
  # Add a new handler
138
140
  def on(name, &block)
139
141
  Aspera.assert_type(name, Symbol){'name'}
140
- Aspera.assert(block)
142
+ Aspera.assert(block){"block required when registering handler for #{name}"}
141
143
  Log.log.debug{"Setting handler for #{name}"}
142
144
  @handlers[name] = block
143
145
  update_regex