aspera-cli 4.27.5 → 4.28.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 (139) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +160 -4
  4. data/CONTRIBUTING.md +4 -2
  5. data/README.md +1 -1
  6. data/bin/asession +1 -1
  7. data/docs/README.md +1086 -331
  8. data/docs/test-mcp-with-ai.md +5 -3
  9. data/lib/aspera/agent/direct.rb +15 -4
  10. data/lib/aspera/agent/factory.rb +2 -1
  11. data/lib/aspera/agent/node.rb +1 -1
  12. data/lib/aspera/api/cos_node.rb +1 -1
  13. data/lib/aspera/api/faspex.rb +1 -1
  14. data/lib/aspera/api/node.rb +6 -2
  15. data/lib/aspera/ascp/installation.rb +47 -11
  16. data/lib/aspera/ascp/management.rb +89 -43
  17. data/lib/aspera/cli/ascp_actions.rb +5 -19
  18. data/lib/aspera/cli/bootstrapper.rb +1 -2
  19. data/lib/aspera/cli/command_registry.rb +43 -3
  20. data/lib/aspera/cli/command_spec.rb +3 -0
  21. data/lib/aspera/cli/completion/ascli.bash +39 -5
  22. data/lib/aspera/cli/completion/ascli.fish +16 -8
  23. data/lib/aspera/cli/completion/ascli.ps1 +41 -0
  24. data/lib/aspera/cli/completion/ascli.zsh +20 -4
  25. data/lib/aspera/cli/completion_actions.rb +360 -0
  26. data/lib/aspera/cli/error.rb +42 -0
  27. data/lib/aspera/cli/extended_value.rb +30 -5
  28. data/lib/aspera/cli/formatter.rb +118 -56
  29. data/lib/aspera/cli/hints.rb +0 -1
  30. data/lib/aspera/cli/http.rb +6 -5
  31. data/lib/aspera/cli/info.rb +10 -4
  32. data/lib/aspera/cli/mailer.rb +1 -1
  33. data/lib/aspera/cli/mcp/http_servlet.rb +156 -0
  34. data/lib/aspera/cli/mcp/secrets.rb +143 -0
  35. data/lib/aspera/cli/mcp/tool.rb +231 -0
  36. data/lib/aspera/cli/option_value.rb +9 -1
  37. data/lib/aspera/cli/options.schema.yaml +390 -39
  38. data/lib/aspera/cli/parser.rb +14 -5
  39. data/lib/aspera/cli/plugins/aoc.rb +8 -11
  40. data/lib/aspera/cli/plugins/base.rb +36 -15
  41. data/lib/aspera/cli/plugins/config.rb +15 -44
  42. data/lib/aspera/cli/plugins/console.rb +3 -1
  43. data/lib/aspera/cli/plugins/factory.rb +1 -1
  44. data/lib/aspera/cli/plugins/faspex5.rb +9 -19
  45. data/lib/aspera/cli/plugins/faspio.rb +36 -5
  46. data/lib/aspera/cli/plugins/httpgw.rb +1 -0
  47. data/lib/aspera/cli/plugins/mcp.rb +177 -118
  48. data/lib/aspera/cli/plugins/node.rb +14 -8
  49. data/lib/aspera/cli/plugins/orchestrator.rb +351 -38
  50. data/lib/aspera/cli/plugins/server.rb +3 -1
  51. data/lib/aspera/cli/plugins/shares.rb +205 -35
  52. data/lib/aspera/cli/preset_actions.rb +15 -7
  53. data/lib/aspera/cli/preset_manager.rb +24 -2
  54. data/lib/aspera/cli/prompt.rb +3 -1
  55. data/lib/aspera/cli/result.rb +17 -4
  56. data/lib/aspera/cli/runner.rb +133 -43
  57. data/lib/aspera/cli/security.rb +134 -0
  58. data/lib/aspera/cli/table_fitter.rb +116 -0
  59. data/lib/aspera/cli/terminal_formatter.rb +23 -34
  60. data/lib/aspera/cli/transfer_agent.rb +4 -1
  61. data/lib/aspera/cli/vault_manager.rb +6 -5
  62. data/lib/aspera/cli/version.rb +1 -1
  63. data/lib/aspera/cli/web_secret.html.erb +61 -0
  64. data/lib/aspera/cli/web_secret.rb +220 -0
  65. data/lib/aspera/cli/wizard.rb +19 -9
  66. data/lib/aspera/command_line_builder.rb +26 -0
  67. data/lib/aspera/data_repository.rb +38 -24
  68. data/lib/aspera/duration.rb +42 -0
  69. data/lib/aspera/environment.rb +88 -24
  70. data/lib/aspera/exec_spec.rb +6 -5
  71. data/lib/aspera/faspex_gw.rb +98 -32
  72. data/lib/aspera/faspex_postproc.rb +187 -44
  73. data/lib/aspera/formatter_interface.rb +0 -1
  74. data/lib/aspera/id_generator.rb +2 -1
  75. data/lib/aspera/json_rpc/client.rb +51 -32
  76. data/lib/aspera/json_rpc/version.rb +1 -0
  77. data/lib/aspera/keychain/base.rb +5 -0
  78. data/lib/aspera/keychain/encrypted_hash.rb +6 -2
  79. data/lib/aspera/keychain/factory.rb +2 -3
  80. data/lib/aspera/keychain/hashicorp_vault.rb +7 -4
  81. data/lib/aspera/keychain/macos_security.rb +119 -13
  82. data/lib/aspera/keychain/one_password_api.rb +8 -3
  83. data/lib/aspera/keychain/one_password_base.rb +14 -9
  84. data/lib/aspera/keychain/one_password_cli.rb +4 -2
  85. data/lib/aspera/log.rb +3 -6
  86. data/lib/aspera/markdown.rb +152 -115
  87. data/lib/aspera/mime.rb +6 -0
  88. data/lib/aspera/nagios.rb +3 -1
  89. data/lib/aspera/node_emulator.rb +98 -101
  90. data/lib/aspera/oauth/{base.rb → client/base.rb} +5 -4
  91. data/lib/aspera/oauth/{boot.rb → client/boot.rb} +3 -2
  92. data/lib/aspera/oauth/{factory.rb → client/factory.rb} +7 -0
  93. data/lib/aspera/oauth/{generic.rb → client/generic.rb} +1 -1
  94. data/lib/aspera/oauth/{json_credentials.rb → client/json_credentials.rb} +1 -1
  95. data/lib/aspera/oauth/{jwt.rb → client/jwt.rb} +1 -1
  96. data/lib/aspera/oauth/{url_json.rb → client/url_json.rb} +1 -1
  97. data/lib/aspera/oauth/{web.rb → client/web.rb} +2 -2
  98. data/lib/aspera/oauth/server/authorize.html.erb +34 -0
  99. data/lib/aspera/oauth/server/pkce.rb +426 -0
  100. data/lib/aspera/orchestrator_workflow_checker.rb +561 -0
  101. data/lib/aspera/preview/terminal.rb +3 -2
  102. data/lib/aspera/rainbow.rb +27 -1
  103. data/lib/aspera/rest/aspera_errors.rb +51 -59
  104. data/lib/aspera/rest/client.rb +21 -14
  105. data/lib/aspera/rest/error_analyzer.rb +9 -0
  106. data/lib/aspera/rest/list.rb +6 -6
  107. data/lib/aspera/rest/util.rb +7 -1
  108. data/lib/aspera/rest.rb +0 -1
  109. data/lib/aspera/schema/IBM Aspera Faspex API-5.0-enhanced.yaml +0 -2
  110. data/lib/aspera/schema/IBM Aspera Orchestrator API-v1-enhanced.yaml +4001 -0
  111. data/lib/aspera/schema/IBM Aspera faspio Gateway API-1.0.0-enhanced.yaml +1097 -0
  112. data/lib/aspera/schema/IBM Aspera on Cloud Automation API-1.0.5-enhanced.yaml +35 -35
  113. data/lib/aspera/schema/IBM_Aspera_Shares.yaml +819 -7
  114. data/lib/aspera/schema/documentation.rb +50 -78
  115. data/lib/aspera/schema/reader.rb +39 -11
  116. data/lib/aspera/schema/registry.rb +8 -2
  117. data/lib/aspera/secret_hider.rb +94 -15
  118. data/lib/aspera/secret_strength.rb +114 -0
  119. data/lib/aspera/servlet_helpers.rb +111 -0
  120. data/lib/aspera/sync/args.schema.yaml +3 -1
  121. data/lib/aspera/sync/conf.schema.yaml +16 -6
  122. data/lib/aspera/sync/operations.rb +21 -1
  123. data/lib/aspera/temp_file_manager.rb +79 -20
  124. data/lib/aspera/transfer/parameters.rb +5 -0
  125. data/lib/aspera/transfer/spec.rb +52 -12
  126. data/lib/aspera/transfer/spec.schema.yaml +8 -5
  127. data/lib/aspera/web_auth/index.html.erb +16 -0
  128. data/lib/aspera/web_auth/server.rb +18 -10
  129. data/lib/aspera/web_page.html.erb +262 -0
  130. data/lib/aspera/web_page.rb +61 -0
  131. data/lib/aspera/web_server_simple.rb +76 -10
  132. data.tar.gz.sig +0 -0
  133. metadata +47 -32
  134. metadata.gz.sig +0 -0
  135. data/lib/aspera/cli/mcp_tool.rb +0 -189
  136. data/lib/aspera/oauth.rb +0 -9
  137. data/lib/aspera/schema/IBM Aspera Orchestrator API-v1.yaml +0 -1784
  138. data/lib/aspera/schema/IBM Aspera faspio Gateway API-1.0.0.yaml +0 -249
  139. data/lib/aspera/web_auth/index.html +0 -147
