@bizone-ai/cli 0.1.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.
package/README.md ADDED
@@ -0,0 +1,289 @@
1
+ # Bizone CLI
2
+
3
+ An assistant CLI for running the **Bizone orchestrator platform** locally using Docker.
4
+
5
+ The CLI starts, configures, and stops the platform containers — a bundled
6
+ `mysql` database, `resource-manager`, `orchestrator`, `state-store`, and `ui` —
7
+ plus the one-shot resources initialization job (`bizone-resources`).
8
+ Optionally runs `cloud-tools` as well if required.
9
+ Works on **macOS** and **Windows**.
10
+
11
+ ---
12
+
13
+ ## Installation
14
+
15
+ Requires **Node.js >= 18** and a running **Docker** engine.
16
+
17
+ ```bash
18
+ cd cli
19
+ npm install # no runtime dependencies, but sets up the bin link
20
+ npm link # makes `bizone` available globally (optional)
21
+ ```
22
+
23
+ Or run directly without linking:
24
+
25
+ ```bash
26
+ node cli/bin/bizone.js <category> <command> [args]
27
+ ```
28
+
29
+ All examples below assume the `bizone` command is on your PATH.
30
+
31
+ ---
32
+
33
+ ## Command structure
34
+
35
+ ```
36
+ bizone <category> <command> [args] [--no-colors]
37
+ ```
38
+
39
+ | Category | Aliases | Purpose |
40
+ |-----------------|-------------|-------------------------------------------------|
41
+ | `start` | — | Start the platform |
42
+ | `stop` | — | Stop the platform |
43
+ | `configuration` | `config`,`c`| General CLI configuration |
44
+ | `images` | `img` | Override container image URLs |
45
+ | `environment` | `env` | Set environment variables per container |
46
+ | `resources` | `res`, `r` | Resources imported into the resource-manager |
47
+ | `database` | `db` | Manage the bundled MySQL database |
48
+
49
+ Global flags:
50
+
51
+ - `--no-colors` — disable ANSI colors (also disabled when `NO_COLOR` is set or output is not a TTY)
52
+ - `--help`, `-h` — show usage
53
+ - `--version`, `-v` — show version
54
+
55
+ ---
56
+
57
+ ## Configuration file
58
+
59
+ All settings are stored in a single JSON file in your home directory:
60
+
61
+ - **macOS / Linux:** `~/.bizone/config.json`
62
+ - **Windows:** `%USERPROFILE%\.bizone\config.json`
63
+
64
+ Running container ids are tracked alongside it in `containers.json`.
65
+
66
+ File shape:
67
+
68
+ ```json
69
+ {
70
+ "config": { "key": "value" },
71
+ "images": { "type": "image-url" },
72
+ "env": { "type": { "NAME": "value" } },
73
+ "resources": { "namespace": "path" }
74
+ }
75
+ ```
76
+
77
+ ---
78
+
79
+ ## `configuration` — general settings
80
+
81
+ ```bash
82
+ bizone config get # show current configuration (with defaults)
83
+ bizone config set {key} {value} # set a config key
84
+ bizone config remove {key} # reset a key back to its default
85
+ ```
86
+
87
+ Stored at `$.config.{key}`.
88
+
89
+ ### Available keys
90
+
91
+ | Key | Default | Description |
92
+ |-------------------------|----------------|----------------------------------------------------------------------------------------------------|
93
+ | `forward_aws_env` | `true` | Forward `AWS_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` into the orchestrator container |
94
+ | `network_name` | `bizone-local` | Shared docker network all containers join |
95
+ | `mysql_port` | `33306` | Host port for the bundled MySQL database |
96
+ | `orchestrator_port` | `8001` | Host port for the orchestrator |
97
+ | `resource_manager_port` | `7070` | Host port for the resource-manager |
98
+ | `state_store_port` | `8088` | Host port for the state-store |
99
+ | `state_store_enable` | `true` | Run `state-store` as part of the sequence |
100
+ | `cloud_tools_port` | `8086` | Host port for cloud-tools |
101
+ | `cloud_tools_enable` | `false` | Run `cloud-tools` as part of the sequence |
102
+ | `ui_port` | `7006` | Host port for the UI |
103
+ | `timeout_sec` | `300` | Readiness wait timeout per service (seconds) |
104
+
105
+ Example:
106
+
107
+ ```bash
108
+ bizone config set ui_port 9006
109
+ bizone config set cloud_tools_enable true
110
+ ```
111
+
112
+ ---
113
+
114
+ ## `images` — override container images
115
+
116
+ ```bash
117
+ bizone images get # list image URLs (override or default)
118
+ bizone images set {type} {url} # override an image
119
+ bizone images remove {type} # reset to default
120
+ ```
121
+
122
+ Stored at `$.images.{type}`. Valid types:
123
+
124
+ `mysql`, `orchestrator`, `state-store`, `resource-manager`, `cloud-tools`, `ui`, `resources`
125
+
126
+ Example:
127
+
128
+ ```bash
129
+ bizone images set state-store bizone-local/state-store:1.0.0
130
+ ```
131
+
132
+ ---
133
+
134
+ ## `environment` — per-container environment variables
135
+
136
+ ```bash
137
+ bizone env get {type} # show env vars (secrets masked)
138
+ bizone env set {type} {name} {value} # set an env var
139
+ bizone env remove {type} {name} # remove an env var
140
+ ```
141
+
142
+ Stored at `$.env.{type}.{name}`. `{type}` is one of the image types above.
143
+
144
+ Each container type also has built-in **default** environment variables that
145
+ are applied at run time unless you override the same key with `env set` (your
146
+ value wins). `env get` shows the effective set, tagging each entry as
147
+ `(default)` or `(override)`. For example, the MySQL client defaults
148
+ (`storage.type=mysql`, `mysql.storage`, `mysql.storage.user`,
149
+ `mysql.storage.password.secret`, `secret.mysql.password`) are applied to every
150
+ container except `ui`, and the `mysql` container gets `MYSQL_ROOT_PASSWORD=root`.
151
+
152
+ Secret values are masked on `get` (showing only the first 4 and last 4
153
+ characters). Keys are treated as secrets when they match a predefined list
154
+ (e.g. `AWS_SECRET_ACCESS_KEY`, `mysql.storage.password.secret`) or contain
155
+ `password` / `secret` / `token` / `apikey`.
156
+
157
+ Example:
158
+
159
+ ```bash
160
+ bizone env set state-store db_url "mysql://user:pass@host:3306/db"
161
+ bizone env get state-store
162
+ # db_url = mysq*******...****/db
163
+ ```
164
+
165
+ These env vars are passed to the container at run time via `-e NAME=value`.
166
+
167
+ ---
168
+
169
+ ## `resources` — startup resource import
170
+
171
+ Define resources to import into the resource-manager on startup, grouped by
172
+ namespace.
173
+
174
+ ```bash
175
+ bizone resources get # list configured sources
176
+ bizone resources set {namespace} {path} # set a file or folder source
177
+ bizone resources remove {namespace} # remove a namespace source
178
+ ```
179
+
180
+ Stored at `$.resources.{namespace}`. `{path}` may be:
181
+
182
+ - a **folder** — every `.json` file found recursively becomes one item
183
+ - a **single file with an `items` array** — that array is used as the items
184
+ - a **single file otherwise** — the whole file becomes the only item
185
+
186
+ Relative paths resolve against the `.bizone` config directory.
187
+
188
+ Example:
189
+
190
+ ```bash
191
+ bizone resources set my_namespace ./my_namespace.json
192
+ ```
193
+
194
+ ### Import procedure
195
+
196
+ On startup, for each namespace, the CLI sends:
197
+
198
+ ```
199
+ POST http://localhost:{resource_manager_port}/admin/{namespace}/status
200
+
201
+ { "user": "bizone-cli", "message": "resource initialization", "items": [ ... ] }
202
+ ```
203
+
204
+ ---
205
+
206
+ ## `start` — start the platform
207
+
208
+ ```bash
209
+ bizone start
210
+ ```
211
+
212
+ Non-blocking: once everything is ready it prints **"Platform is running"** and
213
+ returns control to the shell. Container ids are saved to
214
+ `~/.bizone/containers.json` keyed by container name.
215
+
216
+ ### Startup sequence
217
+
218
+ 1. Create the shared docker network `bizone-local` (skipped if it exists).
219
+ 2. Start `mysql` **only if it is not already running** (an existing DB container
220
+ is reused), then wait until it accepts connections (`mysqladmin ping`).
221
+ The resource-manager is configured to store into this database, whose data
222
+ lives in the persistent docker volume `bizone-mysql-data`.
223
+ 3. Start `resource-manager`; wait until `GET /.system/status` returns `{ "status": "OK" }`.
224
+ 4. Run the `bizone-resources` initialization job (blocking).
225
+ 5. Import any configured `resources` namespaces (see above).
226
+ 6. Start `orchestrator`, `ui`, and any enabled optional services in parallel
227
+ (state-store / cloud-tools run only when `*_enable=true`).
228
+ - The orchestrator receives the AWS env vars when `forward_aws_env=true`.
229
+ 7. Wait for all of them to become ready:
230
+ - `ui` — `GET /actuator/health/readiness` returns `{"status":{"code":"UP"}}`
231
+ - all others — `GET /.system/status` returns `{ "status": "OK" }`
232
+ - each wait times out after `timeout_sec` seconds.
233
+
234
+ Each container is started with:
235
+
236
+ ```
237
+ docker run -d \
238
+ --name bizone-{type} -h bizone-{type} \
239
+ --network bizone-local --network-alias bizone-{type} \
240
+ -p {host_port}:{container_port} \
241
+ [-e NAME=value ...] \
242
+ {image}
243
+ ```
244
+
245
+ ---
246
+
247
+ ## `stop` — stop the platform
248
+
249
+ ```bash
250
+ bizone stop
251
+ ```
252
+
253
+ Reads `containers.json`, validates each id against `docker ps`, stops &
254
+ removes the running containers, and updates the local file.
255
+
256
+ The **database is intentionally left running** so its data persists and the
257
+ next `bizone start` reuses it. To remove the database, use `bizone db destroy`.
258
+
259
+ ---
260
+
261
+ ## `database` — manage the MySQL database
262
+
263
+ ```bash
264
+ bizone database destroy # remove the DB container and its data volume
265
+ ```
266
+
267
+ The bundled `mysql` container stores its data in the persistent docker volume
268
+ `bizone-mysql-data`, so it survives `bizone stop` and container restarts.
269
+
270
+ `destroy` permanently removes both the `bizone-mysql` container and the
271
+ `bizone-mysql-data` volume (all data is lost). It refuses to run while any other
272
+ platform container (`bizone-*`) is still running — run `bizone stop` first.
273
+
274
+ ---
275
+
276
+ ## Troubleshooting
277
+
278
+ - **"Docker does not appear to be running"** — start Docker Desktop / the Docker daemon.
279
+ - **Startup timed out** — increase `timeout_sec`, or check the container logs with `docker logs bizone-{type}`.
280
+ - **Port already in use** — change the relevant `*_port` config value.
281
+ - **Stale containers blocking a re-run** — `bizone start` force-removes existing
282
+ `bizone-*` containers by name before starting; you can also run `bizone stop`.
283
+ - **`pull access denied` from AWS ECR** — when an image lives in an
284
+ `{account}.dkr.ecr.{region}.amazonaws.com` registry and the pull is
285
+ unauthorized, `bizone start` automatically authenticates by running
286
+ `aws ecr get-login-password --region {region}` and piping it into
287
+ `docker login --username AWS --password-stdin {registry}`, then retries.
288
+ This requires the **AWS CLI** to be installed and configured with valid
289
+ credentials for that account.
package/bin/bizone.js ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+ import { main } from '../src/cli.js';
3
+
4
+ main(process.argv.slice(2))
5
+ .then((code) => process.exit(code || 0))
6
+ .catch((err) => {
7
+ console.error(err && err.message ? err.message : err);
8
+ process.exit(1);
9
+ });
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@bizone-ai/cli",
3
+ "version": "0.1.0",
4
+ "description": "Assistant CLI for running the Bizone orchestrator platform locally with Docker",
5
+ "type": "module",
6
+ "bin": {
7
+ "bizone": "bin/bizone.js"
8
+ },
9
+ "engines": {
10
+ "node": ">=18"
11
+ },
12
+ "files": [
13
+ "bin",
14
+ "src",
15
+ "README.md"
16
+ ],
17
+ "scripts": {
18
+ "start": "node bin/bizone.js",
19
+ "test": "echo No tests"
20
+ },
21
+ "license": "UNLICENSED"
22
+ }
package/src/cli.js ADDED
@@ -0,0 +1,103 @@
1
+ // Argument parsing and command dispatch.
2
+
3
+ import { initColors, color, log } from './colors.js';
4
+ import { configuration } from './commands/configuration.js';
5
+ import { images } from './commands/images.js';
6
+ import { environment } from './commands/environment.js';
7
+ import { resources } from './commands/resources.js';
8
+ import { database } from './commands/database.js';
9
+ import { start } from './commands/start.js';
10
+ import { stop } from './commands/stop.js';
11
+ import { CONFIG_PATH } from './config.js';
12
+
13
+ const VERSION = '1.0.0';
14
+
15
+ // category -> { aliases, handler }
16
+ const CATEGORIES = {
17
+ configuration: { aliases: ['config', 'c'], handler: configuration },
18
+ images: { aliases: ['img'], handler: images },
19
+ environment: { aliases: ['env'], handler: environment },
20
+ resources: { aliases: ['res', 'r'], handler: resources },
21
+ database: { aliases: ['db'], handler: database },
22
+ };
23
+
24
+ function resolveCategory(name) {
25
+ if (CATEGORIES[name]) return CATEGORIES[name];
26
+ for (const cat of Object.values(CATEGORIES)) {
27
+ if (cat.aliases.includes(name)) return cat;
28
+ }
29
+ return null;
30
+ }
31
+
32
+ function printHelp() {
33
+ const b = color.bold;
34
+ const c = color.cyan;
35
+ log.plain(b('bizone') + ' - run the Bizone orchestrator platform locally with Docker');
36
+ log.plain('');
37
+ log.plain(b('Usage:') + ' bizone <category> <command> [args] [--no-colors]');
38
+ log.plain('');
39
+ log.plain(b('Lifecycle:'));
40
+ log.plain(` ${c('bizone start')} Start the platform (startup sequence)`);
41
+ log.plain(` ${c('bizone stop')} Stop running platform containers`);
42
+ log.plain('');
43
+ log.plain(b('Configuration:') + color.dim(' (config, c)'));
44
+ log.plain(` ${c('bizone config get')} Show current configuration`);
45
+ log.plain(` ${c('bizone config set {key} {value}')} Set a config value`);
46
+ log.plain(` ${c('bizone config remove {key}')} Remove a config value`);
47
+ log.plain('');
48
+ log.plain(b('Images:') + color.dim(' (img)'));
49
+ log.plain(` ${c('bizone images get')} List image URLs (override or default)`);
50
+ log.plain(` ${c('bizone images set {type} {url}')} Override an image`);
51
+ log.plain(` ${c('bizone images remove {type}')} Reset image to default`);
52
+ log.plain('');
53
+ log.plain(b('Environment:') + color.dim(' (env)'));
54
+ log.plain(` ${c('bizone env get {type}')} Show env vars (secrets masked)`);
55
+ log.plain(` ${c('bizone env set {type} {name} {value}')} Set an env var`);
56
+ log.plain(` ${c('bizone env remove {type} {name}')} Remove an env var`);
57
+ log.plain('');
58
+ log.plain(b('Resources:') + color.dim(' (res, r)'));
59
+ log.plain(` ${c('bizone resources get')} List resource sources`);
60
+ log.plain(` ${c('bizone resources set {namespace} {path}')} Set a resource source`);
61
+ log.plain(` ${c('bizone resources remove {namespace}')} Remove a resource source`);
62
+ log.plain('');
63
+ log.plain(b('Database:') + color.dim(' (db)'));
64
+ log.plain(` ${c('bizone database destroy')} Remove the DB container and its data volume`);
65
+ log.plain('');
66
+ log.plain(b('Global flags:'));
67
+ log.plain(` ${c('--no-colors')} Disable ANSI colors`);
68
+ log.plain(` ${c('--help, -h')} Show this help`);
69
+ log.plain(` ${c('--version, -v')} Show version`);
70
+ log.plain('');
71
+ log.plain(color.dim(`Config file: ${CONFIG_PATH}`));
72
+ }
73
+
74
+ export async function main(argv) {
75
+ // Extract global flags anywhere in argv.
76
+ const noColors = argv.includes('--no-colors');
77
+ initColors({ noColors });
78
+ const args = argv.filter((a) => a !== '--no-colors');
79
+
80
+ if (args.length === 0 || args[0] === '--help' || args[0] === '-h' || args[0] === 'help') {
81
+ printHelp();
82
+ return 0;
83
+ }
84
+ if (args[0] === '--version' || args[0] === '-v' || args[0] === 'version') {
85
+ log.plain(VERSION);
86
+ return 0;
87
+ }
88
+
89
+ const [category, ...rest] = args;
90
+
91
+ // Lifecycle commands act as bare categories.
92
+ if (category === 'start') return await start();
93
+ if (category === 'stop') return stop();
94
+
95
+ const resolved = resolveCategory(category);
96
+ if (!resolved) {
97
+ log.err(`Unknown command: ${color.cyan(category)}`);
98
+ log.plain(`Run ${color.cyan('bizone --help')} for usage.`);
99
+ return 1;
100
+ }
101
+
102
+ return await resolved.handler(rest);
103
+ }
package/src/colors.js ADDED
@@ -0,0 +1,46 @@
1
+ // Minimal ANSI color helpers. Colors are disabled when `--no-colors` is passed
2
+ // or when stdout is not a TTY / NO_COLOR env is set.
3
+
4
+ let enabled = true;
5
+
6
+ export function setColorsEnabled(value) {
7
+ enabled = value;
8
+ }
9
+
10
+ export function initColors({ noColors }) {
11
+ if (noColors) {
12
+ enabled = false;
13
+ return;
14
+ }
15
+ if (process.env.NO_COLOR) {
16
+ enabled = false;
17
+ return;
18
+ }
19
+ enabled = Boolean(process.stdout.isTTY);
20
+ }
21
+
22
+ function wrap(open, close) {
23
+ return (text) => (enabled ? `[${open}m${text}[${close}m` : String(text));
24
+ }
25
+
26
+ export const color = {
27
+ bold: wrap(1, 22),
28
+ dim: wrap(2, 22),
29
+ red: wrap(31, 39),
30
+ green: wrap(32, 39),
31
+ yellow: wrap(33, 39),
32
+ blue: wrap(34, 39),
33
+ magenta: wrap(35, 39),
34
+ cyan: wrap(36, 39),
35
+ gray: wrap(90, 39),
36
+ };
37
+
38
+ // Common log helpers.
39
+ export const log = {
40
+ info: (msg) => console.log(msg),
41
+ step: (msg) => console.log(`${color.cyan('•')} ${msg}`),
42
+ ok: (msg) => console.log(`${color.green('✓')} ${msg}`),
43
+ warn: (msg) => console.log(`${color.yellow('!')} ${msg}`),
44
+ err: (msg) => console.error(`${color.red('✗')} ${msg}`),
45
+ plain: (msg) => console.log(msg),
46
+ };
@@ -0,0 +1,69 @@
1
+ // `bizone configuration|config|c get|set|remove`
2
+
3
+ import { loadConfig, saveConfig, getConfigValue } from '../config.js';
4
+ import { CONFIG_KEYS, DEFAULT_CONFIG } from '../defaults.js';
5
+ import { color, log } from '../colors.js';
6
+
7
+ function printKnownKeys() {
8
+ log.plain(color.bold('Known configuration keys (with defaults):'));
9
+ for (const k of CONFIG_KEYS) {
10
+ log.plain(` ${color.cyan(k)} = ${color.dim(DEFAULT_CONFIG[k])}`);
11
+ }
12
+ }
13
+
14
+ export function configuration(args) {
15
+ const [sub, key, ...rest] = args;
16
+ const cfg = loadConfig();
17
+
18
+ switch (sub) {
19
+ case 'get': {
20
+ log.plain(color.bold('Configuration:'));
21
+ for (const k of CONFIG_KEYS) {
22
+ const isSet = Object.prototype.hasOwnProperty.call(cfg.config, k);
23
+ const val = getConfigValue(cfg, k);
24
+ const tag = isSet ? '' : color.dim(' (default)');
25
+ log.plain(` ${color.cyan(k)} = ${val}${tag}`);
26
+ }
27
+ // Any non-standard custom keys the user added.
28
+ for (const k of Object.keys(cfg.config)) {
29
+ if (!CONFIG_KEYS.includes(k)) {
30
+ log.plain(` ${color.cyan(k)} = ${cfg.config[k]}${color.yellow(' (custom)')}`);
31
+ }
32
+ }
33
+ return 0;
34
+ }
35
+ case 'set': {
36
+ if (!key || rest.length === 0) {
37
+ log.err('Usage: bizone config set {key} {value}');
38
+ printKnownKeys();
39
+ return 1;
40
+ }
41
+ const value = rest.join(' ');
42
+ if (!CONFIG_KEYS.includes(key)) {
43
+ log.warn(`"${key}" is not a known config key; storing it anyway.`);
44
+ }
45
+ cfg.config[key] = value;
46
+ saveConfig(cfg);
47
+ log.ok(`Set ${color.cyan(key)} = ${value}`);
48
+ return 0;
49
+ }
50
+ case 'remove': {
51
+ if (!key) {
52
+ log.err('Usage: bizone config remove {key}');
53
+ return 1;
54
+ }
55
+ if (Object.prototype.hasOwnProperty.call(cfg.config, key)) {
56
+ delete cfg.config[key];
57
+ saveConfig(cfg);
58
+ log.ok(`Removed ${color.cyan(key)}`);
59
+ } else {
60
+ log.warn(`Key ${color.cyan(key)} was not set.`);
61
+ }
62
+ return 0;
63
+ }
64
+ default:
65
+ log.err('Usage: bizone config <get|set|remove> ...');
66
+ printKnownKeys();
67
+ return 1;
68
+ }
69
+ }
@@ -0,0 +1,68 @@
1
+ // `bizone database|db destroy` — remove the database container and its volume.
2
+ // Only permitted when no other platform containers are running.
3
+
4
+ import { loadContainers, saveContainers } from '../config.js';
5
+ import { CONTAINERS } from '../defaults.js';
6
+ import {
7
+ dockerAvailable,
8
+ runningContainerNames,
9
+ stopContainer,
10
+ removeVolume,
11
+ } from '../docker.js';
12
+ import { color, log } from '../colors.js';
13
+
14
+ const mysql = CONTAINERS.mysql;
15
+
16
+ export function database(args) {
17
+ const [sub] = args;
18
+ switch (sub) {
19
+ case 'destroy':
20
+ return destroy();
21
+ default:
22
+ log.err('Usage: bizone database destroy');
23
+ return 1;
24
+ }
25
+ }
26
+
27
+ function destroy() {
28
+ if (!dockerAvailable()) {
29
+ log.err('Docker does not appear to be running or installed.');
30
+ return 1;
31
+ }
32
+
33
+ // Refuse while any other platform container is still running.
34
+ const running = runningContainerNames();
35
+ const others = [...running].filter(
36
+ (n) => n.startsWith('bizone-') && n !== mysql.containerName,
37
+ );
38
+ if (others.length > 0) {
39
+ log.err(`Cannot destroy the database while platform containers are running: ${others.join(', ')}`);
40
+ log.plain(`Run ${color.cyan('bizone stop')} first, then ${color.cyan('bizone db destroy')}.`);
41
+ return 1;
42
+ }
43
+
44
+ log.warn(`This will permanently delete the database container and ALL its data (volume ${mysql.volume.name}).`);
45
+
46
+ // Stop + remove the container (no-op if already gone).
47
+ log.step(`Removing container ${mysql.containerName}...`);
48
+ stopContainer(mysql.containerName);
49
+
50
+ // Remove the data volume.
51
+ log.step(`Removing volume ${mysql.volume.name}...`);
52
+ if (removeVolume(mysql.volume.name)) {
53
+ log.ok(`Removed volume ${mysql.volume.name}`);
54
+ } else {
55
+ log.warn(`Volume ${mysql.volume.name} not found or already removed`);
56
+ }
57
+
58
+ // Drop from the local record.
59
+ const containers = loadContainers();
60
+ if (containers[mysql.containerName]) {
61
+ delete containers[mysql.containerName];
62
+ saveContainers(containers);
63
+ }
64
+
65
+ log.plain('');
66
+ log.plain(color.green(color.bold('✓ Database destroyed')));
67
+ return 0;
68
+ }