Metadata-Version: 2.4
Name: vortex_cli
Version: 7.0.0
Summary: Vortex CLI
Author-email: Jordan Amos <jordan.amos@gmail.com>
License: MIT License
        
        Copyright (c) 2023 jordanamos
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Keywords: vortex,cli,puakma,tornado
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Natural Language :: English
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.27
Requires-Dist: tabulate>=0.9
Requires-Dist: watchfiles>=0.19
Provides-Extra: keyring
Requires-Dist: keyring; extra == "keyring"
Dynamic: license-file

# Vortex CLI

[![Build Status](https://dev.azure.com/amostj/vortex-cli/_apis/build/status%2Fjordanamos.vortex-cli?branchName=main)](https://dev.azure.com/amostj/vortex-cli/_build/latest?definitionId=11&branchName=main)  [![PyPI version](https://badge.fury.io/py/vortex-cli.svg)](https://badge.fury.io/py/vortex-cli)

Vortex CLI is a command line alternative to the [Puakma Vortex IDE](https://github.com/brendonupson/PuakmaVortex) that simplifies the process of developing Puakma Applications on a [Puakma Tornado Server](https://github.com/brendonupson/Puakma) using Visual Studio Code. It allows you to clone applications from the server to a local workspace, edit the files using Visual Studio Code, and automatically upload changes to the server as you work.

Vortex CLI also comes pre-packaged with the necessary Puakma .jar files for development.

#### Visual Studio Code and Extensions

While it is possible to use without it, this software has been purposefully designed for use with [Visual Studio Code](https://github.com/microsoft/vscode) and the [Project Manager For Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-dependency) or the [Extension Pack For Java](https://marketplace.visualstudio.com/items?itemName=vscjava.vscode-java-pack) extension. This software leverages [Workspaces](https://code.visualstudio.com/docs/editor/workspaces) in Visual Studio Code and manages a `vortex.code-workspace` file within the workspace.

## Installation

1. Install the tool using pip.

   ```
   pip install vortex-cli
   ```

2. It is recommended to set the workspace you would like to work out of via the `VORTEX_HOME` environment variable.

   On Unix:

   ```
   export VORTEX_HOME=/path/to/workspace
   ```

   Otherwise, Vortex CLI will use a default **'vortex-cli-workspace'** directory inside your home directory.

3. Run vortex with the `--init` flag to create your workspace (If it doesn't already exist) and the necessary config files:
   ```
   vortex --init
   ```

4. Define the servers you will be working with in the `servers.ini` file inside the `.config` directory within your workspace. You can quickly access this using the `code` command to view your workspace in VSCode.

   ```
   vortex code
   ```

   In the `servers.ini` file, you can define as many servers as you need, each with their own unique name. For example:

   ```
   [DEFAULT] ; This section is optional and only useful if you have multiple definitions
   port = 80 ; Options provided under DEFAULT will be applied to all definitions if not provided
   soap_path = system/SOAPDesigner.pma
   default = server1 ; Useful when you have multiple definitions


   [server1] ; This can be called whatever you want and can be referenced using the '--server' flag
   host = example.com
   port = 8080 ; we can overwrite the DEFAULT value
   username = myuser ; Optional - Prompted at runtime if not provided
   password = mypassword ; Optional - Prompted at runtime if not provided
   ; Optional
   puakma_db_conn_id = 13 ; Optional - discovered from the server when omitted
   backend = soap ; 'soap' (default) or 'gateway' - see The Gateway Backend below
   gateway_path = vortex/gateway.pma ; only used when backend = gateway - blank (or not installed on the server) = webdesign directly
   clone_with_resources = html,css,js ; resources with these extensions are always cloned - 'clone --get-resources' still clones ALL resources
   lib_path = ; optional extra jars to add to the classpath (the server's own jars are downloaded automatically - see 'vortex libs')
   workspace_folders = ~/dev/shared,notes ; extra folders to mount in the generated .code-workspace files. Relative paths resolve against the workspace root. Under [DEFAULT] they are added to every workspace; here they apply to this server's workspace (and the global one)
   java_home = /usr/lib/jvm/java-17-openjdk-amd64/ ; The local path to the JRE to use. Should be the same version running on your server
   java_environment_name = JavaSE-17 ; Java Execution Environment name https://docs.osgi.org/reference/eenames.html
   ```

## Upgrading to 7.0

7.0 replaces the gateway backend with a pass-through to webdesign and removes commands.

`backend = gateway` still means the **`vortex/gateway`** Puakma application at `gateway_path`
(default `vortex/gateway.pma`), but that application has been replaced: it is now a
permission wrapper around `system/webdesign`'s `vortex` JSON API, reached at
`{gateway_path}/api/...`. The old gateway's own endpoints (the undo journal, element hashes,
windowed downloads, status, agenda, render) are gone. `backend = soap` is unchanged.

- **The gateway is optional.** On a server where nothing answers at `gateway_path` (or it is
  blank), `backend = gateway` talks to `system/webdesign`'s `vortex` API directly. A server
  still running the OLD gateway has no `api` action there, so it is treated the same way -
  install the new gateway to get its role checks back.
- **Removed commands:** `push`, `pull`, `undo`, `status`, `agenda` (and
  `config --show-server`), `render`, and `compile` with its `build` alias. The gateway keeps no
  server-side journal and no element hashes, so there is no undo and no incremental pull (a
  fresh `vortex clone` replaces it). On a gateway server **`vortex watch` is the deploy
  path**; the `.class` files it uploads come from the IDE's build into `zbin/`.
- **`vortex import` works on `backend = gateway`** (the gateway's `import` route): it always
  creates a new application, and its scheduled actions start disabled.
- **`vortex list`** keeps the Version column but loses Last Modified, and on a gateway
  server it cannot hide inactive applications (the inventory has no `DisableApp` flag).
- **`vortex db --sql`** runs through the application that owns the connection, so that
  application must be cloned. There is no server dry run for `--update`.
- **Roles** are `Admin`, `GatewayDesignRead`, `GatewayDesignWrite`, `GatewayDBRead`,
  `GatewayDBWrite` and `GatewaySystem` (`GatewayLogRead` is gone - the log needs
  `GatewaySystem`). `vortex config --check-gateway` shows which ones you hold.

## Upgrading to 6.0

6.0 is additive - **the default behaviour is unchanged**. Every server keeps using the SOAP
designer path unless you opt it in.

- **New optional backend: the gateway.** Set `backend = gateway` on a server definition to
  route operations through a gateway Puakma application instead of SOAPDesigner. The default
  is `backend = soap`, which never contacts the gateway at all - so servers without it
  installed are unaffected. See [The Gateway Backend](#the-gateway-backend).
- **`puakma_db_conn_id` is now optional.** When omitted it is discovered from the server
  (over SOAP - nothing on the gateway backend needs it). Existing configs that set it
  explicitly keep working.

## Upgrading to 5.0

5.0 changes some defaults you may rely on:

- **`resource_ext_only` is replaced by `clone_with_resources` - and the meaning flipped.**
  Previously the extensions *restricted* what `--get-resources` cloned. Now resources with the
  listed extensions are **always** cloned, and `--get-resources` clones every resource
  unfiltered. Rename the key in `servers.ini` (vortex warns while the old key is present).
- **`vortex watch` now watches every cloned app across all servers** and uploads each change to
  the server it was cloned from. Use `--server` for the old single-server behaviour, and mark
  production definitions `protected = true` so they are never watched by accident.
- `find`, `grep` and `vortex list --local` now search all cloned apps unless `--server` is
  given, and ID-taking commands infer their server from local clones (see below).

## Usage

For a full list of commands see `--help`.

### Command Overview

- `code`: Open the workspace in Visual Studio Code (`-s <server>` opens that server's own workspace with exactly its jars on the Java classpath).
- `use`: Set the default server so you don't need to pass `--server` on every command. e.g. `vortex use production`
- `list` (or `ls`): List Puakma Applications on the server or cloned locally. (`ls` is an alias for `vortex list --local`)
- `clone`: Clone Puakma Applications and their design objects into the workspace. Apps can be referenced by ID (`vortex clone 13`), by TemplateName (`vortex clone bettrackr_app`), or by group/name (`vortex clone bettrackr/app`). A bare application group clones every active, non-inherited application in it (`vortex clone BetTrackr`; add `--all`/`-a` for the disabled and inherited ones too) - all optionally server-qualified (`dev:13`, `dev:bettrackr/app`, `dev:BetTrackr`). `--reclone` re-clones what is already cloned: every server's clones, or one server's with `--server`. See [Cloning a whole group](#cloning-a-whole-group).
- `watch`: Watch the workspace for changes to Design Objects and automatically upload them to the server each app was cloned from. Watches all servers at once unless `--server` is given.
- `clean`: Delete the locally cloned Puakma Application directories in the workspace.
  Takes optional `APP_ID`s to clean just those clones (all of the server's, if none are
  given). Deletes immediately and needs no network - there is no undo.
- `config`: View and manage configuration. `--check-gateway` shows the server's backend and, for `backend = gateway`, who the gateway thinks you are and which gateway roles you hold.
- `log`: View the server log.
- `find`: Find Design Objects of cloned applications by name.
- `grep`: Search the contents of cloned Design Objects using a Regular Expression.
- `new`: Create new Design Objects, Applications, or Keywords. Use `--update <ID>` to update instead. Run without flags to launch an interactive wizard.
- `keyword` (or `kw`): Read or update an application's Keyword values. `vortex keyword 9` lists them, `-n <Name>` filters to one, and `-n <Name> --values ...` updates it, printing the prior value beside the new one before asking to continue. Secret-looking names are redacted unless `--reveal` is given - see [Keyword values and secrets](#keyword-values-and-secrets).
- `copy`: Copy a Design Object from one application to another.
- `delete`: Delete Design Objects by ID.
- `db`: Interact with Database Connections. Accepts `--server`/`-s` like other commands for one-off queries against another server.
- `schema`: Manage the Puakma data dictionary (PMATABLE/ATTRIBUTE): record table/column definitions and print the DDL to run by hand. Never executes DDL.
- `libs`: Show or refresh the per-server Java library cache (each server's `puakma.jar` and shared libraries, downloaded from the server itself).
- `docs`: Open the Tornado Server Blackbook.
- `execute`: Execute a command on the server (e.g. `vortex execute status`).
- `export` / `import`: Export applications as `.pmx` files, and create an application from one.

### Keyword values and secrets

Keywords are an application's live configuration, and in practice they hold credentials in
cleartext - API keys, passwords, vendor secrets, signing keys. `vortex keyword` therefore
**redacts by default**:

```
vortex keyword 9                      # list every Keyword (secrets redacted)
vortex keyword 9 -n AppVersion        # just this one
vortex keyword 9 -n AppVersion --values 3.7.0    # update it
vortex keyword 9 --reveal             # print secret values in full
```

- A Keyword is treated as secret when its **name** contains a marker such as `password`,
  `passwd`, `passphrase`, `secret`, `credential`, `signature`, `apikey`, `privatekey`,
  `keystore`, `webhook`, `connectionstring` or `mnemonic`, or when any *word* of the name
  is one of `key`, `keys`, `pwd`, `token`, `salt`, `hash`, `private`, `auth`, `bearer`,
  `cert`, `pem`, `jwt`, `dsn`, `otp`, `pin`, `seed` or `sig`. Names are split on
  camelCase/snake_case/kebab-case, so `AccessKeyId` and `API_KEY` are redacted while
  `Monkey` and `Concert` are not. The match is on the name only - a value-based heuristic
  would leak the thing it is trying to hide on every near miss. Classification is
  deliberately biased towards over-redaction: a false positive costs one `--reveal`, a
  false negative prints a live credential.
- Redacted values print as `<redacted>`, one per value, so the value *count* stays visible.
- `--reveal` is the explicit opt-in. Without it, neither a listing nor an update's
  before/after diff can put a live credential into your terminal scrollback, your shell
  history or a CI log.

An update always prints the prior value beside the new one and waits for `[Y/y]` on stdin
before writing; on a `protected = true` server the server name must also be typed back
(`--yes` does not bypass that). The write is an upsert done by vortex: it never inserts a
duplicate KEYWORD row, and it folds any existing duplicates of the name into the oldest one.
`keyword` needs `backend = gateway`.

Reading does **not** require the application to be cloned locally - diagnosing a bad
configuration value should not depend on having a working copy.

### Cloning a whole group

`vortex clone` takes an ID, a TemplateName, an application group, or `group/name`:

```
vortex clone 13                 # by ID
vortex clone bettrackr_app      # by TemplateName
vortex clone bettrackr/app      # by group/name
vortex clone BetTrackr          # every active application in the BetTrackr group
vortex clone -a BetTrackr       # ... including its disabled and inherited ones
vortex clone dev:BetTrackr      # ... on the 'dev' server
vortex clone --group BetTrackr  # explicitly a group, never a TemplateName
```

A group clone skips disabled and inherited applications (the ones `vortex list`
hides by default) unless `--all`/`-a` is given. An application you name outright -
by ID, TemplateName or `group/name` - is always cloned, whatever its state.

Groups are matched the way `vortex list --group` matches them - a
case-insensitive substring - except that an exact (case-insensitive) group name
always wins, so cloning `BetTrackr` never drags in `BetTrackrLegacy`. If a
partial name still spans several groups, vortex stops and lists them rather
than cloning the lot.

A bare word is looked up as **both** a TemplateName and a group. In the rare
case that it is genuinely both, vortex refuses to guess: use `--group NAME` for
the group, or the ID / `group/name` for the single application. Every flag
(`--reclone`, `--all`, `--get-resources`, `--open-urls`, `--timeout`, `--server`)
applies to group clones as it does to single apps.

### Working with Multiple Servers

Each section in `servers.ini` defines a server (hosts must be unique across
sections). Cloned apps remember which server they came from, so you can work on
apps from several servers at the same time:

- `vortex watch` watches **every** cloned app and uploads each change to the
  server it was cloned from. One terminal, all servers. Log lines are prefixed
  with the server name (e.g. `[dev] Upload DATA of ...`). Use `--server` to
  watch a single server only.
- Commands that take IDs (`clone`, `export`, `delete`, `copy`) work
  out the server on their own: IDs can be qualified as `SERVER:ID`
  (e.g. `vortex export dev:123`), and unqualified IDs are resolved against your
  locally cloned apps. If an ID exists on more than one server, vortex stops
  and asks rather than guessing.
- `find`, `grep` and `vortex list --local` search across all cloned apps unless
  `--server` is given.
- `watch`, `clone` and `clean` take a workspace-wide lock: only one watch at
  a time, and cloning or cleaning is refused while a watch is running (stop
  the watch first - a clone under a running watch adds directories the
  watcher doesn't know about). Finer-grained commands that alter design
  elements (`delete`, `copy`, `new`) lock per-application: they are refused
  for apps a watch is watching and run concurrently otherwise.
- In VS Code, app folders are listed in per-server blocks (`dev: group/app`,
  ...) with the server's jars on the Java classpath (see them in the Java
  Projects view). vscode-java's classpath settings are
  **window-scoped** - folder-level settings are ignored - so the global
  workspace unions every server's jars (version clashes between servers are
  possible there). For a guaranteed-correct classpath, open a single server's
  own workspace with `vortex code -s <server>` (generated as
  `.vscode/<server>.code-workspace`, containing only that server's apps and
  exactly its jars).

`vortex use <server>` still sets the default server for commands that have no
IDs to infer from (`list`, `log`, `db`, `execute`, `new`):

```
vortex use production
vortex list
```

#### Credentials

`username`/`password` can be left out of `servers.ini`. Each server's
credentials resolve in this order and are only requested when a command
actually connects to that server:

1. The system keyring - store with `vortex config --set-password -s <server>`
   (requires `pip install keyring`)
2. Per-server environment variables `VORTEX_USERNAME_<SERVER>` /
   `VORTEX_PASSWORD_<SERVER>` (e.g. `VORTEX_PASSWORD_DEV`)
3. `VORTEX_USERNAME` / `VORTEX_PASSWORD`
4. An interactive prompt naming the server

#### Protected Servers

Set `protected = true` on a server definition (e.g. production) to make it
hard to change by accident:

- Write operations (`delete`, `copy`, `new`, `import`, `db --update`,
  `execute`, `keyword --values`, `schema` changes) require the server name to
  be typed back to continue. This is deliberately **not** bypassed by `--yes`.
- `vortex watch` skips protected servers unless `--include-protected` is
  given, so saving a file can never hot-deploy to production by accident.

### The Gateway Backend

`backend = gateway` sends every request through **the gateway** - a small Puakma application
(`vortex/gateway`) that you deploy to a server - or, where it is not installed, straight to
`system/webdesign` (see below). The gateway is `system/webdesign`'s `vortex`
JSON API at a different address: a request to `https://<host>/vortex/gateway.pma/api/<path>`
is webdesign's `/system/webdesign.pma/vortex/<path>` - same path, method, body and reply -
with three things added in front of it:

- **Role checks.** Every route needs one role, checked on the server: `GatewayDesignRead`
  (inventory, clone, keywords, libraries), `GatewayDesignWrite` (design, keyword and
  dictionary writes), `GatewayDBRead` (`SELECT`, connections), `GatewayDBWrite` (`INSERT`,
  `UPDATE`, `DELETE`), `GatewaySystem` (log, console, export, import) and `Admin` (everything,
  including creating applications). Write implies read. Roles are rows in *that server's*
  copy of the app, so a grant on dev confers nothing on prod. A route the gateway does not map
  is refused.
- **Guards.** The gateway never addresses the Puakma system database, never lets an id from
  one application be used under another, and runs exactly one `SELECT`/`INSERT`/`UPDATE`/`DELETE` per SQL request (DDL, `WITH`, and any
  `;` are refused).
- **Its own routes** for the server log, console, `.pmx` export/import and `whoami`.

Opt in per server:

```ini
[dev]
host = dev.example.com
backend = gateway            ; default is 'soap', which never contacts the gateway
; gateway_path = vortex/gateway.pma   (this is the default)
```

A `backend = gateway` server never talks to SOAPDesigner. Check what you are talking to and
what you may do:

```
vortex config --check-gateway -s dev
```

**The gateway is optional; it is a pass-through with role checks.** The first command that
needs it asks the gateway's `whoami`, once per run:

- **The gateway answers:** every request goes through it.
- **Nothing is installed at `gateway_path`** (a plain HTTP 404), or `gateway_path` is blank:
  every request goes straight to `system/webdesign`'s `vortex` API instead. That grants
  nothing extra - webdesign's own access control still applies. The commands that exist
  only in the gateway (`log`, `execute`, `import`) fail with a message saying they need it;
  `export` uses webdesign's `ExportPMX` directly.
- **The gateway is installed but says no or is down** (a refusal, a login page, a 5xx, a
  timeout): that is an error, never a detour around it. A refusal names the code and, for a
  missing role, the role.

There is never a fallback to SOAP, and `backend = soap` never contacts the gateway. Platform
applications (`system/*`, `vortex/*` - the gateway itself included - and `puakma`) are not
special-cased: `GatewayDesignWrite` writes them like any other application, which is how the
gateway is redeployed through itself.

There is **no undo**: the gateway keeps no journal of what a write replaced. Deletes and uploads
are final.

The gateway is **not bundled with this CLI** - deploy it to a server with `vortex export` /
`vortex import` and grant its roles in webdesign's security UI. Its DOCUMENTATION element
describes every route, role and refusal code.

> **Note:** the gateway's roles are only a real boundary for an identity whose *sole* route to
> the server is the gateway. Any identity that can reach `system/webdesign` or deploy code can
> grant itself any role. Keep webdesign access for operators; agent identities should not have
> it.

| Command | `backend = gateway` (through the gateway, or webdesign directly without it) | `backend = soap` |
|---|---|---|
| `clone` | `GET {app}` - the whole application in one request | SOAP |
| `list` | `GET` inventory | SOAP SQL |
| `watch` (modify) | `GET` + `PUT {app}/design/{id}` | SOAP `uploadDesign` |
| `watch` (create / delete), `copy`, `new object` | `POST`/`PUT`/`DELETE {app}/design`, `PUT design/params` | SOAP |
| `delete` | `DELETE {app}/design/{id}` | SOAP |
| `new app` | `POST` (Admin) | SOAP `saveApplication` |
| `keyword`, `new keyword` | `{app}/keywords` routes; the upsert is done by vortex | read **unsupported** - SOAP has no keyword read call; `new keyword` SOAP `saveKeyword` |
| `schema`, `db --list`, `db --schema` | `{app}/database/{conn}/table[/column]` routes | SOAP SQL + `savePuakma*` |
| `db --sql` | `POST {app}/database/{conn}/sql` (the owning app must be cloned) | SOAP |
| `log` | `GET logs` (needs the gateway) | SOAP SQL |
| `execute` | `POST console` (needs the gateway) | SOAP |
| `export` | `GET export` (webdesign `ExportPMX` without the gateway) | SOAP `downloadPmx` |
| `import` | `POST import` (needs the gateway; always a new app; scheduled actions start disabled) | SOAP `uploadPmx` |
| `libs` | `GET libraries`, `GET systemjar` | webdesign's `vortex` API directly |

Where webdesign's API behaves differently from SOAP, the CLI compensates so the commands
behave as they did. The differences that remain visible:

- **Design element writes are whole-row.** The CLI reads the row back first and re-sends what
  it does not model (the other blob, `Options`), so a `DATA`-only upload never wipes source.
  One consequence: every write is one extra `GET`.
- **A clone transfers every resource.** `GET {app}` has no filter, so `clone_with_resources`
  and `--get-resources` only decide what is written to disk and kept in the manifest.
- **A clone has no Java class version**, so `watch` does not check a `.class` file's version
  against the server's on a gateway server.
- **Renaming with `new object --update --name` does not rewrite references.** SOAP's
  `updateDesignObject` also updated design params in the app that referred to the old name;
  the webdesign route does not. Fix `OpenAction`/`ParentPage`-style params by hand after a rename.
- **`schema` cannot record `--default`, `--position` or the column half of `--ref`.** The
  webdesign column write has no `DefaultValue`/`Position`/`RefColumn` fields (SOAP's
  `savePuakmaAttribute2` had them). `--default`/`--position` are refused with a message;
  `--ref TABLE.COLUMN` records the table and warns. Updates leave existing values untouched.
  Extend `POST`/`PUT .../column` in webdesign's `vortex.java` to lift this.
- **`db --sql` runs one statement and has no dry run.** Only a `SELECT` runs without
  `--update`; row columns may come back in any order.
- **`list` shows inactive applications** (the inventory has no `DisableApp` flag), and a group
  clone skips only inherited applications.
- **Every write flushes the application's design cache**, where SOAP flushed one element.
- **`db --list` orders by table name** (SOAP's `SELECT DISTINCT` had no order); `--schema`
  output is identical.
- **Database-name resolution is app-scoped.** `schema`/`db --list` find the connection whose
  dictionary holds the tables (as SOAP did by `pmatable` count), asking locally cloned
  applications first and scanning the server inventory only if none of them has it.

### Interactive Wizards

Run `vortex new object` or `vortex new app` without any flags to launch a step-by-step wizard:

```
vortex new object
# → pick app from locally cloned list
# → pick design type
# → enter name
# → enter content type (resource/documentation only)
# → enter parent page, open action, save action (page only)
# → preview and confirm

vortex new app
# → enter name
# → pick from existing groups or enter a new one
# → enter description (optional)
# → preview and confirm
```

### Skipping Confirmation Prompts

Pass `--yes` (or `-y`) to skip Y/y confirmation prompts, useful for scripting:

```
vortex --yes delete 123 456
vortex -y new object --name MyAction --app-id 10 --type action
```

Note: `--yes` never bypasses the typed confirmation for servers marked
`protected = true`.

### Java Design Objects and `zbin/`

The server runs compiled classes, not source. While `vortex watch` is running, saving a Java
source uploads the SOURCE, and the VS Code Java extension's incremental build (the Eclipse
compiler) writes the class files into `zbin/`, where watch picks them up and uploads the
DATA. It always does, even when the bytes are unchanged, so a source change is always paired
with its class on the server. `'$'` nested-class files (`Foo$1.class`) are never uploaded - Tornado
loads each class from its own design element - and a class compiled alongside `$` siblings is
refused with an error, since it would fail on the server. Refactor anonymous/inner classes
into top-level SHARED_CODE classes.

### Server-Provided Java Libraries

The IDE's Java build and IntelliSense need the Puakma framework jar and the server's shared
libraries on the classpath. Instead of maintaining local copies, vortex downloads them from
each server's `webdesign` application (the `vortex` API's `systemjar` and `libraries`
endpoints - through the gateway on `backend = gateway` when it is installed, which needs
`GatewayDesignRead`) and
caches them per server under `<workspace>/<host>/.lib/`:

- The cache is filled automatically the first time it's needed - on `clone` and `watch` -
  and never re-downloaded unless you ask.
- `vortex libs` shows what's cached for each server; `vortex libs --refresh` re-downloads
  (add `-s <server>` for one server), e.g. after a server upgrade.
- Each server's VS Code workspace (`vortex code -s <server>`) uses that server's own cached
  jars, so identical class names on different servers/versions never cross-contaminate.
- Servers whose `webdesign` app doesn't provide the `vortex` API yet fall back to the
  `puakma.jar` bundled with vortex-cli plus any `lib_path` entries, with a warning.
- `vortex clean` keeps each host's `.lib` cache so the jars don't need
  re-downloading; pass `--include-libs` to remove them as well.

### Managing the Data Dictionary

`vortex schema` records design-time table and column definitions in the Puakma data
dictionary (the PMATABLE/ATTRIBUTE tables, which also back `vortex db --schema`) and prints
the matching DDL for you to run by hand. It never executes DDL, and deletes only remove
dictionary rows - never real tables or columns.

```
vortex schema mydb --add-table invoice --description "Customer invoices"
vortex schema mydb --add-column invoice invoice_id --type BIGINT --pk --auto-increment
vortex schema mydb --add-column invoice total --type NUMERIC --size 10,2 --not-null
vortex schema mydb --add-column invoice customer_id --type BIGINT --ref customer.customer_id
vortex schema mydb --ddl invoice    # print CREATE TABLE from the dictionary
```

On a `backend = gateway` server the dictionary is reached through webdesign's vortex API via
the gateway (`GatewayDBRead` to read, `GatewayDesignWrite` to change - dictionary rows are design
metadata; no DDL ever runs). That API cannot record `--default` or `--position` (refused) nor
the column half of `--ref` (warned) - see [The Gateway Backend](#the-gateway-backend).
