scc-cli 1.4.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.

Potentially problematic release.


This version of scc-cli might be problematic. Click here for more details.

Files changed (112) hide show
  1. scc_cli/__init__.py +15 -0
  2. scc_cli/audit/__init__.py +37 -0
  3. scc_cli/audit/parser.py +191 -0
  4. scc_cli/audit/reader.py +180 -0
  5. scc_cli/auth.py +145 -0
  6. scc_cli/claude_adapter.py +485 -0
  7. scc_cli/cli.py +259 -0
  8. scc_cli/cli_admin.py +683 -0
  9. scc_cli/cli_audit.py +245 -0
  10. scc_cli/cli_common.py +166 -0
  11. scc_cli/cli_config.py +527 -0
  12. scc_cli/cli_exceptions.py +705 -0
  13. scc_cli/cli_helpers.py +244 -0
  14. scc_cli/cli_init.py +272 -0
  15. scc_cli/cli_launch.py +1400 -0
  16. scc_cli/cli_org.py +1433 -0
  17. scc_cli/cli_support.py +322 -0
  18. scc_cli/cli_team.py +858 -0
  19. scc_cli/cli_worktree.py +865 -0
  20. scc_cli/config.py +583 -0
  21. scc_cli/console.py +562 -0
  22. scc_cli/constants.py +79 -0
  23. scc_cli/contexts.py +377 -0
  24. scc_cli/deprecation.py +54 -0
  25. scc_cli/deps.py +189 -0
  26. scc_cli/docker/__init__.py +127 -0
  27. scc_cli/docker/core.py +466 -0
  28. scc_cli/docker/credentials.py +726 -0
  29. scc_cli/docker/launch.py +603 -0
  30. scc_cli/doctor/__init__.py +99 -0
  31. scc_cli/doctor/checks.py +1082 -0
  32. scc_cli/doctor/render.py +346 -0
  33. scc_cli/doctor/types.py +66 -0
  34. scc_cli/errors.py +288 -0
  35. scc_cli/evaluation/__init__.py +27 -0
  36. scc_cli/evaluation/apply_exceptions.py +207 -0
  37. scc_cli/evaluation/evaluate.py +97 -0
  38. scc_cli/evaluation/models.py +80 -0
  39. scc_cli/exit_codes.py +55 -0
  40. scc_cli/git.py +1405 -0
  41. scc_cli/json_command.py +166 -0
  42. scc_cli/json_output.py +96 -0
  43. scc_cli/kinds.py +62 -0
  44. scc_cli/marketplace/__init__.py +123 -0
  45. scc_cli/marketplace/compute.py +377 -0
  46. scc_cli/marketplace/constants.py +87 -0
  47. scc_cli/marketplace/managed.py +135 -0
  48. scc_cli/marketplace/materialize.py +723 -0
  49. scc_cli/marketplace/normalize.py +548 -0
  50. scc_cli/marketplace/render.py +238 -0
  51. scc_cli/marketplace/resolve.py +459 -0
  52. scc_cli/marketplace/schema.py +502 -0
  53. scc_cli/marketplace/sync.py +257 -0
  54. scc_cli/marketplace/team_cache.py +195 -0
  55. scc_cli/marketplace/team_fetch.py +688 -0
  56. scc_cli/marketplace/trust.py +244 -0
  57. scc_cli/models/__init__.py +41 -0
  58. scc_cli/models/exceptions.py +273 -0
  59. scc_cli/models/plugin_audit.py +434 -0
  60. scc_cli/org_templates.py +269 -0
  61. scc_cli/output_mode.py +167 -0
  62. scc_cli/panels.py +113 -0
  63. scc_cli/platform.py +350 -0
  64. scc_cli/profiles.py +1034 -0
  65. scc_cli/remote.py +443 -0
  66. scc_cli/schemas/__init__.py +1 -0
  67. scc_cli/schemas/org-v1.schema.json +456 -0
  68. scc_cli/schemas/team-config.v1.schema.json +163 -0
  69. scc_cli/sessions.py +425 -0
  70. scc_cli/setup.py +582 -0
  71. scc_cli/source_resolver.py +470 -0
  72. scc_cli/stats.py +378 -0
  73. scc_cli/stores/__init__.py +13 -0
  74. scc_cli/stores/exception_store.py +251 -0
  75. scc_cli/subprocess_utils.py +88 -0
  76. scc_cli/teams.py +339 -0
  77. scc_cli/templates/__init__.py +2 -0
  78. scc_cli/templates/org/__init__.py +0 -0
  79. scc_cli/templates/org/minimal.json +19 -0
  80. scc_cli/templates/org/reference.json +74 -0
  81. scc_cli/templates/org/strict.json +38 -0
  82. scc_cli/templates/org/teams.json +42 -0
  83. scc_cli/templates/statusline.sh +75 -0
  84. scc_cli/theme.py +348 -0
  85. scc_cli/ui/__init__.py +124 -0
  86. scc_cli/ui/branding.py +68 -0
  87. scc_cli/ui/chrome.py +395 -0
  88. scc_cli/ui/dashboard/__init__.py +62 -0
  89. scc_cli/ui/dashboard/_dashboard.py +669 -0
  90. scc_cli/ui/dashboard/loaders.py +369 -0
  91. scc_cli/ui/dashboard/models.py +184 -0
  92. scc_cli/ui/dashboard/orchestrator.py +337 -0
  93. scc_cli/ui/formatters.py +443 -0
  94. scc_cli/ui/gate.py +350 -0
  95. scc_cli/ui/help.py +157 -0
  96. scc_cli/ui/keys.py +521 -0
  97. scc_cli/ui/list_screen.py +431 -0
  98. scc_cli/ui/picker.py +700 -0
  99. scc_cli/ui/prompts.py +200 -0
  100. scc_cli/ui/wizard.py +490 -0
  101. scc_cli/update.py +680 -0
  102. scc_cli/utils/__init__.py +39 -0
  103. scc_cli/utils/fixit.py +264 -0
  104. scc_cli/utils/fuzzy.py +124 -0
  105. scc_cli/utils/locks.py +101 -0
  106. scc_cli/utils/ttl.py +376 -0
  107. scc_cli/validate.py +455 -0
  108. scc_cli-1.4.0.dist-info/METADATA +369 -0
  109. scc_cli-1.4.0.dist-info/RECORD +112 -0
  110. scc_cli-1.4.0.dist-info/WHEEL +4 -0
  111. scc_cli-1.4.0.dist-info/entry_points.txt +2 -0
  112. scc_cli-1.4.0.dist-info/licenses/LICENSE +21 -0