@@ -29,6 +29,98 @@ components:
29
29
  type: boolean
30
30
  description: Show passwords and secrets in logs.
31
31
  default: false
32
+ UiOptions:
33
+ type: object
34
+ description: >-
35
+ User interface: how URLs to open, secrets and files are presented to the user.
36
+ A plain value, e.g. `--ui=terminal`, is the `mode`.
37
+ additionalProperties: false
38
+ properties:
39
+ mode:
40
+ type: string
41
+ description: >-
42
+ `auto`: `browser` on Windows and macOS, or if env var `DISPLAY` is set, else `terminal`.
43
+ `terminal`: URLs and files to open are displayed on terminal, and secrets are entered on terminal.
44
+ `browser`: a browser opens URLs, secrets are entered on a web page, and a text editor opens files.
45
+ `text` is an alias for `terminal`, and `graphical` is an alias for `browser`.
46
+ enum:
47
+ - auto
48
+ - terminal
49
+ - browser
50
+ - text
51
+ - graphical
52
+ default: auto
53
+ command:
54
+ type: array
55
+ description: >-
56
+ Command that opens a URL, instead of the system default.
57
+ First item is the executable, then its arguments. The URL is added as last argument.
58
+ When set, it is used to open URLs, whatever the `mode`.
59
+ items:
60
+ type: string
61
+ SecurityOptions:
62
+ type: object
63
+ description: >-
64
+ Restrictions of the features that access local resources or execute code.
65
+ On the command line, the default level is `low`.
66
+ In MCP server mode, the default level is `high`, and commands of the client cannot change the security options.
67
+ additionalProperties: false
68
+ properties:
69
+ level:
70
+ type: string
71
+ description: >-
72
+ `low`: no restriction.
73
+ `medium`: `@file:` reads only files in `allowed_paths`, `@ruby:`, `@uri:` and `@env:` are refused.
74
+ `high`: `@ruby:`, `@file:`, `@uri:` and `@env:` are refused.
75
+ enum:
76
+ - low
77
+ - medium
78
+ - high
79
+ allowed_paths:
80
+ type: array
81
+ description: >-
82
+ With level `medium`: files and folders that `@file:` can read.
83
+ A symbolic link leading outside of them is refused.
84
+ items:
85
+ type: string
86
+ TempOptions:
87
+ type: object
88
+ description: >-
89
+ Temporary files.
90
+ A plain value, e.g. `--temp=/tmp`, is the `folder`.
91
+ additionalProperties: false
92
+ properties:
93
+ folder:
94
+ type: string
95
+ description: >-
96
+ Folder for temporary files, e.g. certificate for WSS transfers, logs of `transferd`.
97
+ `@sys`: temporary folder of the system for the current user (Ruby `Etc.systmpdir`).
98
+ `@env`: from environment variables, e.g. `TMPDIR` (Ruby `Dir.tmpdir`).
99
+ default: '@sys'
100
+ clean:
101
+ type: boolean
102
+ description: >-
103
+ Delete temporary files created by the command when it ends.
104
+ `false` keeps them, e.g. for debugging.
105
+ Files left by previous commands are still deleted according to `expiry`.
106
+ default: true
107
+ expiry:
108
+ oneOf:
109
+ - type: string
110
+ - type: number
111
+ minimum: 0
112
+ - type: boolean
113
+ enum:
114
+ - false
115
+ description: >-
116
+ Age after which file lists of `ascp` left by previous commands (e.g. interrupted) are deleted, when a command starts.
117
+ They are in folder `filelists` of the main folder (option `home`).
118
+ A duration, e.g. `5d`, `12h`, `1w`, `1h 30m`, `1d 02:00:00`, a plain number is seconds.
119
+ `false` or `null` (`@none:`): never delete them.
120
+ `0`: delete all of them, including file lists of transfers in progress in other executions of ascli.
121
+ Such a transfer fails if `ascp` reads its file list again: next session of a multi-session transfer, or resume after an error.
122
+ Use `0` only when no other transfer is in progress, not in a permanent configuration.
123
+ default: 5d
32
124
  McpServerOptions:
33
125
  type: object
34
126
  description: MCP server configuration for the `mcp server` command.
@@ -51,14 +143,60 @@ components:
51
143
  type: integer
52
144
  description: Maximum JSON frame size for stdio transport.
53
145
  default: 4194304
146
+ url:
147
+ type: string
148
+ description: >-
149
+ Address and port the HTTP transport listens on, without path.
150
+ Use `https://` for TLS, with `cert` and `key`.
151
+ default: http://127.0.0.1:3000
152
+ cert:
153
+ $ref: "#/components/schemas/WebServerOptions/properties/cert"
154
+ key:
155
+ $ref: "#/components/schemas/WebServerOptions/properties/key"
156
+ chain:
157
+ $ref: "#/components/schemas/WebServerOptions/properties/chain"
54
158
  port:
55
159
  type: integer
56
- description: TCP port for HTTP transport.
57
- default: 3000
160
+ description: "Deprecated: use `url`. TCP port for HTTP transport."
58
161
  bind:
59
162
  type: string
60
- description: Bind address for HTTP transport.
61
- default: 127.0.0.1
163
+ description: "Deprecated: use `url`. Bind address for HTTP transport."
164
+ public_url:
165
+ type: string
166
+ description: >-
167
+ URL of the server for clients, used in OAuth metadata and in the URL of secret pages (`@secret:`), for example `http://myhost:3000`.
168
+
169
+ Default: built from `url`. Mandatory with `oauth` when `url` has host `0.0.0.0` or `::`.
170
+ auth_token:
171
+ type: string
172
+ description: >-
173
+ Bearer token required in the `Authorization` header of HTTP requests.
174
+ It is also the access code asked on the secret pages (`@secret:`), and on the consent page with `oauth`.
175
+
176
+ If not set, HTTP requests are not authenticated.
177
+ Mandatory when the host of `url` or `public_url` is not loopback.
178
+ Generate it with `openssl rand -hex 32`: a weak token is refused (see `auth_token_min_bits`).
179
+ auth_token_min_bits:
180
+ type: integer
181
+ minimum: 0
182
+ default: 80
183
+ description: >-
184
+ Minimum estimated strength of `auth_token`, in bits (log2 of the number of guesses to find it).
185
+ The strength is estimated with gem `zxcvbn` if installed, else with a built-in estimate
186
+ that detects repeats, sequences, keyboard rows, words and years.
187
+ `0` disables the check.
188
+ oauth:
189
+ type: boolean
190
+ description: Enable OAuth 2.0 PKCE flow with dynamic client registration.
191
+ default: false
192
+ oauth_access_token_ttl:
193
+ type: integer
194
+ description: OAuth access token lifetime in seconds.
195
+ default: 3600
196
+ oauth_refresh_token_ttl:
197
+ type: integer
198
+ description: OAuth refresh token lifetime in seconds.
199
+ default: 2592000
62
200
  stateless:
63
201
  type: boolean
64
202
  description: Run HTTP transport in stateless mode.
@@ -419,7 +557,8 @@ components:
419
557
  enum: [system]
420
558
  name:
421
559
  type: string
422
- description: Name of the keychain to use.
560
+ description: Name of the keychain to use (file `<name>.keychain-db` in the keychain search list).
561
+ default: ascli
423
562
  VaultOnePasswordOptions:
424
563
  type: object
425
564
  description: 1Password secret store (Connect REST API or CLI).
@@ -518,8 +657,9 @@ components:
518
657
  default: false
519
658
  double:
520
659
  type: boolean
521
- description: Use double vertical resolution via half-block characters.
522
- default: true
660
+ description: >-
661
+ Use double vertical resolution via half-block characters (Unicode).
662
+ Default is `true` if the terminal supports Unicode (see `utf8`).
523
663
  font_ratio:
524
664
  type: number
525
665
  format: float
@@ -647,11 +787,24 @@ components:
647
787
  Username expected from clients in HTTP Basic authentication.
648
788
  Set together with `password`.
649
789
  When not set, requests are accepted without authentication.
790
+ Mandatory when `url` is not a loopback address (`localhost`, `127.0.0.0/8`, `::1`).
650
791
  example: node_user
651
792
  password:
652
793
  type: string
653
- description: Password expected from clients in HTTP Basic authentication, for the above `username`.
794
+ description: >-
795
+ Password expected from clients in HTTP Basic authentication, for the above `username`.
796
+ Generate it with `openssl rand -hex 32`: a weak password is refused (see `password_min_bits`).
654
797
  example: my_password
798
+ password_min_bits:
799
+ type: integer
800
+ minimum: 0
801
+ default: 80
802
+ description: >-
803
+ Minimum estimated strength of `password`, in bits (log2 of the number of guesses to find it).
804
+ The strength is estimated with gem `zxcvbn` if installed, else with a built-in estimate
805
+ that detects repeats, sequences, keyboard rows, words and years.
806
+ `0` disables the check.
807
+ A random password of 32 bytes (`openssl rand -hex 32`) is far above the default.
655
808
  NodeTelemetryOptions:
656
809
  type: object
657
810
  description: >-
@@ -713,40 +866,88 @@ components:
713
866
  Valid keys are defined by the workflow's input specification.
714
867
  Use `orchestrator workflows inputs WORKFLOW_ID` to discover required parameters.
715
868
  additionalProperties: true
716
- OrchestratorImportWorkflow:
869
+ OrchestratorWorkflowExport:
717
870
  type: object
718
- description: Workflow definition to import into Orchestrator.
719
- required:
720
- - name
871
+ description: Options of `orchestrator workflows export`.
872
+ additionalProperties: false
721
873
  properties:
722
- name:
723
- type: string
724
- description: Workflow name.
725
- example: My Workflow
726
- description:
727
- type: string
728
- description: Workflow description.
729
- example: This is a sample workflow
730
- workflow_data:
731
- type: object
732
- description: Complete workflow definition.
874
+ dependencies:
875
+ type: boolean
876
+ description: >-
877
+ Export the sub-workflows and remote nodes used by the workflow in a package (`.wkf`),
878
+ saved in the folder given by option `to_folder` (default: current folder), instead of displaying the workflow.
879
+ default: false
880
+ OrchestratorWorkflowImport:
881
+ type: object
882
+ description: Options of `orchestrator workflows import`.
883
+ additionalProperties: false
884
+ properties:
885
+ folder_id:
886
+ type: integer
887
+ description: >-
888
+ Folder of the new workflow (default: the default folder).
889
+ The `folder_id` of existing workflows is in `orchestrator workflows list`.
890
+ publish:
891
+ type: boolean
892
+ description: >-
893
+ Publish the workflow after import.
894
+ If not set, the server option `publish_api_imported_workflow` decides.
895
+ link_to_subworkflows:
896
+ type: boolean
897
+ description: Link to existing sub-workflows instead of importing those of the package (`.wkf`).
898
+ default: false
733
899
  OrchestratorImportWithConstraints:
734
900
  type: object
735
- description: Workflow definition to import with constraint resolution mappings.
901
+ description: >-
902
+ Import of a workflow file present on the Orchestrator host, as a new workflow or as a new revision of an existing workflow.
903
+ `orchestrator workflows import` stores each uploaded file in `<run_dir>/workflows/import/`.
904
+ Conflicts with existing global templates, sub-workflows and remote nodes are resolved by name.
905
+ Use `orchestrator workflows find_constraints` to list the conflicts, and `orchestrator workflows check_import` to check the file beforehand.
906
+ additionalProperties: false
736
907
  required:
737
- - workflow_data
738
- - constraints
908
+ - filename
739
909
  properties:
740
- workflow_data:
910
+ filename:
911
+ type: string
912
+ description: Path of the workflow file (`.yml` or `.wkf`) on the Orchestrator host.
913
+ example: /opt/aspera/orchestrator/var/run/orchestrator/workflows/import/my_workflow.yml
914
+ add_as_revision:
915
+ type: integer
916
+ description: >-
917
+ ID of an existing workflow: the file is imported as a new revision of this workflow (to be published).
918
+ If not set, a new workflow is created.
919
+ example: 123
920
+ subwf_constraints:
741
921
  type: object
742
- description: Complete workflow definition to import.
743
- constraints:
922
+ description: 'Resolution for each sub-workflow, by name: `Map to the Existing Sub-Workflow`, `Create a new Sub-Workflow with a unique name`, or `Add a new revision to the existing Sub-Workflow`.'
923
+ additionalProperties:
924
+ type: string
925
+ enum:
926
+ - Map to the Existing Sub-Workflow
927
+ - Create a new Sub-Workflow with a unique name
928
+ - Add a new revision to the existing Sub-Workflow
929
+ action_template_constraints:
744
930
  type: object
745
- description: Constraint resolution mappings for conflicting resources.
746
- example:
747
- resource_pools: {}
748
- queues: {}
749
- monitors: {}
931
+ description: 'Resolution for each global action template, by name: `Map to the existing Global Template`, `Create a new Global Template with a different name`, or `Override the existing Global Template with this new template`.'
932
+ additionalProperties:
933
+ type: string
934
+ enum:
935
+ - Map to the existing Global Template
936
+ - Create a new Global Template with a different name
937
+ - Override the existing Global Template with this new template
938
+ remote_node_constraints:
939
+ type: object
940
+ description: 'Resolution for each remote node, by name: `Map to the existing Remote Node`, `Create a new Remote Node with a different name`, or `Override the Existing Remote Node with this importing node`.'
941
+ additionalProperties:
942
+ type: string
943
+ enum:
944
+ - Map to the existing Remote Node
945
+ - Create a new Remote Node with a different name
946
+ - Override the Existing Remote Node with this importing node
947
+ auto_enable_missing_plugins:
948
+ type: boolean
949
+ description: Enable the plugins used by the workflow that are not enabled.
950
+ default: false
750
951
  OrchestratorReloadPluginSet:
751
952
  type: object
752
953
  description: Set of plugins to reload.
@@ -792,8 +993,13 @@ components:
792
993
  `ALL` for all fields, `DEF` for default fields.
793
994
  Prefix a field name with `-` to exclude it.
794
995
  select:
