vortex-cli 4.19.2__tar.gz → 4.20.0__tar.gz

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 (44) hide show
  1. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/PKG-INFO +42 -2
  2. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/README.md +41 -1
  3. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/pyproject.toml +1 -1
  4. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/cli.py +29 -8
  5. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/copy.py +4 -2
  6. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/delete.py +3 -1
  7. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/log.py +2 -2
  8. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/new.py +173 -5
  9. vortex_cli-4.20.0/vortex/commands/use.py +28 -0
  10. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/main.py +13 -2
  11. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/models.py +4 -4
  12. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/soap.py +2 -2
  13. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex_cli.egg-info/PKG-INFO +42 -2
  14. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex_cli.egg-info/SOURCES.txt +1 -0
  15. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/LICENSE +0 -0
  16. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/setup.cfg +0 -0
  17. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/__init__.py +0 -0
  18. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/__main__.py +0 -0
  19. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/colour.py +0 -0
  20. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/__init__.py +0 -0
  21. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/clean.py +0 -0
  22. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/clone.py +0 -0
  23. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/code.py +0 -0
  24. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/config.py +0 -0
  25. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/db.py +0 -0
  26. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/docs.py +0 -0
  27. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/execute.py +0 -0
  28. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/export.py +0 -0
  29. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/find.py +0 -0
  30. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/grep.py +0 -0
  31. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/import_.py +0 -0
  32. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/list.py +0 -0
  33. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/commands/watch.py +0 -0
  34. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/constants.py +0 -0
  35. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/docs/Blackbook.pdf +0 -0
  36. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/lib/puakma-6.0.40.jar +0 -0
  37. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/logging.py +0 -0
  38. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/spinner.py +0 -0
  39. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/util.py +0 -0
  40. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex/workspace.py +0 -0
  41. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex_cli.egg-info/dependency_links.txt +0 -0
  42. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex_cli.egg-info/entry_points.txt +0 -0
  43. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex_cli.egg-info/requires.txt +0 -0
  44. {vortex_cli-4.19.2 → vortex_cli-4.20.0}/vortex_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortex_cli
3
- Version: 4.19.2
3
+ Version: 4.20.0
4
4
  Summary: Vortex CLI
5
5
  Author-email: Jordan Amos <jordan.amos@gmail.com>
6
6
  License: MIT License
@@ -109,6 +109,7 @@ For a full list of commands see `--help`.
109
109
  ### Command Overview
110
110
 
111
111
  - `code`: Open the workspace in Visual Studio Code.
112
+ - `use`: Set the default server so you don't need to pass `--server` on every command. e.g. `vortex use production`
112
113
  - `list` (or `ls`): List Puakma Applications on the server or cloned locally. (`ls` is an alias for `vortex list --local`)
113
114
  - `clone`: Clone Puakma Applications and their design objects into the workspace or in the .pmx format.
114
115
  - `watch`: Watch the workspace for changes to Design Objects and automatically upload them to the server.
@@ -117,9 +118,48 @@ For a full list of commands see `--help`.
117
118
  - `log`: View the server log.
118
119
  - `find`: Find Design Objects of cloned applications by name.
119
120
  - `grep`: Search the contents of cloned Design Objects using a Regular Expression.
120
- - `new`: Create new Design Objects, Applications, or Keywords. Use --update <ID> to update instead.
121
+ - `new`: Create new Design Objects, Applications, or Keywords. Use `--update <ID>` to update instead. Run without flags to launch an interactive wizard.
121
122
  - `copy`: Copy a Design Object from one application to another.
122
123
  - `delete`: Delete Design Objects by ID.
123
124
  - `db`: Interact with Database Connections.
124
125
  - `docs`: Open the Tornado Server Blackbook.
125
126
  - `execute`: Execute a command on the server.