scc_cli/cli_helpers.py ADDED
@@ -0,0 +1,244 @@
1
+ """Provide centralized CLI helpers for confirmation and safety patterns.
2
+
3
+ Provide standardized helpers for:
4
+ - Destructive operation confirmation (prune, worktree remove, etc.)
5
+ - Governance command validation (unblock, exceptions)
6
+ - Non-interactive mode detection (CI environments)
7
+ - JSON mode compatibility
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ import sys
14
+ from dataclasses import dataclass
15
+ from datetime import datetime, timezone
16
+
17
+ import typer
18
+ from rich.console import Console
19
+
20
+ from .exit_codes import EXIT_USAGE
21
+ from .output_mode import is_json_mode
22
+
23
+ console = Console()
24
+ stderr_console = Console(stderr=True)
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class ConfirmItems:
29
+ """Hold items to display before a confirmation prompt.
30
+
31
+ Attributes:
32
+ title: Header text shown above the item list.
33
+ items: List of item descriptions to display.
34
+ max_display: Maximum items to show before truncating with count.
35
+ """
36
+
37
+ title: str
38
+ items: list[str]
39
+ max_display: int = 10
40
+
41
+
42
+ def is_interactive() -> bool:
43
+ """Check if running in interactive mode.
44
+
45
+ Return False if:
46
+ - stdin is not a TTY (piped input, redirected)
47
+ - CI environment variable is set to truthy value
48
+ - JSON output mode is active
49
+
50
+ Returns:
51
+ True if interactive prompts are safe to use.
52
+ """
53
+ if is_json_mode():
54
+ return False
55
+
56
+ is_tty = sys.stdin.isatty()
57
+ is_ci = os.getenv("CI", "").lower() in ("1", "true", "yes")
58
+
59
+ return is_tty and not is_ci
60
+
61
+
62
+ def confirm_action(
63
+ *,
64
+ yes: bool,
65
+ prompt: str,
66
+ dry_run: bool = False,
67
+ items: ConfirmItems | None = None,
68
+ non_interactive_requires_yes: bool = True,
69
+ ) -> bool:
70
+ """Standard confirmation behavior for destructive operations.
71
+
72
+ Args:
73
+ yes: If True, skip confirmation prompt.
74
+ prompt: The confirmation question to ask.
75
+ dry_run: If True, return False (caller should not mutate state).
76
+ items: Optional list of affected items to display before prompting.
77
+ non_interactive_requires_yes: If True, exit with EXIT_USAGE when
78
+ running non-interactively without --yes.
79
+
80
+ Returns:
81
+ True if action should proceed, False if dry-run mode.
82
+
83
+ Raises:
84
+ typer.Exit(EXIT_USAGE): If non-interactive and would prompt without --yes.
85
+ typer.Abort: If user declines confirmation.
86
+
87
+ Behavior:
88
+ - If dry_run: return False (caller should not mutate state)
89
+ - If yes: return True (skip prompt)
90
+ - If JSON mode: exit EXIT_USAGE (never prompt in JSON mode)
91
+ - If non-interactive and would prompt: exit EXIT_USAGE
92
+ - Otherwise: print affected resources (if provided) and prompt
93
+ """
94
+ if dry_run:
95
+ return False
96
+
97
+ if yes:
98
+ return True
99
+
100
+ # JSON mode must never prompt
101
+ if is_json_mode():
102
+ stderr_console.print(
103
+ "[red]Error:[/red] Cannot prompt in JSON mode. Use --yes to confirm.",
104
+ style="bold",
105
+ )
106
+ raise typer.Exit(EXIT_USAGE)
107
+
108
+ # Non-interactive mode detection
109
+ if not is_interactive() and non_interactive_requires_yes:
110
+ console.print(
111
+ "[red]Error:[/red] This operation requires confirmation. "
112
+ "Use --yes to skip in non-interactive mode.",
113
+ style="bold",
114
+ )
115
+ raise typer.Exit(EXIT_USAGE)
116
+
117
+ # Display affected items
118
+ if items:
119
+ console.print(f"\n[bold]{items.title}[/bold]")
120
+ shown = items.items[: items.max_display]
121
+ for item in shown:
122
+ console.print(f" [dim]•[/dim] {item}")
123
+ if len(items.items) > items.max_display:
124
+ remaining = len(items.items) - items.max_display
125
+ console.print(f" [dim](+ {remaining} more)[/dim]")
126
+ console.print()
127
+
128
+ # Prompt for confirmation
129
+ return typer.confirm(prompt, abort=True)
130
+
131
+
132
+ def require_reason_for_governance(
133
+ *,
134
+ yes: bool,
135
+ reason: str | None,
136
+ command_name: str = "unblock",
137
+ ) -> str:
138
+ """Require --reason when --yes is used for governance commands.
139
+
140
+ Args:
141
+ yes: Whether --yes flag was provided.
142
+ reason: The reason string if provided via --reason.
143
+ command_name: Name of the command for error messages.
144
+
145
+ Returns:
146
+ The reason string (either provided or collected interactively).
147
+
148
+ Raises:
149
+ typer.Exit(EXIT_USAGE): If --yes used without --reason.
150
+ """
151
+ if yes and not reason:
152
+ console.print(
153
+ f"[red]Error:[/red] --reason is required when using --yes with {command_name}.",
154
+ style="bold",
155
+ )
156
+ raise typer.Exit(EXIT_USAGE)
157
+
158
+ if reason:
159
+ return reason
160
+
161
+ # Interactive mode: prompt for reason
162
+ if is_json_mode():
163
+ stderr_console.print(
164
+ "[red]Error:[/red] Cannot prompt for reason in JSON mode. Use --reason.",
165
+ style="bold",
166
+ )
167
+ raise typer.Exit(EXIT_USAGE)
168
+
169
+ if not is_interactive():
170
+ console.print(
171
+ "[red]Error:[/red] Cannot prompt for reason in non-interactive mode. Use --reason.",
172
+ style="bold",
173
+ )
174
+ raise typer.Exit(EXIT_USAGE)
175
+
176
+ prompted_reason: str = typer.prompt("Reason for this exception")
177
+ return prompted_reason
178
+
179
+
180
+ @dataclass(frozen=True)
181
+ class AuditRecord:
182
+ """Audit record for governance operations."""
183
+
184
+ timestamp: datetime
185
+ command: str
186
+ actor: str
187
+ target: str
188
+ reason: str
189
+ ticket: str | None = None
190
+ expires_in: str | None = None
191
+
192
+ def to_dict(self) -> dict[str, str | None]:
193
+ """Convert to dictionary for JSON serialization."""
194
+ result: dict[str, str | None] = {
195
+ "timestamp": self.timestamp.isoformat(),
196
+ "command": self.command,
197
+ "actor": self.actor,
198
+ "target": self.target,
199
+ "reason": self.reason,
200
+ }
201
+ if self.ticket:
202
+ result["ticket"] = self.ticket
203
+ if self.expires_in:
204
+ result["expires_in"] = self.expires_in
205
+ return result
206
+
207
+
208
+ def get_current_user() -> str:
209
+ """Get the current user for audit purposes."""
210
+ return os.getenv("USER", os.getenv("USERNAME", "unknown"))
211
+
212
+
213
+ def create_audit_record(
214
+ *,
215
+ command: str,
216
+ target: str,
217
+ reason: str,
218
+ ticket: str | None = None,
219
+ expires_in: str | None = None,
220
+ ) -> AuditRecord:
221
+ """Create an audit record for governance operations.
222
+
223
+ This is mandatory for governance commands (unblock, exception creation).
224
+ For other destructive operations, use only if SCC_AUDIT_LOG is enabled.
225
+
226
+ Args:
227
+ command: The governance command being executed.
228
+ target: The resource being affected (e.g., plugin name, policy).
229
+ reason: Justification for the operation.
230
+ ticket: Optional issue/ticket reference for traceability.
231
+ expires_in: Optional duration string for temporary exceptions.
232
+
233
+ Returns:
234
+ A timestamped audit record with actor information.
235
+ """
236
+ return AuditRecord(
237
+ timestamp=datetime.now(timezone.utc),
238
+ command=command,
239
+ actor=get_current_user(),
240
+ target=target,
241
+ reason=reason,
242
+ ticket=ticket,
243
+ expires_in=expires_in,
244
+ )
scc_cli/cli_init.py ADDED
@@ -0,0 +1,272 @@
1
+ """
2
+ Define the SCC init command for project configuration initialization.
3
+
4
+ Create a .scc.yaml configuration file in a project directory with
5
+ sensible defaults and helpful comments.
6
+ """
7
+
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ import typer
12
+ from rich.panel import Panel
13
+
14
+ from .cli_common import console, handle_errors
15
+ from .cli_helpers import confirm_action, is_interactive
16
+ from .exit_codes import EXIT_CONFIG, EXIT_SUCCESS, EXIT_USAGE
17
+ from .json_output import build_envelope
18
+ from .kinds import Kind
19
+ from .output_mode import print_json, set_pretty_mode
20
+
21
+ # ─────────────────────────────────────────────────────────────────────────────
22
+ # Pure Functions (No I/O)
23
+ # ─────────────────────────────────────────────────────────────────────────────
24
+
25
+
26
+ def is_git_repo(path: Path) -> bool:
27
+ """Check if path is a git repository.
28
+
29
+ Args:
30
+ path: Directory path to check.
31
+
32
+ Returns:
33
+ True if .git directory exists, False otherwise.
34
+ """
35
+ git_dir = path / ".git"
36
+ return git_dir.exists() and git_dir.is_dir()
37
+
38
+
39
+ def build_init_data(
40
+ file_path: str,
41
+ created: bool,
42
+ overwritten: bool,
43
+ is_git_repo: bool,
44
+ ) -> dict[str, Any]:
45
+ """Build init result data for JSON output.
46
+
47
+ Args:
48
+ file_path: Path to created .scc.yaml file.
49
+ created: Whether the file was created.
50
+ overwritten: Whether an existing file was overwritten.
51
+ is_git_repo: Whether the target is a git repository.
52
+
53
+ Returns:
54
+ Dictionary with init result data.
55
+ """
56
+ return {
57
+ "file_path": file_path,
58
+ "created": created,
59
+ "overwritten": overwritten,
60
+ "is_git_repo": is_git_repo,
61
+ }
62
+
63
+
64
+ def generate_template_content() -> str:
65
+ """Generate .scc.yaml template content with helpful comments.
66
+
67
+ Returns:
68
+ YAML template string with comments.
69
+ """
70
+ return """\
71
+ # SCC Project Configuration
72
+ # ─────────────────────────────────────────────────────────────────────────────
73
+ # This file configures SCC (Sandboxed Claude CLI) for this project.
74
+ # Place this file in your repository root.
75
+ #
76
+ # For full documentation, see: https://github.com/sundsvall/scc-cli#configuration
77
+ # ─────────────────────────────────────────────────────────────────────────────
78
+
79
+ # Additional plugins to enable for this project
80
+ # These plugins are loaded on top of your team profile's plugins.
81
+ # Only plugins allowed by your organization can be added here.
82
+ additional_plugins: []
83
+ # - "project-specific-linter"
84
+ # - "custom-formatter"
85
+
86
+ # Session configuration
87
+ session:
88
+ # Session timeout in hours (default: 8)
89
+ timeout_hours: 8
90
+
91
+ # Optional: MCP servers specific to this project
92
+ # mcp_servers: []
93
+ # - name: "project-db"
94
+ # command: "npx"
95
+ # args: ["@project/mcp-server"]
96
+
97
+ # Optional: Environment variables for the sandbox
98
+ # env: {}
99
+ # PROJECT_NAME: "my-project"
100
+ """
101
+
102
+
103
+ # ─────────────────────────────────────────────────────────────────────────────
104
+ # CLI Command
105
+ # ─────────────────────────────────────────────────────────────────────────────
106
+
107
+
108
+ @handle_errors
109
+ def init_cmd(
110
+ path: str | None = typer.Argument(
111
+ None,
112
+ help="Target directory (default: current directory).",
113
+ ),
114
+ force: bool = typer.Option(
115
+ False,
116
+ "--force",
117
+ "-f",
118
+ help="Overwrite existing .scc.yaml file without prompting.",
119
+ ),
120
+ yes: bool = typer.Option(
121
+ False,
122
+ "-y",
123
+ "--yes",
124
+ help="Skip confirmation prompts (still requires --force to overwrite).",
125
+ ),
126
+ json_output: bool = typer.Option(
127
+ False,
128
+ "--json",
129
+ help="Output as JSON.",
130
+ ),
131
+ pretty: bool = typer.Option(
132
+ False,
133
+ "--pretty",
134
+ help="Pretty-print JSON (implies --json).",
135
+ ),
136
+ ) -> None:
137
+ """Initialize SCC project configuration.
138
+
139
+ Creates a .scc.yaml file in the target directory with sensible defaults
140
+ and helpful comments explaining each configuration option.
141
+
142
+ Three-tier overwrite logic:
143
+ - If file doesn't exist: create it
144
+ - If file exists + --force: overwrite without prompting
145
+ - If file exists + no --force: prompt in interactive mode, or hint to use --force
146
+ """
147
+ # --pretty implies --json
148
+ if pretty:
149
+ json_output = True
150
+ set_pretty_mode(True)
151
+
152
+ # Resolve target path
153
+ if path is None:
154
+ target_dir = Path.cwd()
155
+ else:
156
+ target_dir = Path(path).resolve()
157
+
158
+ # Validate target directory
159
+ if not target_dir.exists():
160
+ if json_output:
161
+ envelope = build_envelope(
162
+ Kind.INIT_RESULT,
163
+ ok=False,
164
+ errors=[f"Directory does not exist: {target_dir}"],
165
+ )
166
+ print_json(envelope)
167
+ raise typer.Exit(EXIT_CONFIG)
168
+ else:
169
+ console.print(f"[red]Error:[/red] Directory does not exist: {target_dir}")
170
+ raise typer.Exit(EXIT_CONFIG)
171
+
172
+ if not target_dir.is_dir():
173
+ if json_output:
174
+ envelope = build_envelope(
175
+ Kind.INIT_RESULT,
176
+ ok=False,
177
+ errors=[f"Path is not a directory: {target_dir}"],
178
+ )
179
+ print_json(envelope)
180
+ raise typer.Exit(EXIT_CONFIG)
181
+ else:
182
+ console.print(f"[red]Error:[/red] Path is not a directory: {target_dir}")
183
+ raise typer.Exit(EXIT_CONFIG)
184
+
185
+ # Check for existing file - three-tier overwrite logic
186
+ scc_yaml = target_dir / ".scc.yaml"
187
+ overwritten = False
188
+
189
+ if scc_yaml.exists():
190
+ if force:
191
+ # Tier 3: --force → overwrite without prompting
192
+ overwritten = True
193
+ elif json_output:
194
+ # JSON mode: never prompt, just tell user to use --force
195
+ envelope = build_envelope(
196
+ Kind.INIT_RESULT,
197
+ ok=False,
198
+ errors=[f"File already exists: {scc_yaml}. Use --force to overwrite."],
199
+ )
200
+ print_json(envelope)
201
+ raise typer.Exit(EXIT_CONFIG)
202
+ elif yes:
203
+ # Tier 4: --yes without --force → hint that --force is required
204
+ console.print(
205
+ f"[yellow]Warning:[/yellow] File already exists: [cyan]{scc_yaml}[/cyan]\n"
206
+ "[dim]--yes skips prompts but does not allow overwriting.[/dim]\n"
207
+ "Use [yellow]--force[/yellow] to overwrite existing file."
208
+ )
209
+ raise typer.Exit(EXIT_USAGE)
210
+ elif is_interactive():
211
+ # Tier 2: Interactive without --force → prompt for confirmation
212
+ console.print(f"[yellow]Warning:[/yellow] File already exists: [cyan]{scc_yaml}[/cyan]")
213
+ try:
214
+ confirm_action(
215
+ yes=False,
216
+ prompt="Overwrite existing .scc.yaml?",
217
+ non_interactive_requires_yes=False,
218
+ )
219
+ overwritten = True
220
+ except typer.Abort:
221
+ console.print("[dim]Aborted.[/dim]")
222
+ raise typer.Exit(EXIT_SUCCESS)
223
+ else:
224
+ # Tier 3: Non-interactive without --force → exit with usage error
225
+ console.print(
226
+ f"[red]Error:[/red] File already exists: [cyan]{scc_yaml}[/cyan]\n"
227
+ "Use [yellow]--force[/yellow] to overwrite in non-interactive mode."
228
+ )
229
+ raise typer.Exit(EXIT_USAGE)
230
+
231
+ # Check if git repo and warn if not
232
+ is_git = is_git_repo(target_dir)
233
+ if not is_git and not json_output:
234
+ console.print(
235
+ "[yellow]Warning:[/yellow] Target directory is not a git repository.\n"
236
+ "SCC works best with git-tracked projects for branch safety and worktree features."
237
+ )
238
+
239
+ # Generate and write template
240
+ template_content = generate_template_content()
241
+ scc_yaml.write_text(template_content)
242
+
243
+ # Build result data
244
+ result_data = build_init_data(
245
+ file_path=str(scc_yaml),
246
+ created=True,
247
+ overwritten=overwritten,
248
+ is_git_repo=is_git,
249
+ )
250
+
251
+ # Output
252
+ if json_output:
253
+ envelope = build_envelope(Kind.INIT_RESULT, data=result_data)
254
+ print_json(envelope)
255
+ raise typer.Exit(EXIT_SUCCESS)
256
+ else:
257
+ action = "Overwrote" if overwritten else "Created"
258
+ console.print(
259
+ Panel(
260
+ f"{action} [cyan]{scc_yaml}[/cyan]\n\n"
261
+ "Edit this file to configure project-specific settings.\n"
262
+ "See the comments in the file for available options.",
263
+ title="[green]SCC Initialized[/green]",
264
+ border_style="green",
265
+ )
266
+ )
267
+ if not is_git:
268
+ console.print(
269
+ "\n[dim]Tip: Initialize a git repository with [cyan]git init[/cyan] "
270
+ "to enable branch safety and worktree features.[/dim]"
271
+ )
272
+ raise typer.Exit(EXIT_SUCCESS)