795
- type: object
796
- description: Select only matching items from a list (field/value filter or lambda).
996
+ description: >-
997
+ Select only matching items from a list:
998
+ `Hash` of field/value, or Ruby lambda (`@ruby:`) returning `true` for items to keep.
999
+ query:
1000
+ description: >-
1001
+ Filter and reshape an object or list result, before `select` and `fields`:
1002
+ JMESPath expression (https://jmespath.org), or Ruby lambda (`@ruby:`) called with the data and returning the new data.
797
1003
  table:
798
1004
  $ref: "#/components/schemas/TableOptions"
799
1005
  flat:
@@ -808,11 +1014,156 @@ components:
808
1014
  type: boolean
809
1015
  description: >-
810
1016
  Use ANSI colors and styles in terminal output and logs.
811
- Default is auto-detected: enabled if both stdout and stderr are terminals and `TERM` is not `dumb`, or if `CLICOLOR_FORCE=1`.
1017
+ Default is auto-detected: disabled if `NO_COLOR` is set or results are written to a file,
1018
+ else enabled if `CLICOLOR_FORCE` is set and not `0`,
1019
+ else enabled if both stdout and stderr are terminals, `TERM` is not `dumb` and `CLICOLOR` is not `0`.
812
1020
  utf8:
813
1021
  type: boolean
814
1022
  description: >-
815
- Use Unicode characters in terminal output (e.g. table borders, check marks).
816
- Default is auto-detected: enabled if stdout is a terminal and the locale is UTF-8.
1023
+ Use Unicode characters in terminal output (e.g. table borders, check marks, hidden secrets), else ASCII replacements.
1024
+ Default is auto-detected: enabled if stdout is a terminal and the locale is UTF-8, or on Windows, the terminal renders Unicode (e.g. Windows Terminal, VS Code).
817
1025
  img:
818
1026
  $ref: "#/components/schemas/ImageOptions"
1027
+ WebServerOptions:
1028
+ type: object
1029
+ description: >-
1030
+ Address and TLS configuration for a local web server (`WebServerSimple`).
1031
+ Used by commands that start an embedded HTTP(S) listener (`faspex5 gateway`,
1032
+ `faspex5 postprocessing`, `aoc gateway`, `node emulator`, …).
1033
+ properties:
1034
+ url:
1035
+ type: string
1036
+ description: >-
1037
+ Address and port the server listens on.
1038
+ Use `https://` with `cert`/`key` for TLS.
1039
+ default: http://localhost:8080
1040
+ cert:
1041
+ type: string
1042
+ description: >-
1043
+ Path to the TLS certificate file.
1044
+ Accepted formats: PEM (`.pem`) or PKCS12 (`.p12` / `.pfx`).
1045
+ example: /path/to/cert.pem
1046
+ key:
1047
+ type: string
1048
+ description: >-
1049
+ Path to the PEM private key file, or the PKCS12 passphrase when `cert` is a `.p12`/`.pfx` file.
1050
+ example: /path/to/key.pem
1051
+ chain:
1052
+ type: string
1053
+ description: Path to the PEM certificate chain file (appended as extra chain certificates).
1054
+ example: /path/to/chain.pem
1055
+ Faspex4GatewayOptions:
1056
+ description: >-
1057
+ Configuration for the Faspex 4 gateway emulator (`faspex5 gateway`, `aoc gateway`).
1058
+ The command starts a local HTTP server that accepts Faspex 4 API calls and translates them
1059
+ to Faspex 5 / AoC API calls.
1060
+ Clients authenticate with HTTP Basic, as on Faspex 4, when `username` and `password` are set.
1061
+ By default the server binds to loopback; use a routable address only when clients must reach
1062
+ the host from outside: then `username` and `password` are required.
1063
+ allOf:
1064
+ - $ref: "#/components/schemas/WebServerOptions"
1065
+ - type: object
1066
+ # List properties solely to allow them under additionalProperties: false.
1067
+ # Types and descriptions are already defined in WebServerOptions above.
1068
+ additionalProperties: false
1069
+ properties:
1070
+ url: {}
1071
+ cert: {}
1072
+ key: {}
1073
+ chain: {}
1074
+ username:
1075
+ type: string
1076
+ description: >-
1077
+ Username expected from clients in HTTP Basic authentication.
1078
+ Set together with `password`.
1079
+ When not set, requests are accepted without authentication.
1080
+ Mandatory when `url` is not a loopback address (`localhost`, `127.0.0.0/8`, `::1`).
1081
+ example: fx4_user
1082
+ password:
1083
+ type: string
1084
+ description: >-
1085
+ Password expected from clients in HTTP Basic authentication, for the above `username`.
1086
+ Generate it with `openssl rand -hex 32`: a weak password is refused (see `password_min_bits`).
1087
+ example: my_password
1088
+ password_min_bits:
1089
+ type: integer
1090
+ minimum: 0
1091
+ default: 80
1092
+ description: >-
1093
+ Minimum estimated strength of `password`, in bits (log2 of the number of guesses to find it).
1094
+ The strength is estimated with gem `zxcvbn` if installed, else with a built-in estimate
1095
+ that detects repeats, sequences, keyboard rows, words and years.
1096
+ `0` disables the check.
1097
+ A random password of 32 bytes (`openssl rand -hex 32`) is far above the default.
1098
+ Faspex4PostProcOptions:
1099
+ description: >-
1100
+ Configuration for the Faspex 4-style post-processing webhook receiver (`faspex5 postprocessing`).
1101
+ The command starts a local HTTP(S) server that Faspex 5 calls after each package upload.
1102
+ It maps the URL path to a script under `script_folder` and executes it with the values of the
1103
+ webhook JSON body whose key starts with `faspex_` as environment variables.
1104
+ By default the server binds to loopback; use a routable address only when Faspex containers
1105
+ must reach the host: then `secret_token` or `allowed_ips` is required.
1106
+ allOf:
1107
+ - $ref: "#/components/schemas/WebServerOptions"
1108
+ - type: object
1109
+ additionalProperties: false
1110
+ properties:
1111
+ # Listed solely to allow them under additionalProperties: false (defined in WebServerOptions).
1112
+ url: {}
1113
+ cert: {}
1114
+ key: {}
1115
+ chain: {}
1116
+ script_folder:
1117
+ type: string
1118
+ description: >-
1119
+ Folder containing the post-processing scripts.
1120
+ The URL path (after the base path) is appended to this folder to locate the script to execute.
1121
+ default: "."
1122
+ example: /opt/faspex/scripts
1123
+ fail_on_error:
1124
+ type: boolean
1125
+ description: >-
1126
+ When `true`, return HTTP 500 if the script exits with a non-zero status code.
1127
+ When `false` (default), the exit code is reported in the response body but HTTP 200 is returned.
1128
+ default: false
1129
+ timeout_seconds:
1130
+ type: integer
1131
+ minimum: 1
1132
+ description: >-
1133
+ Maximum time in seconds to wait for the script to complete before killing it.
1134
+ default: 60
1135
+ secret_token:
1136
+ type: string
1137
+ minLength: 1
1138
+ description: >-
1139
+ Shared secret for webhook authentication.
1140
+ When set, every incoming request must include `?token=<value>` in the URL;
1141
+ requests without the correct token are rejected with HTTP 401.
1142
+ Configure the Faspex webhook URL as `http://host:port/path/script.sh?token=<value>`.
1143
+ This parameter or `allowed_ips` is required when `url` is not a local address.
1144
+ Generate it with `openssl rand -hex 32`: a weak token is refused (see `secret_token_min_bits`).
1145
+ secret_token_min_bits:
1146
+ type: integer
1147
+ minimum: 0
1148
+ default: 80
1149
+ description: >-
1150
+ Minimum estimated strength of `secret_token`, in bits (log2 of the number of guesses to find it).
1151
+ The strength is estimated with gem `zxcvbn` if installed, else with a built-in estimate
1152
+ that detects repeats, sequences, keyboard rows, words and years.
1153
+ `0` disables the check.
1154
+ A random token of 32 bytes (`openssl rand -hex 32`) is far above the default.
1155
+ allowed_ips:
1156
+ description: >-
1157
+ IP address or network (CIDR, e.g. `10.89.0.0/24`), or list of them, allowed to call the listener.
1158
+ Requests from any other source IP are rejected with HTTP 403.
1159
+ When not set, no IP restriction is applied.
1160
+ This parameter or `secret_token` is required when `url` is not a local address.
1161
+ oneOf:
1162
+ - type: string
1163
+ example: "192.168.1.10"
1164
+ - type: array
1165
+ items:
1166
+ type: string
1167
+ example:
1168
+ - "192.168.1.10"
1169
+ - "10.89.0.0/24"
@@ -205,8 +205,9 @@ module Aspera
205
205
  # @param validation [Class, Array, nil] Accepted value type(s) or list of Symbols
206
206
  # @param aliases [Hash] map of aliases: key = alias, value = real value
207
207
  # @param default [Object] default value
208
+ # @param raw [Boolean] `true`: value is not evaluated as extended value
208
209
  # @return [Object, Array, nil] one value, list or nil (if optional and no default)
209
- def get_next_argument(descr, mandatory: true, multiple: false, accept_list: nil, validation: Type::STRING, aliases: nil, default: nil, schema: nil)
210
+ def get_next_argument(descr, mandatory: true, multiple: false, accept_list: nil, validation: Type::STRING, aliases: nil, default: nil, schema: nil, raw: false)
210
211
  ensure_parsed
211
212
  Aspera.assert_array_all(accept_list, Symbol) unless accept_list.nil?
212
213
  Aspera.assert_hash_all(aliases, Symbol, Symbol) unless aliases.nil?
@@ -215,7 +216,7 @@ module Aspera
215
216
  Aspera.assert_array_all(validation, Class) { 'validation' } unless validation.nil?
216
217
  descr = "#{descr}#{add_types_info(validation)}"
217
218
  result =
218
- if !@command_line.pending_arguments.empty? then read_arguments(descr, multiple: multiple, validation: validation, accept_list: accept_list, aliases: aliases)
219
+ if !@command_line.pending_arguments.empty? then read_arguments(descr, multiple: multiple, validation: validation, accept_list: accept_list, aliases: aliases, raw: raw)
219
220
  elsif !default.nil? then default
220
221
  elsif mandatory then get_interactive(descr, multiple: multiple, accept_list: accept_list, aliases: aliases, schema: schema)
221
222
  end
@@ -353,6 +354,14 @@ module Aspera
353
354
  values.include?(@command_line.pending_arguments.first)
354
355
  end
355
356
 
357
+ # Check the next pending positional arguments, without consuming them
358
+ # @param values [Array<String>] values to compare with, in order
359
+ # @return [Boolean] true if the next pending positional arguments are values
360
+ def next_arguments?(values)
361
+ ensure_parsed
362
+ @command_line.pending_arguments.first(values.length).eql?(values)
363
+ end
364
+
356
365
  # Check for unprocessed options or arguments error messages
357
366
  # @return [Array<String>] list of error messages for unprocessed tokens
358
367
  def final_errors
@@ -516,11 +525,11 @@ module Aspera
516
525
  " (#{types.map(&:name).join(', ')})"
517
526
  end
518
527
 
519
- # Consume and evaluate positional arguments
528
+ # Consume positional arguments, and evaluate them unless `raw`
520
529
  # @return [Object, Array] one value, or list if `multiple`
521
- def read_arguments(descr, multiple:, validation:, accept_list:, aliases:)
530
+ def read_arguments(descr, multiple:, validation:, accept_list:, aliases:, raw:)
522
531
  values = @command_line.shift_arguments(multiple)
523
- values = values.map { |v| ExtendedValue.instance.evaluate(v, context: "argument: #{descr}", allowed: validation) }
532
+ values = values.map { |v| ExtendedValue.instance.evaluate(v, context: "argument: #{descr}", allowed: validation) } unless raw
524
533
  # If expecting list and only one arg of type array : it is the list
525
534
  values = values.first if multiple && values.length.eql?(1) && values.first.is_a?(Array)
526
535
  if accept_list
@@ -6,6 +6,7 @@ require 'aspera/cli/plugins/node'
6
6
  require 'aspera/cli/plugins/ats'
7
7
  require 'aspera/cli/transfer_agent'
8
8
  require 'aspera/cli/special_values'
9
+ require 'aspera/environment'
9
10
  require 'aspera/cli/wizard'
10
11
  require 'aspera/agent/node'
11
12
  require 'aspera/transfer/result'
@@ -198,6 +199,7 @@ module Aspera
198
199
  }