127
+
128
+ ### Switching Servers
129
+
130
+ If you work with multiple servers, use `vortex use <server>` to set the default instead of passing `--server` on every command:
131
+
132
+ ```
133
+ vortex use production
134
+ vortex list
135
+ vortex watch
136
+ ```
137
+
138
+ ### Interactive Wizards
139
+
140
+ Run `vortex new object` or `vortex new app` without any flags to launch a step-by-step wizard:
141
+
142
+ ```
143
+ vortex new object
144
+ # → pick app from locally cloned list
145
+ # → pick design type
146
+ # → enter name
147
+ # → enter content type (resource only)
148
+ # → enter parent page, open action, save action (page only)
149
+ # → preview and confirm
150
+
151
+ vortex new app
152
+ # → enter name
153
+ # → pick from existing groups or enter a new one
154
+ # → enter description (optional)
155
+ # → preview and confirm
156
+ ```
157
+
158
+ ### Skipping Confirmation Prompts
159
+
160
+ Pass `--yes` (or `-y`) to skip Y/y confirmation prompts, useful for scripting:
161
+
162
+ ```
163
+ vortex --yes delete 123 456
164
+ vortex -y new object --name MyAction --app-id 10 --type action
165
+ ```
@@ -67,6 +67,7 @@ For a full list of commands see `--help`.
67
67
  ### Command Overview
68
68
 
69
69
  - `code`: Open the workspace in Visual Studio Code.
70
+ - `use`: Set the default server so you don't need to pass `--server` on every command. e.g. `vortex use production`
70
71
  - `list` (or `ls`): List Puakma Applications on the server or cloned locally. (`ls` is an alias for `vortex list --local`)
71
72
  - `clone`: Clone Puakma Applications and their design objects into the workspace or in the .pmx format.
72
73
  - `watch`: Watch the workspace for changes to Design Objects and automatically upload them to the server.
@@ -75,9 +76,48 @@ For a full list of commands see `--help`.
75
76
  - `log`: View the server log.
76
77
  - `find`: Find Design Objects of cloned applications by name.
77
78
  - `grep`: Search the contents of cloned Design Objects using a Regular Expression.
78
- - `new`: Create new Design Objects, Applications, or Keywords. Use --update <ID> to update instead.
79
+ - `new`: Create new Design Objects, Applications, or Keywords. Use `--update <ID>` to update instead. Run without flags to launch an interactive wizard.
79
80
  - `copy`: Copy a Design Object from one application to another.
80
81
  - `delete`: Delete Design Objects by ID.
81
82
  - `db`: Interact with Database Connections.
82
83
  - `docs`: Open the Tornado Server Blackbook.
83
84
  - `execute`: Execute a command on the server.
85
+
86
+ ### Switching Servers
87
+
88
+ If you work with multiple servers, use `vortex use <server>` to set the default instead of passing `--server` on every command:
89
+
90
+ ```
91
+ vortex use production
92
+ vortex list
93
+ vortex watch
94
+ ```
95
+
96
+ ### Interactive Wizards
97
+
98
+ Run `vortex new object` or `vortex new app` without any flags to launch a step-by-step wizard:
99
+
100
+ ```
101
+ vortex new object
102
+ # → pick app from locally cloned list
103
+ # → pick design type
104
+ # → enter name
105
+ # → enter content type (resource only)
106
+ # → enter parent page, open action, save action (page only)
107
+ # → preview and confirm
108
+
109
+ vortex new app
110
+ # → enter name
111
+ # → pick from existing groups or enter a new one
112
+ # → enter description (optional)
113
+ # → preview and confirm
114
+ ```
115
+
116
+ ### Skipping Confirmation Prompts
117
+
118
+ Pass `--yes` (or `-y`) to skip Y/y confirmation prompts, useful for scripting:
119
+
120
+ ```
121
+ vortex --yes delete 123 456
122
+ vortex -y new object --name MyAction --app-id 10 --type action
123
+ ```
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "vortex_cli"
8
- version = "4.19.2"
8
+ version = "4.20.0"
9
9
  description = "Vortex CLI"
10
10
  requires-python = ">=3.10"
11
11
  readme = { file = "README.md", content-type = "text/markdown" }
@@ -90,11 +90,14 @@ def validate_args(
90
90
  ) -> None:
91
91
  if args.command == "new":
92
92
  msg = None
93
- is_update_mode = bool(args.update_ids)
93
+ is_update_mode = bool(getattr(args, "update_ids", None))
94
94
  if args.subcommand == "object":
95
95
  missing_required_fields = not is_update_mode and not (
96
96
  args.name and args.app_id and args.design_type
97
97
  )
