krita-cli 1.0.0__py3-none-any.whl

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.
krita_cli/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Krita CLI — Typer-based command-line interface."""
2
+
3
+ from krita_cli.app import app
4
+
5
+ __all__ = ["app"]
krita_cli/_shared.py ADDED
@@ -0,0 +1,94 @@
1
+ """Shared CLI utilities used by app.py and command modules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from contextlib import contextmanager
6
+ from typing import Any
7
+
8
+ import typer
9
+ from rich.console import Console
10
+ from typer import Context
11
+
12
+ from krita_cli.config_cmd import load_config
13
+ from krita_client import (
14
+ ClientConfig,
15
+ ErrorCode,
16
+ KritaClient,
17
+ KritaError,
18
+ )
19
+
20
+ console = Console()
21
+
22
+
23
+ class CLIState:
24
+ """Shared state passed through Typer context."""
25
+
26
+ def __init__(self) -> None:
27
+ self.url: str | None = None
28
+
29
+
30
+ def _handle_error(exc: KritaError) -> None:
31
+ """Display a Krita client error and exit."""
32
+ console.print(f"[red]Error:[/red] {exc.message}")
33
+ if exc.code:
34
+ console.print(f"[dim]Code: {exc.code}[/dim]")
35
+
36
+ if exc.recoverable:
37
+ if exc.code == ErrorCode.NO_ACTIVE_DOCUMENT:
38
+ console.print("[green]Hint: Open a document or create a new canvas first.[/green]")
39
+ elif exc.code == ErrorCode.INVALID_PARAMETERS:
40
+ console.print("[green]Hint: Check your input values are within allowed ranges.[/green]")
41
+ elif exc.code == ErrorCode.LAYER_NOT_FOUND:
42
+ console.print("[green]Hint: Ensure there is an active paint layer in your document.[/green]")
43
+ elif exc.code == ErrorCode.PLUGIN_UNREACHABLE:
44
+ console.print("[green]Hint: Make sure Krita is running with the MCP plugin enabled.[/green]")
45
+ elif exc.code == ErrorCode.COMMAND_TIMEOUT:
46
+ console.print("[green]Hint: The operation took too long. Try again or check Krita status.[/green]")
47
+ elif exc.code == ErrorCode.BRUSH_NOT_FOUND:
48
+ console.print("[green]Hint: Check the brush preset name or list available brushes first.[/green]")
49
+ elif exc.code == ErrorCode.FILE_NOT_FOUND:
50
+ console.print("[green]Hint: Verify the file path exists and is accessible.[/green]")
51
+ else:
52
+ console.print(
53
+ "[green]Hint: This error appears to be recoverable. Adjust your request and try again.[/green]"
54
+ )
55
+
56
+ raise typer.Exit(code=1)
57
+
58
+
59
+ @contextmanager
60
+ def _handle_errors() -> Any:
61
+ """Context manager to handle Krita errors gracefully."""
62
+ try:
63
+ yield
64
+ except KritaError as exc:
65
+ _handle_error(exc)
66
+
67
+
68
+ def _get_client(ctx: Context) -> KritaClient:
69
+ """Create a Krita client from the Typer context."""
70
+ state: CLIState = ctx.obj or CLIState()
71
+ if state.url is not None:
72
+ config = ClientConfig(url=state.url)
73
+ else:
74
+ plugin_config = load_config()
75
+ port = plugin_config.get("port", 5678)
76
+ config = ClientConfig(url=f"http://localhost:{port}")
77
+ return KritaClient(config)
78
+
79
+
80
+ def _format_result(result: dict[str, object]) -> None:
81
+ """Display a command result in a readable format."""
82
+ if "error" in result:
83
+ console.print(f"[red]Error:[/red] {result['error']}")
84
+ raise typer.Exit(code=1)
85
+ for key, value in result.items(): # pragma: no cover - display-only
86
+ if key == "status":
87
+ continue
88
+ console.print(f"[dim]{key}:[/dim] {value}")
89
+
90
+
91
+ def _print_result(result: dict[str, object], message: str) -> None:
92
+ """Display a command result with a custom message."""
93
+ console.print(f"[green]{message}[/green]")
94
+ _format_result(result)
krita_cli/app.py ADDED
@@ -0,0 +1,149 @@
1
+ """Krita CLI — Main application composition."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from typing import Annotated
7
+
8
+ import typer
9
+ from typer import Context
10
+
11
+ from krita_cli import _shared
12
+ from krita_cli.commands import config as _config
13
+ from krita_client import (
14
+ KritaCommandError,
15
+ KritaConnectionError,
16
+ KritaValidationError,
17
+ )
18
+
19
+ app = typer.Typer(
20
+ name="krita",
21
+ help="CLI for programmatic painting in Krita via the MCP plugin.",
22
+ no_args_is_help=True,
23
+ add_completion=False,
24
+ )
25
+ app.add_typer(_config.app, name="config")
26
+ console = _shared.console
27
+
28
+ # Re-export shared utilities for backward compatibility
29
+ CLIState = _shared.CLIState
30
+ _handle_error = _shared._handle_error
31
+ _get_client = _shared._get_client
32
+ _format_result = _shared._format_result
33
+
34
+
35
+ # -- Global options -----------------------------------------------------------
36
+
37
+
38
+ @app.callback()
39
+ def callback(
40
+ ctx: Context,
41
+ url: Annotated[
42
+ str | None,
43
+ typer.Option("--url", "-u", help="Krita plugin URL (overrides KRITA_URL env var)"),
44
+ ] = None,
45
+ ) -> None:
46
+ """Krita CLI — programmatic painting in Krita."""
47
+ ctx.obj = CLIState()
48
+ ctx.obj.url = url
49
+
50
+
51
+ # -- Register sub-apps --------------------------------------------------------
52
+ # Lazy imports to avoid circular dependency between app.py and command modules.
53
+ from krita_cli.commands import (
54
+ batch as _batch,
55
+ )
56
+ from krita_cli.commands import (
57
+ brush as _brush,
58
+ )
59
+ from krita_cli.commands import (
60
+ call as _call,
61
+ )
62
+ from krita_cli.commands import (
63
+ canvas as _canvas,
64
+ )
65
+ from krita_cli.commands import (
66
+ color as _color,
67
+ )
68
+ from krita_cli.commands import (
69
+ file_ops as _file_ops,
70
+ )
71
+ from krita_cli.commands import (
72
+ health as _health,
73
+ )
74
+ from krita_cli.commands import (
75
+ history_cmd as _history_cmd,
76
+ )
77
+ from krita_cli.commands import (
78
+ introspect as _introspect,
79
+ )
80
+ from krita_cli.commands import (
81
+ layers as _layers,
82
+ )
83
+ from krita_cli.commands import (
84
+ navigation as _navigation,
85
+ )
86
+ from krita_cli.commands import (
87
+ replay as _replay,
88
+ )
89
+ from krita_cli.commands import (
90
+ rollback as _rollback,
91
+ )
92
+ from krita_cli.commands import (
93
+ selection as _selection,
94
+ )
95
+ from krita_cli.commands import (
96
+ stroke as _stroke,
97
+ )
98
+
99
+ app.add_typer(_canvas.app, name="canvas")
100
+ app.add_typer(_color.app, name="color")
101
+ app.add_typer(_brush.app, name="brush")
102
+ app.add_typer(_stroke.app, name="stroke")
103
+ app.add_typer(_navigation.app, name="navigation")
104
+ app.add_typer(_file_ops.app, name="file")
105
+ app.add_typer(_health.app)
106
+ app.add_typer(_call.app)
107
+ app.add_typer(_history_cmd.app)
108
+ app.add_typer(_batch.app)
109
+ app.add_typer(_rollback.app)
110
+ app.add_typer(_introspect.app, name="introspect")
111
+ app.add_typer(_layers.app, name="layers")
112
+ app.add_typer(_replay.app)
113
+ app.add_typer(_selection.app, name="selection")
114
+
115
+ # Re-export command functions for backward compatibility (cli.py shim)
116
+ new_canvas = _canvas.new_canvas
117
+ get_canvas = _canvas.get_canvas
118
+ save = _canvas.save
119
+ clear = _canvas.clear
120
+ set_color = _color.set_color
121
+ get_color_at = _color.get_color_at
122
+ set_brush = _brush.set_brush
123
+ list_brushes = _brush.list_brushes
124
+ stroke = _stroke.stroke
125
+ fill = _stroke.fill
126
+ draw_shape = _stroke.draw_shape
127
+ undo = _navigation.undo
128
+ redo = _navigation.redo
129
+ open_file = _file_ops.open_file
130
+ health = _health.health
131
+ call = _call.call
132
+
133
+
134
+ # -- Entry point --------------------------------------------------------------
135
+
136
+
137
+ def main() -> None: # pragma: no cover
138
+ """Main entry point for the CLI."""
139
+ try:
140
+ app()
141
+ except (KritaConnectionError, KritaCommandError, KritaValidationError) as exc:
142
+ _handle_error(exc)
143
+ except KeyboardInterrupt:
144
+ console.print("\n[dim]Interrupted.[/dim]")
145
+ sys.exit(130)
146
+
147
+
148
+ if __name__ == "__main__": # pragma: no cover
149
+ main()
krita_cli/cli.py ADDED
@@ -0,0 +1,59 @@
1
+ """Krita CLI — Compatibility shim.
2
+
3
+ All functionality has moved to krita_cli.app.
4
+ This module re-exports everything for backward compatibility.
5
+ """
6
+
7
+ from krita_cli.app import (
8
+ CLIState,
9
+ _format_result,
10
+ _get_client,
11
+ _handle_error,
12
+ app,
13
+ call,
14
+ callback,
15
+ clear,
16
+ console,
17
+ draw_shape,
18
+ fill,
19
+ get_canvas,
20
+ get_color_at,
21
+ health,
22
+ list_brushes,
23
+ main,
24
+ new_canvas,
25
+ open_file,
26
+ redo,
27
+ save,
28
+ set_brush,
29
+ set_color,
30
+ stroke,
31
+ undo,
32
+ )
33
+
34
+ __all__ = [
35
+ "CLIState",
36
+ "_format_result",
37
+ "_get_client",
38
+ "_handle_error",
39
+ "app",
40
+ "call",
41
+ "callback",
42
+ "clear",
43
+ "console",
44
+ "draw_shape",
45
+ "fill",
46
+ "get_canvas",
47
+ "get_color_at",
48
+ "health",
49
+ "list_brushes",
50
+ "main",
51
+ "new_canvas",
52
+ "open_file",
53
+ "redo",
54
+ "save",
55
+ "set_brush",
56
+ "set_color",
57
+ "stroke",
58
+ "undo",
59
+ ]
@@ -0,0 +1 @@
1
+ """CLI command sub-applications."""
@@ -0,0 +1,86 @@
1
+ """Batch command CLI command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+ from typing import Annotated, Any, cast
8
+
9
+ import typer
10
+ from rich.console import Console
11
+ from typer import Context
12
+
13
+ from krita_cli import _shared
14
+ from krita_client import KritaError
15
+
16
+ console = Console()
17
+
18
+ app = typer.Typer()
19
+
20
+
21
+ @app.command()
22
+ def batch(
23
+ ctx: Context,
24
+ file: Annotated[Path, typer.Argument(help="JSON file with batch commands")],
25
+ stop_on_error: Annotated[bool, typer.Option("--stop-on-error", help="Stop on first error")] = False,
26
+ ) -> None:
27
+ """Execute multiple commands from a JSON file.
28
+
29
+ The JSON file should contain an array of command objects, each with
30
+ an "action" and optional "params" key.
31
+
32
+ Example JSON file:
33
+ [
34
+ {"action": "set_color", "params": {"color": "#ff0000"}},
35
+ {"action": "stroke", "params": {"points": [[0, 0], [100, 100]]}},
36
+ {"action": "fill", "params": {"x": 200, "y": 200, "radius": 30}}
37
+ ]
38
+ """
39
+ try:
40
+ commands = json.loads(file.read_text())
41
+ except json.JSONDecodeError as exc:
42
+ console.print(f"[red]Error:[/red] Invalid JSON in {file}: {exc}")
43
+ raise typer.Exit(code=1) from exc
44
+ except OSError as exc:
45
+ console.print(f"[red]Error:[/red] Cannot read {file}: {exc}")
46
+ raise typer.Exit(code=1) from exc
47
+
48
+ if not isinstance(commands, list):
49
+ console.print("[red]Error:[/red] JSON file must contain an array of commands.")
50
+ raise typer.Exit(code=1)
51
+
52
+ try:
53
+ client = _shared._get_client(ctx)
54
+ result = client.batch_execute(commands, stop_on_error=stop_on_error)
55
+ status = result.get("status", "unknown")
56
+ results_raw = result.get("results", [])
57
+ if not isinstance(results_raw, list):
58
+ results_raw = []
59
+ results = cast("list[dict[str, Any]]", results_raw)
60
+
61
+ ok = sum(1 for r in results if isinstance(r, dict) and r.get("status") == "ok")
62
+ errs = sum(1 for r in results if isinstance(r, dict) and r.get("status") == "error")
63
+ count = result.get("count", 0)
64
+ color = "green" if status == "ok" else "red" if status == "error" else "yellow"
65
+ console.print(f"[{color}]Batch: {status}[/{color}]")
66
+ console.print(f" {ok} succeeded, {errs} failed out of {count}")
67
+ batch_id = result.get("batch_id")
68
+ if batch_id:
69
+ console.print(f" Batch ID: [dim]{batch_id}[/dim]")
70
+ if errs > 0:
71
+ console.print("[red]Errors:[/red]")
72
+ for r in results:
73
+ if isinstance(r, dict) and r.get("status") == "error":
74
+ err_msg = r.get("error")
75
+ if not err_msg:
76
+ result_data = r.get("result", {})
77
+ if isinstance(result_data, dict) and "error" in result_data:
78
+ err_info = result_data["error"]
79
+ err_msg = (
80
+ err_info.get("message", str(err_info)) if isinstance(err_info, dict) else str(err_info)
81
+ )
82
+ if not err_msg:
83
+ err_msg = "unknown"
84
+ console.print(f" [red]✗ {r.get('action', 'unknown')}: {err_msg}[/red]")
85
+ except KritaError as exc:
86
+ _shared._handle_error(exc)
@@ -0,0 +1,58 @@
1
+ """Brush-related CLI commands: set-brush, list-brushes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated
6
+
7
+ import typer
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+ from typer import Context
11
+
12
+ from krita_cli import _shared
13
+ from krita_client import KritaError
14
+
15
+ console = Console()
16
+
17
+ app = typer.Typer()
18
+
19
+
20
+ @app.command("set-brush")
21
+ def set_brush(
22
+ ctx: Context,
23
+ preset: Annotated[str | None, typer.Option("--preset", "-p", help="Brush preset name")] = None,
24
+ size: Annotated[int | None, typer.Option("--size", "-s", help="Brush size in pixels")] = None,
25
+ opacity: Annotated[float | None, typer.Option("--opacity", "-o", help="Brush opacity (0.0-1.0)")] = None,
26
+ ) -> None:
27
+ """Set brush preset and properties."""
28
+ try:
29
+ client = _shared._get_client(ctx)
30
+ result = client.set_brush(preset=preset, size=size, opacity=opacity)
31
+ _shared._format_result(result)
32
+ except KritaError as exc:
33
+ _shared._handle_error(exc)
34
+
35
+
36
+ @app.command("list-brushes")
37
+ def list_brushes(
38
+ ctx: Context,
39
+ filter: Annotated[str, typer.Option("--filter", "-f", help="Filter by name")] = "", # noqa: A002
40
+ limit: Annotated[int, typer.Option("--limit", "-l", help="Maximum number to return")] = 20,
41
+ ) -> None:
42
+ """List available brush presets."""
43
+ try:
44
+ client = _shared._get_client(ctx)
45
+ result = client.list_brushes(filter=filter, limit=limit)
46
+ brushes_raw = result.get("brushes", [])
47
+ brushes = list(brushes_raw) if isinstance(brushes_raw, list) else []
48
+ if not brushes:
49
+ console.print("No brushes found matching filter.")
50
+ return
51
+ table = Table(title=f"Available Brushes ({len(brushes)})")
52
+ table.add_column("#", style="dim")
53
+ table.add_column("Name")
54
+ for i, name in enumerate(brushes, 1):
55
+ table.add_row(str(i), name)
56
+ console.print(table)
57
+ except KritaError as exc:
58
+ _shared._handle_error(exc)
@@ -0,0 +1,49 @@
1
+ """Raw command mode CLI command: call."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from typing import Annotated
7
+
8
+ import typer
9
+ from rich.console import Console
10
+ from typer import Context
11
+
12
+ from krita_cli import _shared
13
+ from krita_client import KritaError
14
+
15
+ console = Console()
16
+
17
+ app = typer.Typer()
18
+
19
+
20
+ @app.command()
21
+ def call(
22
+ ctx: Context,
23
+ action: Annotated[str, typer.Argument(help="Command action name")],
24
+ params_json: Annotated[str | None, typer.Argument(help="JSON params string")] = None,
25
+ ) -> None:
26
+ """Send a raw command to the Krita plugin.
27
+
28
+ Useful for commands not yet exposed as subcommands, or for scripting.
29
+
30
+ \b
31
+ Examples:
32
+ krita call new_canvas '{"width": 1920, "height": 1080}'
33
+ krita call set_color '{"color": "#ff0000"}'
34
+ krita call stroke '{"points": [[0,0],[100,100]]}'
35
+ """
36
+ params: dict[str, object] = {}
37
+ if params_json:
38
+ try:
39
+ params = json.loads(params_json)
40
+ except json.JSONDecodeError as exc:
41
+ console.print(f"[red]Error:[/red] Invalid JSON: {exc}")
42
+ raise typer.Exit(code=1) from exc
43
+
44
+ try:
45
+ client = _shared._get_client(ctx)
46
+ result = client.send_command(action, params)
47
+ console.print(json.dumps(result, indent=2, default=str))
48
+ except KritaError as exc:
49
+ _shared._handle_error(exc)
@@ -0,0 +1,77 @@
1
+ """Canvas-related CLI commands: new-canvas, get-canvas, save, clear."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated
6
+
7
+ import typer
8
+ from rich.console import Console
9
+ from typer import Context
10
+
11
+ from krita_cli import _shared
12
+ from krita_client import KritaError
13
+
14
+ console = Console()
15
+
16
+ app = typer.Typer()
17
+
18
+
19
+ @app.command("new-canvas")
20
+ def new_canvas(
21
+ ctx: Context,
22
+ width: Annotated[int, typer.Option("--width", "-W", help="Canvas width in pixels")] = 800,
23
+ height: Annotated[int, typer.Option("--height", "-H", help="Canvas height in pixels")] = 600,
24
+ name: Annotated[str, typer.Option("--name", "-n", help="Document name")] = "New Canvas",
25
+ background: Annotated[str, typer.Option("--background", "-b", help="Background color (hex)")] = "#1a1a2e",
26
+ ) -> None:
27
+ """Create a new canvas in Krita."""
28
+ try:
29
+ client = _shared._get_client(ctx)
30
+ result = client.new_canvas(width=width, height=height, name=name, background=background)
31
+ _shared._format_result(result)
32
+ except KritaError as exc:
33
+ _shared._handle_error(exc)
34
+
35
+
36
+ @app.command("get-canvas")
37
+ def get_canvas(
38
+ ctx: Context,
39
+ filename: Annotated[str, typer.Option("--filename", "-f", help="Output filename")] = "canvas.png",
40
+ ) -> None:
41
+ """Export the current canvas to a PNG file."""
42
+ console.print("[dim]Exporting canvas (this may take a while for large canvases)...[/dim]")
43
+ try:
44
+ client = _shared._get_client(ctx)
45
+ result = client.get_canvas(filename=filename)
46
+ _shared._format_result(result)
47
+ except KritaError as exc:
48
+ _shared._handle_error(exc)
49
+
50
+
51
+ @app.command()
52
+ def save(
53
+ ctx: Context,
54
+ path: Annotated[str, typer.Argument(help="Full file path to save to")],
55
+ ) -> None:
56
+ """Save the current canvas to a specific file path."""
57
+ console.print("[dim]Saving canvas (this may take a while for large canvases)...[/dim]")
58
+ try:
59
+ client = _shared._get_client(ctx)
60
+ result = client.save(path=path)
61
+ _shared._format_result(result)
62
+ except KritaError as exc:
63
+ _shared._handle_error(exc)
64
+
65
+
66
+ @app.command()
67
+ def clear(
68
+ ctx: Context,
69
+ color: Annotated[str, typer.Option("--color", "-c", help="Color to fill with")] = "#1a1a2e",
70
+ ) -> None:
71
+ """Clear the canvas to a solid color."""
72
+ try:
73
+ client = _shared._get_client(ctx)
74
+ result = client.clear(color=color)
75
+ _shared._format_result(result)
76
+ except KritaError as exc:
77
+ _shared._handle_error(exc)
@@ -0,0 +1,42 @@
1
+ """Color-related CLI commands: set-color, get-color-at."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated
6
+
7
+ import typer
8
+ from typer import Context
9
+
10
+ from krita_cli import _shared
11
+ from krita_client import KritaError
12
+
13
+ app = typer.Typer()
14
+
15
+
16
+ @app.command("set-color")
17
+ def set_color(
18
+ ctx: Context,
19
+ color: Annotated[str, typer.Argument(help="Hex color code (e.g., #ff6b6b)")],
20
+ ) -> None:
21
+ """Set the foreground paint color."""
22
+ try:
23
+ client = _shared._get_client(ctx)
24
+ result = client.set_color(color=color)
25
+ _shared._format_result(result)
26
+ except KritaError as exc:
27
+ _shared._handle_error(exc)
28
+
29
+
30
+ @app.command("get-color-at")
31
+ def get_color_at(
32
+ ctx: Context,
33
+ x: Annotated[int, typer.Argument(help="X coordinate")],
34
+ y: Annotated[int, typer.Argument(help="Y coordinate")],
35
+ ) -> None:
36
+ """Sample the color at a specific pixel (eyedropper)."""
37
+ try:
38
+ client = _shared._get_client(ctx)
39
+ result = client.get_color_at(x=x, y=y)
40
+ _shared._format_result(result)
41
+ except KritaError as exc:
42
+ _shared._handle_error(exc)
@@ -0,0 +1,48 @@
1
+ """CLI commands for plugin configuration: show, set, reset."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Annotated
6
+
7
+ import typer
8
+ from rich.console import Console
9
+ from rich.table import Table
10
+
11
+ from krita_cli import config_cmd
12
+
13
+ app = typer.Typer(name="config", help="Plugin configuration commands.")
14
+ console = Console()
15
+
16
+
17
+ @app.command("show")
18
+ def config_show() -> None:
19
+ """Show current plugin configuration."""
20
+ config = config_cmd.load_config()
21
+ table = Table(title=f"Plugin Config ({config_cmd.CONFIG_FILE})")
22
+ table.add_column("Key", style="cyan")
23
+ table.add_column("Value", style="green")
24
+ for key, value in config.items():
25
+ table.add_row(str(key), str(value))
26
+ console.print(table)
27
+
28
+
29
+ @app.command("set")
30
+ def config_set(
31
+ key: Annotated[str, typer.Argument(help="Configuration key")],
32
+ value: Annotated[str, typer.Argument(help="Configuration value")],
33
+ ) -> None:
34
+ """Set a plugin configuration value."""
35
+ try:
36
+ config_cmd.set_key(key, value)
37
+ except ValueError as exc:
38
+ console.print(f"[red]Error:[/red] {exc}")
39
+ raise typer.Exit(1) from exc
40
+ console.print(f"[green]Set {key} = {value}[/green]")
41
+ console.print("[dim]Restart Krita for changes to take effect.[/dim]")
42
+
43
+
44
+ @app.command("reset")
45
+ def config_reset() -> None:
46
+ """Reset plugin configuration to defaults."""
47
+ config_cmd.reset_config()
48
+ console.print("[green]Configuration reset to defaults.[/green]")