199
200
  preset_value[:password] = options.get_option(:password, mandatory: true) if pub_info['password_protected']
200
201
  return {
202
+ preset_ids: [],
201
203
  preset_value: preset_value,
202
204
  test_args: 'organization'
203
205
  }
@@ -213,7 +215,7 @@ module Aspera
213
215
  private_key_path = wizard.ask_private_key(
214
216
  user: wiz_username,
215
217
  url: app_url,
216
- page: '👤 → Account Settings → Profile → Public Key'
218
+ page: [Environment.char('👤'), 'Account Settings', 'Profile', 'Public Key']
217
219
  )
218
220
  client_id = options.get_option(:client_id)
219
221
  client_secret = options.get_option(:client_secret)
@@ -227,7 +229,7 @@ module Aspera
227
229
  else
228
230
  formatter.display_status('Using organization specific client_id.')
229
231
  formatter.display_status('Please login to your Aspera on Cloud instance.'.red)
230
- formatter.display_status('Navigate to: 𓃑 → Admin → Integrations → API Clients')
232
+ wizard.display_navigation(Environment.char('𓃑'), 'Admin', 'Integrations', 'API Clients')
231
233
  formatter.display_status('Check or create in integration:')
232
234
  formatter.display_status('- name: cli')
233
235
  formatter.display_status("- redirect uri: #{REDIRECT_LOCALHOST}")