98
+ # Allow wizard mode when all required fields are missing
99
+ has_any_required = args.name or args.app_id or args.design_type
100
+ wizard_mode = missing_required_fields and not has_any_required
98
101
  missing_content_type = (
99
102
  args.design_type in (DesignType.RESOURCE, DesignType.DOCUMENTATION)
100
103
  and not args.content_type
@@ -111,7 +114,7 @@ def validate_args(
111
114
  or args.parent_page
112
115
  or args.content_type
113
116
  )
114
- if missing_required_fields:
117
+ if missing_required_fields and not wizard_mode:
115
118
  msg = "--name, --app-id, and --type are required unless using --update"
116
119
  elif missing_content_type:
117
120
  msg = (
@@ -123,13 +126,19 @@ def validate_args(
123
126
  elif update_is_missing_args:
124
127
  msg = "Please specify an option to update"
125
128
  elif args.subcommand == "app":
126
- if not is_update_mode and not (args.name and args.group):
129
+ has_any_app_required = args.name or args.group
130
+ app_wizard_mode = not is_update_mode and not has_any_app_required
131
+ if (
132
+ not is_update_mode
133
+ and not (args.name and args.group)
134
+ and not app_wizard_mode
135
+ ):
127
136
  msg = "--name and --group are required when creating new applications."
128
137
  if msg:
129
138
  new_parser.error(msg)
130
139
 
131
140
  elif args.command == "clone" and not (args.app_ids or args.reclone):
132
- clone_parser.error("Please specifiy the APP_ID[s] to clone or use '--reclone'")
141
+ clone_parser.error("Please specify the APP_ID[s] to clone or use '--reclone'")
133
142
  elif args.command == "db" and args.sql is not None:
134
143
  try:
135
144
  args.database = int(args.database)
@@ -250,7 +259,7 @@ def add_clone_parser(
250
259
  )
251
260
  clone_parser.add_argument(
252
261
  "--reclone",
253
- help="Reclone locally cloned applictions",
262
+ help="Reclone locally cloned applications",
254
263
  action="store_true",
255
264
  )
256
265
  clone_parser.add_argument(
@@ -416,7 +425,7 @@ def add_log_parser(command_parser: _SubParsersAction[ArgumentParser]) -> None:
416
425
  type=_positive_int,
417
426
  help=(
418
427
  "Used only with --keep-alive. "
419
- "The number of seconds betwen requests for new logs. Default is %(default)s"
428
+ "The number of seconds between requests for new logs. Default is %(default)s"
420
429
  ),
421
430
  default=5,
422
431
  metavar="SECONDS",
@@ -659,13 +668,13 @@ def add_db_parser(command_parser: _SubParsersAction[ArgumentParser]) -> Argument
659
668
  "--update",
660
669
  "-u",
661
670
  action="store_true",
662
- help="Set this flag when running querys for altering/updating the database",
671
+ help="Set this flag when running queries for altering/updating the database",
663
672
  )
664
673
  return db_parser
665
674
 
666
675
 
667
676
  def add_docs_parser(command_parser: _SubParsersAction[ArgumentParser]) -> None:
668
- command_parser.add_parser("docs", help="Open the Torndao Server Blackbook")
677
+ command_parser.add_parser("docs", help="Open the Tornado Server Blackbook")
669
678
 
670
679
 
671
680
  def add_execute_parser(command_parser: _SubParsersAction[ArgumentParser]) -> None:
@@ -710,6 +719,18 @@ def add_execute_parser(command_parser: _SubParsersAction[ArgumentParser]) -> Non
710
719
  _add_server_option(execute_parser)
711
720
 
712
721
 
722
+ def add_use_parser(command_parser: _SubParsersAction[ArgumentParser]) -> None:
723
+ use_parser = command_parser.add_parser(
724
+ "use",
725
+ help="Set the default server definition to use",
726
+ )
727
+ use_parser.add_argument(
728
+ "server_name",
729
+ metavar="SERVER_NAME",
730
+ help="The name of the server definition in the config file",
731
+ )
732
+
733
+
713
734
  def add_import_parser(
714
735
  command_parser: _SubParsersAction[ArgumentParser],
715
736
  ) -> ArgumentParser:
@@ -40,7 +40,7 @@ async def _acopy_obj(
40
40
  to_app: PuakmaApplication,
41
41
  copy_params: bool,
42
42
  ) -> int:
43
- _err_msg = f"Failed to copy {obj} to {obj.app.id}"
43
+ _err_msg = f"Failed to copy {obj} to {to_app.id}"
44
44
  try:
45
45
  # Copying to the same application, need a new name in that case
46
46
  if obj.app == to_app:
@@ -151,6 +151,8 @@ def copy_(
151
151
  obj_ids: list[int],
152
152
  to_app_id: int,
153
153
  copy_params: bool,
154
+ *,
155
+ yes: bool = False,
154
156
  ) -> int:
155
157
  to_app = workspace.lookup_app(server, to_app_id)
156
158
  objs = []
@@ -171,7 +173,7 @@ def copy_(
171
173
 
172
174
  print(f"The following Objects will be copied to {to_app}:\n")
173
175
  render_objects(objs, show_params=copy_params)
174
- if input("\n[Y/y] to continue:") not in ["Y", "y"]:
176
+ if not yes and input("\n[Y/y] to continue:") not in ["Y", "y"]:
175
177
  return 1
176
178
 
177
179
  with (
@@ -38,6 +38,8 @@ def delete(
38
38
  workspace: Workspace,
39
39
  server: PuakmaServer,
40
40
  obj_ids: list[int],
41
+ *,
42
+ yes: bool = False,
41
43
  ) -> int:
42
44
  app_objs: dict[PuakmaApplication, list[DesignObject]] = collections.defaultdict(
43
45
  list
@@ -54,7 +56,7 @@ def delete(
54
56
  _deleted = Colour.colour("DELETED", Colour.RED)
55
57
  print(f"The following Design Objects will be {_deleted}:\n")
56
58
  render_objects(all_objs)
57
- if input("\n[Y/y] to continue:") not in ["Y", "y"]:
59
+ if not yes and input("\n[Y/y] to continue:") not in ["Y", "y"]:
58
60
  logger.error("Operation Cancelled")
59
61
  return 1
60
62
 
@@ -23,7 +23,7 @@ def log(
23
23
  server: PuakmaServer,
24
24
  limit: int,
25
25
  source_filter: str | None,
26
- messsage_filter: str | None,
26
+ message_filter: str | None,
27
27
  errors_only: bool,
28
28
  info_only: bool,
29
29
  debug_only: bool,
@@ -35,7 +35,7 @@ def log(
35
35
  s.get_last_log_items,
36
36
  limit,
37
37
  source_filter,
38
- messsage_filter,
38
+ message_filter,
39
39
  errors_only,
40
40
  info_only,
41
41
  debug_only,
@@ -177,6 +177,8 @@ def _validate_update_objs(
177
177
  workspace: Workspace,
178
178
  server: PuakmaServer,
179
179
  design_obj_ids: list[int],
180
+ *,
181
+ yes: bool = False,
180
182
  **kwargs: Any,
181
183
  ) -> tuple[tuple[DesignObject, ...], bool]:
182
184
  _universal_keys = ("name", "design_type", "comment", "inherit_from")
@@ -213,7 +215,7 @@ def _validate_update_objs(
213
215
  _updated = Colour.colour("UPDATED", Colour.GREEN)
214
216
  print(f"The following Design Object(s) will be {_updated}:\n")
215
217
  print(tabulate.tabulate(rows, headers=row_headers))
216
- if input("\n[Y/y] to continue:") not in ["Y", "y"]:
218
+ if not yes and input("\n[Y/y] to continue:") not in ["Y", "y"]:
217
219
  exit(1)
218
220
 
219
221
  return tuple(objs), recreate_params
@@ -223,11 +225,13 @@ def _update_objects(
223
225
  workspace: Workspace,
224
226
  server: PuakmaServer,
225
227
  design_obj_ids: list[int],
228
+ *,
229
+ yes: bool = False,
226
230
  **kwargs: Any,
227
231
  ) -> int:
228
232
  try:
229
233
  updated_objs, recreate_params = _validate_update_objs(
230
- workspace, server, design_obj_ids, **kwargs
234
+ workspace, server, design_obj_ids, yes=yes, **kwargs
231
235
  )
232
236
  except (DesignObjectNotFound, DesignObjectAmbiguousError) as e:
233
237
  logger.error(e)
@@ -259,6 +263,8 @@ def _new_object(
259
263
  parent_page: str | None = None,
260
264
  open_action: str | None = None,
261
265
  save_action: str | None = None,
266
+ *,
267
+ yes: bool = False,
262
268
  ) -> int:
263
269
  if not _validate_args_for_type(
264
270
  design_type,
@@ -288,7 +294,7 @@ def _new_object(
288
294
  _created = Colour.colour("CREATED", Colour.GREEN)
289
295
  print(f"The following Design Object(s) will be {_created}:\n")
290
296
  render_objects(objs, show_params=_show_params)
291
- if input("\n[Y/y] to continue:") not in ["Y", "y"]:
297
+ if not yes and input("\n[Y/y] to continue:") not in ["Y", "y"]:
292
298
  return 1
293
299
 
294
300
  recreate_params = True if parent_page or open_action or save_action else False
@@ -308,11 +314,13 @@ def _new_app(
308
314
  inherit_from: str | None,
309
315
  template_name: str | None,
310
316
  description: str | None,
317
+ *,
318
+ yes: bool = False,
311
319
  ) -> int:
312
320
  app = PuakmaApplication(-1, name, group, inherit_from, template_name, server.host)
313
321
  print("The following Application will be created:\n")
314
322
  render_apps([app], show_inherited=True)
315
- if input("\n[Y/y] to continue:") not in ["Y", "y"]:
323
+ if not yes and input("\n[Y/y] to continue:") not in ["Y", "y"]:
316
324
  return 1
317
325
 
318
326
  with server as s:
@@ -342,13 +350,167 @@ def _new_keyword(
342
350
  return 0
343
351
 
344
352
 
353
+ def _pick_from_list(prompt: str, options: list[str]) -> str:
354
+ print(f"\n{prompt}")
355
+ for i, option in enumerate(options, 1):
356
+ print(f" {i}. {option}")
357
+ while True:
358
+ choice = input("Enter number: ").strip()
359
+ try:
360
+ idx = int(choice)
361
+ if 1 <= idx <= len(options):
362
+ return options[idx - 1]
363
+ except ValueError:
364
+ pass
365
+ print(f"Invalid choice. Please enter a number between 1 and {len(options)}.")
366
+
367
+
368
+ def _new_object_wizard(
369
+ workspace: Workspace,
370
+ server: PuakmaServer,
371
+ *,
372
+ yes: bool = False,
373
+ ) -> int:
374
+ # Step 1: List locally cloned apps for user to pick from
375
+ print("Creating a new Design Object...\n")
376
+ apps = workspace.listapps(server)
377
+ if not apps:
378
+ logger.error(
379
+ "No locally cloned applications found. Run 'vortex clone <app_id>' first."
380
+ )
381
+ return 1
382
+
383
+ app_labels = [f"[{app.id}] {app}" for app in apps]
384
+ chosen_label = _pick_from_list("Select an application:", app_labels)
385
+ chosen_idx = app_labels.index(chosen_label)
386
+ chosen_app = apps[chosen_idx]
387
+
388
+ # Reload the app from local workspace to get design objects
389
+ app = workspace.lookup_app(server, chosen_app.id)
390
+
391
+ # Step 2: Pick design type
392
+ design_type_names = [t.name.lower() for t in DesignType if t != DesignType.ERROR]
393
+ chosen_type_name = _pick_from_list("Select a design type:", design_type_names)
394
+ design_type = DesignType.from_name(chosen_type_name)
395
+
396
+ # Step 3: Ask for name
397
+ name = ""
398
+ while not name.strip():
399
+ name = input("\nEnter object name: ").strip()
400
+ if not name:
401
+ print("Name cannot be empty.")
402
+
403
+ # Step 4: content_type — RESOURCE only (PAGE auto-sets text/html, others don't support it)
404
+ content_type = None
405
+ if design_type == DesignType.RESOURCE:
406
+ content_type = input(
407
+ "Enter content type (e.g. text/javascript, text/css): "
408
+ ).strip()
409
+ if not content_type:
410
+ logger.error("Content type is required for resource type.")
411
+ return 1
412
+
413
+ # Step 5: parent_page, open_action, save_action — PAGE only
414
+ parent_page = None
415
+ open_action = None
416
+ save_action = None
417
+ if design_type == DesignType.PAGE:
418
+ parent_page = (
419
+ input("Enter parent page (optional, press Enter to skip): ").strip() or None
420
+ )
421
+ open_action = (
422
+ input("Enter open action (optional, press Enter to skip): ").strip() or None
423
+ )
424
+ save_action = (
425
+ input("Enter save action (optional, press Enter to skip): ").strip() or None
426
+ )
427
+
428
+ return _new_object(
429
+ workspace,
430
+ server,
431
+ name=name,
432
+ app_id=app.id,
433
+ design_type=design_type,
434
+ content_type=content_type,
435
+ parent_page=parent_page,
436
+ open_action=open_action,
437
+ save_action=save_action,
438
+ yes=yes,
439
+ )
440
+
441
+
442
+ def _new_app_wizard(
443
+ server: PuakmaServer,
444
+ *,
445
+ yes: bool = False,
446
+ ) -> int:
447
+ print("Creating a new Application...\n")
448
+
449
+ # Step 1: Ask for app name
450
+ name = ""
451
+ while not name.strip():
452
+ name = input("Enter application name: ").strip()
453
+ if not name:
454
+ print("Name cannot be empty.")
455
+
456
+ # Step 2: Ask for group (show existing groups if available)
457
+ # Note: Do NOT use 'with server as s:' here — the context manager closes the
458
+ # underlying httpx client on exit, which would break the subsequent _new_app() call.
459
+ try:
460
+ apps = server.fetch_all_apps([], [], [], False, False, False)
461
+ existing_groups = sorted({app.group for app in apps if app.group})
462
+ if existing_groups:
463
+ print("\nExisting groups:")
464
+ for i, group in enumerate(existing_groups, 1):
465
+ print(f" {i}. {group}")
466
+ print(f" {len(existing_groups) + 1}. (enter a new group)")
467
+ while True:
468
+ choice = input("Enter number or group name: ").strip()
469
+ try:
470
+ idx = int(choice)
471
+ if 1 <= idx <= len(existing_groups):
472
+ group = existing_groups[idx - 1]
473
+ break
474
+ elif idx == len(existing_groups) + 1:
475
+ group = input("Enter new group name: ").strip()
476
+ break
477
+ except ValueError:
478
+ group = choice
479
+ break
480
+ print(
481
+ f"Invalid choice. Please enter a number between "
482
+ f"1 and {len(existing_groups) + 1}, or a group name."
483
+ )
484
+ else:
485
+ group = input("Enter application group: ").strip()
486
+ except Exception:
487
+ group = input("Enter application group: ").strip()
488
+
489
+ # Step 3: Ask for optional description
490
+ description = (
491
+ input("Enter description (optional, press Enter to skip): ").strip() or None
492
+ )
493
+
494
+ return _new_app(
495
+ server,
496
+ group,
497
+ name,
498
+ inherit_from=None,
499
+ template_name=None,
500
+ description=description,
501
+ yes=yes,
502
+ )
503
+
504
+
345
505
  def new(workspace: Workspace, server: PuakmaServer, args: argparse.Namespace) -> int:
506
+ yes = getattr(args, "yes", False)
346
507
  if args.subcommand in ("obj", "object"):
347
- if args.update_ids:
508
+ if getattr(args, "update_ids", None):
348
509
  return _update_objects(
349
510
  workspace,
350
511
  server,
351
512
  args.update_ids,
513
+ yes=yes,
352
514
  name=args.name,
353
515
  design_type=args.design_type,
354
516
  content_type=args.content_type,
@@ -358,6 +520,8 @@ def new(workspace: Workspace, server: PuakmaServer, args: argparse.Namespace) ->
358
520
  open_action=args.open_action,
359
521
  save_action=args.save_action,
360
522
  )
523
+ elif not (args.name and args.app_id and args.design_type):
524
+ return _new_object_wizard(workspace, server, yes=yes)
361
525
  else:
362
526
  return _new_object(
363
527
  workspace,
@@ -371,8 +535,11 @@ def new(workspace: Workspace, server: PuakmaServer, args: argparse.Namespace) ->
371
535
  parent_page=args.parent_page,
372
536
  open_action=args.open_action,
373
537
  save_action=args.save_action,
538
+ yes=yes,
374
539
  )
375
540
  elif args.subcommand == "app":
541
+ if not (args.name and args.group):
542
+ return _new_app_wizard(server, yes=yes)
376
543
  return _new_app(
377
544
  server,
378
545
  args.group,
@@ -380,6 +547,7 @@ def new(workspace: Workspace, server: PuakmaServer, args: argparse.Namespace) ->
380
547
  args.inherit_from,
381
548
  args.template,
382
549
  args.description,
550
+ yes=yes,
383
551
  )
384
552
  elif args.subcommand in ("kw", "keyword"):
385
553
  return _new_keyword(
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ import configparser
4
+ import logging
5
+
6
+ from vortex.workspace import Workspace
7
+
8
+ logger = logging.getLogger("vortex")
9
+
10
+
11
+ def use(workspace: Workspace, server_name: str) -> int:
12
+ config = configparser.ConfigParser()
13
+ config.read(workspace.server_config_file)
14
+
15
+ available_servers = config.sections()
16
+ if server_name not in available_servers:
17
+ logger.error(
18
+ f"Server '{server_name}' not found in config. "
19
+ f"Available servers: {available_servers}"
20
+ )
21
+ return 1
22
+
23
+ config.set(config.default_section, "default", server_name)
24
+ with open(workspace.server_config_file, "w") as f:
25
+ config.write(f)
26
+
27
+ logger.info(f"Default server set to '{server_name}'")
28
+ return 0
@@ -28,6 +28,7 @@ from vortex.commands.import_ import import_
28
28
  from vortex.commands.list import list_
29
29
  from vortex.commands.log import log
30
30
  from vortex.commands.new import new
31
+ from vortex.commands.use import use
31
32
  from vortex.commands.watch import watch
32
33
  from vortex.logging import LoggingHandler
33
34
  from vortex.logging import logging_handler
@@ -47,7 +48,7 @@ def error_handler() -> Generator[None]:
47
48
  except KeyboardInterrupt:
48
49
  raise SystemExit(130) from None
49
50
  except Exception as e:
50
- msg = f"An unexpected error occured. {e}".rstrip()
51
+ msg = f"An unexpected error occurred. {e}".rstrip()
51
52
  logger.critical(msg)
52
53
  LoggingHandler.log_to_file_and_exit(e, 3)
53
54
 
@@ -82,6 +83,12 @@ def main(argv: Sequence[str] | None = None) -> int:
82
83
  action="store_true",
83
84
  help="Initialise the workspace and config files, if they don't already exist",
84
85
  )
86
+ parser.add_argument(
87
+ "--yes",
88
+ "-y",
89
+ action="store_true",
90
+ help="Skip confirmation prompts and auto-confirm",
91
+ )
85
92
 
86
93
  command_parser = parser.add_subparsers(dest="command")
87
94
  cli.add_list_parser(command_parser)
@@ -97,6 +104,7 @@ def main(argv: Sequence[str] | None = None) -> int:
97
104
  cli.add_execute_parser(command_parser)
98
105
  cli.add_export_parser(command_parser)
99
106
  cli.add_import_parser(command_parser)
107
+ cli.add_use_parser(command_parser)
100
108
 
101
109
  clone_parser = cli.add_clone_parser(command_parser)
102
110
  code_parser = cli.add_code_parser(command_parser)
@@ -135,6 +143,8 @@ def main(argv: Sequence[str] | None = None) -> int:
135
143
  return code(workspace, remaining_args)
136
144
  elif args.command == "docs":
137
145
  return docs()
146
+ elif args.command == "use":
147
+ return use(workspace, args.server_name)
138
148
 
139
149
  server = workspace.read_server_from_config(server_name)
140
150
  logger.debug(f"Using server {server}")
@@ -238,7 +248,7 @@ def main(argv: Sequence[str] | None = None) -> int:
238
248
  )
239
249
  return new(workspace, server, args)
240
250
  elif args.command == "delete":
241
- return delete(workspace, server, args.obj_ids)
251
+ return delete(workspace, server, args.obj_ids, yes=args.yes)
242
252
  elif args.command == "copy":
243
253
  return copy_(
244
254
  workspace,
@@ -246,6 +256,7 @@ def main(argv: Sequence[str] | None = None) -> int:
246
256
  args.ids,
247
257
  to_app_id=args.app_id,
248
258
  copy_params=args.copy_params,
259
+ yes=args.yes,
249
260
  )
250
261
  elif args.command == "db":
251
262
  return db(
@@ -269,7 +269,7 @@ class PuakmaServer:
269
269
  self,
270
270
  limit_items: int,
271
271
  source_filter: str | None,
272
- messsage_filter: str | None,
272
+ message_filter: str | None,
273
273
  errors_only: bool,
274
274
  info_only: bool,
275
275
  debug_only: bool,
@@ -282,8 +282,8 @@ class PuakmaServer:
282
282
  where.write("WHERE 1=1")
283
283
  if source_filter:
284
284
  _and("source", source_filter)
285
- if messsage_filter:
286
- _and("logstring", messsage_filter)
285
+ if message_filter:
286
+ _and("logstring", message_filter)
287
287
  if last_log_item_id is not None and last_log_item_id > 0:
288
288
  where.write(f" AND logid > {last_log_item_id}")
289
289
  if errors_only:
@@ -329,7 +329,7 @@ class PuakmaApplication:
329
329
  self.id = id_
330
330
  self.name = name
331
331
  self.group = group
332
- # Some apps dont have a group. Adding a palce holder will
332
+ # Some apps dont have a group. Adding a placeholder will
333
333
  # allow for consistent paths locally
334
334
  self.group_safe = group or "~"
335
335
  self.inherit_from = inherit_from
@@ -310,8 +310,8 @@ class ServerDesigner(_PuakmaSOAPService):
310
310
  name = "ServerDesigner"
311
311
 
312
312
  async def ainitiate_connection(self) -> str:
313
- opertaion = "initiateConnection"
314
- resp = await self._apost(opertaion)
313
+ operation = "initiateConnection"
314
+ resp = await self._apost(operation)
315
315
  logger.info(f"Connected to {self.server.host}...")
316
316
  return str(resp.text)
317
317
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vortex_cli
3
- Version: 4.19.2
3
+ Version: 4.20.0
4
4
  Summary: Vortex CLI
5
5
  Author-email: Jordan Amos <jordan.amos@gmail.com>
6
6
  License: MIT License
@@ -109,6 +109,7 @@ For a full list of commands see `--help`.
109
109
  ### Command Overview
110
110
 
111
111
  - `code`: Open the workspace in Visual Studio Code.
112
+ - `use`: Set the default server so you don't need to pass `--server` on every command. e.g. `vortex use production`
112
113
  - `list` (or `ls`): List Puakma Applications on the server or cloned locally. (`ls` is an alias for `vortex list --local`)
113
114
  - `clone`: Clone Puakma Applications and their design objects into the workspace or in the .pmx format.
114
115
  - `watch`: Watch the workspace for changes to Design Objects and automatically upload them to the server.
@@ -117,9 +118,48 @@ For a full list of commands see `--help`.
117
118
  - `log`: View the server log.
118
119
  - `find`: Find Design Objects of cloned applications by name.
119
120
  - `grep`: Search the contents of cloned Design Objects using a Regular Expression.
120
- - `new`: Create new Design Objects, Applications, or Keywords. Use --update <ID> to update instead.
121
+ - `new`: Create new Design Objects, Applications, or Keywords. Use `--update <ID>` to update instead. Run without flags to launch an interactive wizard.
121
122
  - `copy`: Copy a Design Object from one application to another.
122
123
  - `delete`: Delete Design Objects by ID.
123
124
  - `db`: Interact with Database Connections.
124
125
  - `docs`: Open the Tornado Server Blackbook.
125
126
  - `execute`: Execute a command on the server.
127
+
128
+ ### Switching Servers
129
+
130
+ If you work with multiple servers, use `vortex use <server>` to set the default instead of passing `--server` on every command:
131
+
132
+ ```
133
+ vortex use production
134
+ vortex list
135
+ vortex watch
136
+ ```
137
+
138
+ ### Interactive Wizards
139
+
140
+ Run `vortex new object` or `vortex new app` without any flags to launch a step-by-step wizard:
141
+
142
+ ```
143
+ vortex new object
144
+ # → pick app from locally cloned list
145
+ # → pick design type
146
+ # → enter name
147
+ # → enter content type (resource only)
148
+ # → enter parent page, open action, save action (page only)
149
+ # → preview and confirm
150
+
151
+ vortex new app
152
+ # → enter name
153
+ # → pick from existing groups or enter a new one
154
+ # → enter description (optional)
155
+ # → preview and confirm
156
+ ```
157
+
158
+ ### Skipping Confirmation Prompts
159
+
160
+ Pass `--yes` (or `-y`) to skip Y/y confirmation prompts, useful for scripting:
161
+
162
+ ```
163
+ vortex --yes delete 123 456
164
+ vortex -y new object --name MyAction --app-id 10 --type action
165
+ ```
@@ -30,6 +30,7 @@ vortex/commands/import_.py
30
30
  vortex/commands/list.py
31
31
  vortex/commands/log.py
32
32
  vortex/commands/new.py
33
+ vortex/commands/use.py
33
34
  vortex/commands/watch.py
34
35
  vortex/docs/Blackbook.pdf
35
36
  vortex/lib/puakma-6.0.40.jar
File without changes
File without changes
File without changes