@@ -259,6 +261,7 @@ module Aspera
259
261
  aoc_api.update("clients/#{options.get_option(:client_id)}", {'jwt_grant_enabled' => true, 'explicit_authorization_required' => false})
260
262
  end
261
263
  return {
264
+ preset_ids: [myself['email'], client_id].compact,
262
265
  preset_value: {
263
266
  url: app_url,
264
267
  username: myself['email'],
@@ -805,7 +808,7 @@ module Aspera
805
808
  end
806
809
  command :automation, description: 'Manage automation (BETA)', setup: :setup_automation_api
807
810
  command :gateway, description: 'Start AoC Faspex4 gateway',
808
- arguments: [{name: :parameters, type: Hash, mandatory: false, default: {}}]
811
+ arguments: [{name: :parameters, type: Hash, mandatory: false, default: {}, schema: Schema::Registry::GATEWAY_OPTIONS}]
809
812
 
810
813
  # user sub-commands
811
814
  commands_under :user do
@@ -1050,8 +1053,7 @@ module Aspera
1050
1053
  end
1051
1054
  package_data['encryption_at_rest'] = true if transfer.user_transfer_spec['content_protection'].eql?('encrypt')
1052
1055
  created_package = aoc_api.create_package_simple(package_data, option_validate, new_user_option)
1053
- Runner.result_transfer(transfer.start(created_package[:spec], rest_token: created_package[:node]))
1054
- return Result::SingleObject.new(created_package[:info])
1056
+ return Runner.result_object_transfer(created_package[:info], transfer.start(created_package[:spec], rest_token: created_package[:node]))
1055
1057
  end
1056
1058
 
1057
1059
  # packages > receive — package_id: from arguments:(:identifier) (or overridden by public_link)
@@ -1608,12 +1610,7 @@ module Aspera
1608
1610
 
1609
1611
  def action_gateway(parameters: {}, **)
1610
1612
  require 'aspera/faspex_gw'
1611
- parameters = parameters.symbolize_keys
1612
- uri = URI.parse(parameters.delete(:url) { WebServerSimple::DEFAULT_URL })
1613
- server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
1614
- Aspera.assert(parameters.except(*WebServerSimple::PARAMS).empty?) { "unexpected parameters: #{parameters.except(*WebServerSimple::PARAMS).keys}" }
1615
- server.mount(uri.path, Faspex4GWServlet, aoc_api, aoc_api.workspace_info[:id])
1616
- server.start
1613
+ Faspex4GWServlet.serve(parameters.symbolize_keys, type: Cli::BadArgument) { [aoc_api, aoc_api.workspace_info[:id]] }
1617
1614
  return Result::Status.new('Gateway terminated')
1618
1615
  end
1619
1616
  end