dsh-crate-web 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.
Files changed (57) hide show
  1. package/README.md +302 -0
  2. package/README.zh-CN.md +304 -0
  3. package/cordis.patch.yml +11 -0
  4. package/core/dsh_pack/__init__.py +77 -0
  5. package/core/dsh_pack/__main__.py +4 -0
  6. package/core/dsh_pack/cli.py +372 -0
  7. package/core/dsh_pack/errors.py +167 -0
  8. package/core/dsh_pack/export.py +912 -0
  9. package/core/dsh_pack/importer.py +1347 -0
  10. package/core/dsh_pack/network.py +77 -0
  11. package/core/dsh_pack/pack.py +248 -0
  12. package/core/dsh_pack/preflight.py +811 -0
  13. package/core/dsh_pack/user_cli.py +462 -0
  14. package/core/dsh_pack/validation.py +272 -0
  15. package/core/dsh_pack/verify.py +1336 -0
  16. package/lib/client.js +1623 -0
  17. package/lib/index.d.ts +30 -0
  18. package/lib/index.js +1275 -0
  19. package/package.json +73 -0
  20. package/restart-helper.mjs +121 -0
  21. package/runtime/python/LICENSE.txt +676 -0
  22. package/runtime/python/__install__.json +82 -0
  23. package/runtime/python/_asyncio.pyd +0 -0
  24. package/runtime/python/_bz2.pyd +0 -0
  25. package/runtime/python/_ctypes.pyd +0 -0
  26. package/runtime/python/_decimal.pyd +0 -0
  27. package/runtime/python/_elementtree.pyd +0 -0
  28. package/runtime/python/_hashlib.pyd +0 -0
  29. package/runtime/python/_lzma.pyd +0 -0
  30. package/runtime/python/_multiprocessing.pyd +0 -0
  31. package/runtime/python/_overlapped.pyd +0 -0
  32. package/runtime/python/_queue.pyd +0 -0
  33. package/runtime/python/_remote_debugging.pyd +0 -0
  34. package/runtime/python/_socket.pyd +0 -0
  35. package/runtime/python/_sqlite3.pyd +0 -0
  36. package/runtime/python/_ssl.pyd +0 -0
  37. package/runtime/python/_uuid.pyd +0 -0
  38. package/runtime/python/_wmi.pyd +0 -0
  39. package/runtime/python/_zoneinfo.pyd +0 -0
  40. package/runtime/python/_zstd.pyd +0 -0
  41. package/runtime/python/libcrypto-3.dll +0 -0
  42. package/runtime/python/libffi-8.dll +0 -0
  43. package/runtime/python/libssl-3.dll +0 -0
  44. package/runtime/python/pyexpat.pyd +0 -0
  45. package/runtime/python/python.cat +0 -0
  46. package/runtime/python/python.exe +0 -0
  47. package/runtime/python/python3.dll +0 -0
  48. package/runtime/python/python314._pth +6 -0
  49. package/runtime/python/python314.dll +0 -0
  50. package/runtime/python/python314.zip +0 -0
  51. package/runtime/python/pythonw.exe +0 -0
  52. package/runtime/python/select.pyd +0 -0
  53. package/runtime/python/sqlite3.dll +0 -0
  54. package/runtime/python/unicodedata.pyd +0 -0
  55. package/runtime/python/vcruntime140.dll +0 -0
  56. package/runtime/python/vcruntime140_1.dll +0 -0
  57. package/runtime/python/winsound.pyd +0 -0
@@ -0,0 +1,462 @@
1
+ """User-facing Phase 6.5 CLI adapter.
2
+
3
+ This module is deliberately a thin boundary around the existing Core CLI.
4
+ It translates stable user arguments, preserves the Core JSON result under
5
+ ``result``, and adds operation reports. Pack schema, Preflight, Import, and
6
+ Verify decisions remain owned by :mod:`dsh_pack.cli` and the Core modules.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import json
13
+ import os
14
+ import re
15
+ import uuid
16
+ from contextlib import redirect_stderr, redirect_stdout
17
+ from datetime import datetime, timezone
18
+ from io import StringIO
19
+ from pathlib import Path
20
+ from typing import Any, Sequence
21
+
22
+ from . import cli as core_cli
23
+ from .errors import PackImportError
24
+ from .importer import ImportOptions, create_import_plan
25
+
26
+
27
+ _OPERATION_ID = re.compile(r"^op-[0-9a-f]{32}$")
28
+
29
+
30
+ def _default_dsh_home() -> Path:
31
+ value = os.environ.get("DSH_HOME")
32
+ return (Path(value).expanduser() if value else Path.home() / ".dsh").resolve()
33
+
34
+
35
+ def _dsh_home(value: str | None) -> Path:
36
+ return (Path(value).expanduser() if value else _default_dsh_home()).resolve()
37
+
38
+
39
+ def _profile_path(value: str, home: Path) -> Path:
40
+ candidate = Path(value).expanduser()
41
+ if (
42
+ candidate.is_absolute()
43
+ or candidate.exists()
44
+ or "/" in value
45
+ or "\\" in value
46
+ or (len(value) > 1 and value[1] == ":")
47
+ ):
48
+ return candidate.resolve()
49
+ return (home / "profiles" / value).resolve()
50
+
51
+
52
+ def _operation_home(args: argparse.Namespace, *, profile: str | None = None) -> Path:
53
+ explicit = getattr(args, "dsh_home", None)
54
+ if explicit:
55
+ return _dsh_home(explicit)
56
+ if profile:
57
+ candidate = Path(profile).expanduser()
58
+ if candidate.is_absolute() or "/" in profile or "\\" in profile:
59
+ resolved = candidate.resolve()
60
+ if resolved.parent.name == "profiles":
61
+ return resolved.parent.parent
62
+ return _default_dsh_home()
63
+
64
+
65
+ def _json_error(message: str) -> dict[str, Any]:
66
+ return {"status": "failed", "error": {"message": message}}
67
+
68
+
69
+ def _invoke_core(argv: list[str]) -> tuple[int, dict[str, Any]]:
70
+ """Invoke the existing Core entry point and parse its JSON contract."""
71
+
72
+ stdout = StringIO()
73
+ stderr = StringIO()
74
+ try:
75
+ with redirect_stdout(stdout), redirect_stderr(stderr):
76
+ exit_code = core_cli.main([*argv, "--json"])
77
+ except SystemExit as error:
78
+ exit_code = int(error.code or 0)
79
+ except Exception as error: # keep the user CLI machine-readable
80
+ return 1, _json_error(str(error))
81
+
82
+ raw = stdout.getvalue().strip()
83
+ if not raw:
84
+ return exit_code, _json_error(stderr.getvalue().strip() or "Core returned no JSON result")
85
+ try:
86
+ result = json.loads(raw)
87
+ except json.JSONDecodeError:
88
+ return exit_code, _json_error(raw)
89
+ if not isinstance(result, dict):
90
+ return exit_code, _json_error("Core returned a non-object JSON result")
91
+ return exit_code, result
92
+
93
+
94
+ def _user_status(command: str, exit_code: int, result: dict[str, Any]) -> str:
95
+ if command == "verify":
96
+ status = result.get("status")
97
+ return status if isinstance(status, str) else ("PASS" if exit_code == 0 else "FAIL")
98
+ if command == "inspect":
99
+ status = result.get("status")
100
+ return status if isinstance(status, str) else ("READY" if exit_code == 0 else "NOT_READY")
101
+ return "PASS" if exit_code == 0 else "FAIL"
102
+
103
+
104
+ def _envelope(
105
+ command: str,
106
+ exit_code: int,
107
+ result: dict[str, Any],
108
+ *,
109
+ operation_id: str | None,
110
+ dry_run: bool = False,
111
+ ) -> dict[str, Any]:
112
+ status = _user_status(command, exit_code, result)
113
+ if dry_run and command == "import":
114
+ preflight = result.get("plan", {}).get("preflight", {}) if isinstance(result.get("plan"), dict) else {}
115
+ if isinstance(preflight, dict) and preflight.get("status") in {"READY", "NOT_READY"}:
116
+ status = preflight["status"]
117
+ return {
118
+ "operationId": operation_id,
119
+ "command": command,
120
+ "status": status,
121
+ "exitCode": exit_code,
122
+ "dryRun": dry_run,
123
+ "result": result,
124
+ }
125
+
126
+
127
+ def _new_operation_id() -> str:
128
+ return f"op-{uuid.uuid4().hex}"
129
+
130
+
131
+ def _operation_path(home: Path, operation_id: str) -> Path:
132
+ if not _OPERATION_ID.fullmatch(operation_id):
133
+ raise ValueError("invalid operation id")
134
+ return home / ".dsh-pack" / "operations" / f"{operation_id}.json"
135
+
136
+
137
+ def _record_operation(home: Path, envelope: dict[str, Any]) -> None:
138
+ operation_id = envelope.get("operationId")
139
+ if not isinstance(operation_id, str):
140
+ return
141
+ path = _operation_path(home, operation_id)
142
+ path.parent.mkdir(parents=True, exist_ok=True)
143
+ record = {
144
+ "schemaVersion": 1,
145
+ "createdAt": datetime.now(timezone.utc).isoformat(),
146
+ **envelope,
147
+ }
148
+ temporary = path.with_name(f".{path.name}.{uuid.uuid4().hex}.tmp")
149
+ try:
150
+ temporary.write_text(json.dumps(record, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8")
151
+ os.replace(temporary, path)
152
+ finally:
153
+ if temporary.exists():
154
+ temporary.unlink()
155
+
156
+
157
+ def _load_operation(home: Path, operation_id: str) -> dict[str, Any]:
158
+ path = _operation_path(home, operation_id)
159
+ try:
160
+ value = json.loads(path.read_text(encoding="utf-8"))
161
+ except FileNotFoundError as error:
162
+ raise ValueError(f"operation report not found: {operation_id}") from error
163
+ except (OSError, json.JSONDecodeError) as error:
164
+ raise ValueError(f"operation report is unreadable: {operation_id}") from error
165
+ if not isinstance(value, dict):
166
+ raise ValueError(f"operation report is not a JSON object: {operation_id}")
167
+ return value
168
+
169
+
170
+ def _print(value: dict[str, Any], *, as_json: bool) -> None:
171
+ if as_json:
172
+ print(json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True))
173
+ return
174
+ operation_id = value.get("operationId")
175
+ if operation_id:
176
+ print(f"Operation: {operation_id}")
177
+ elif value.get("dryRun"):
178
+ print("Operation: not persisted (--dry-run)")
179
+ print(f"Status: {value.get('status', 'UNKNOWN')}")
180
+ result = value.get("result", value)
181
+ print(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=True))
182
+
183
+
184
+ def _append_option(argv: list[str], flag: str, value: str | None) -> None:
185
+ if value:
186
+ argv.extend([flag, value])
187
+
188
+
189
+ def _append_repeated(argv: list[str], flag: str, values: list[str]) -> None:
190
+ for value in values:
191
+ argv.extend([flag, value])
192
+
193
+
194
+ def _export_core_args(args: argparse.Namespace, home: Path) -> list[str]:
195
+ argv = ["export", "--profile", str(_profile_path(args.profile, home)), "--output", str(Path(args.output).expanduser()), "--json"]
196
+ _append_option(argv, "--environment-json", args.environment_json)
197
+ for flag, value in (
198
+ ("--dsh-version", args.dsh_version),
199
+ ("--node-version", args.node_version),
200
+ ("--os-name", args.os_name),
201
+ ("--os-version", args.os_version),
202
+ ("--os-arch", args.os_arch),
203
+ ):
204
+ _append_option(argv, flag, value)
205
+ _append_repeated(argv, "--embed", args.embed)
206
+ _append_repeated(argv, "--reference-only", args.reference_only)
207
+ if args.include_installed_bundles:
208
+ argv.append("--include-installed-bundles")
209
+ _append_repeated(argv, "--required-secret", args.required_secret)
210
+ return argv
211
+
212
+
213
+ def _inspect_core_args(args: argparse.Namespace) -> list[str]:
214
+ argv = ["inspect", args.pack, "--json"]
215
+ for flag, value in (
216
+ ("--dsh-version", args.dsh_version),
217
+ ("--node-version", args.node_version),
218
+ ("--os-name", args.os_name),
219
+ ("--os-version", args.os_version),
220
+ ):
221
+ _append_option(argv, flag, value)
222
+ _append_repeated(argv, "--secret", args.secret)
223
+ _append_repeated(argv, "--plugin", args.plugin)
224
+ if args.check_reference_plugins:
225
+ argv.append("--check-reference-plugins")
226
+ if args.no_process_secrets:
227
+ argv.append("--no-process-secrets")
228
+ return argv
229
+
230
+
231
+ def _import_core_args(args: argparse.Namespace, home: Path) -> list[str]:
232
+ argv = ["import", args.pack, "--dsh-home", str(home), "--json"]
233
+ for flag, value in (
234
+ ("--dsh-version", args.dsh_version),
235
+ ("--node-version", args.node_version),
236
+ ("--os-name", args.os_name),
237
+ ("--os-version", args.os_version),
238
+ ):
239
+ _append_option(argv, flag, value)
240
+ _append_repeated(argv, "--secret", args.secret)
241
+ _append_repeated(argv, "--reference-plugin", args.reference_plugin)
242
+ _append_option(argv, "--target-profile", args.target_profile)
243
+ if args.overwrite:
244
+ argv.append("--overwrite")
245
+ if args.confirm_overwrite:
246
+ argv.append("--confirm-overwrite")
247
+ if args.offline:
248
+ argv.append("--offline")
249
+ if args.no_process_secrets:
250
+ argv.append("--no-process-secrets")
251
+ return argv
252
+
253
+
254
+ def _verify_core_args(args: argparse.Namespace, home: Path) -> list[str]:
255
+ argv = ["verify", "--dsh-home", str(home), "--profile", args.profile, "--mode", args.mode, "--json"]
256
+ _append_option(argv, "--runner-config", args.runner_config)
257
+ _append_repeated(argv, "--plugin-contract", args.plugin_contract)
258
+ for flag, value in (
259
+ ("--dsh-version", args.dsh_version),
260
+ ("--node-version", args.node_version),
261
+ ("--os-name", args.os_name),
262
+ ("--os-version", args.os_version),
263
+ ):
264
+ _append_option(argv, flag, value)
265
+ _append_repeated(argv, "--secret", args.secret)
266
+ if args.no_process_secrets:
267
+ argv.append("--no-process-secrets")
268
+ return argv
269
+
270
+
271
+ def _delete_core_args(args: argparse.Namespace, home: Path) -> list[str]:
272
+ argv = ["delete-profile", "--dsh-home", str(home), "--profile", args.profile, "--json"]
273
+ if args.confirm_delete:
274
+ argv.append("--confirm-delete")
275
+ return argv
276
+
277
+
278
+ def _finish_operation(
279
+ command: str,
280
+ args: argparse.Namespace,
281
+ home: Path,
282
+ exit_code: int,
283
+ result: dict[str, Any],
284
+ ) -> int:
285
+ envelope = _envelope(command, exit_code, result, operation_id=_new_operation_id())
286
+ try:
287
+ _record_operation(home, envelope)
288
+ except OSError as error:
289
+ # The Core result remains authoritative; report persistence is a
290
+ # secondary diagnostic aid and must not rewrite Core decisions.
291
+ envelope["reportError"] = str(error)
292
+ _print(envelope, as_json=args.json)
293
+ return exit_code
294
+
295
+
296
+ def _run_export(args: argparse.Namespace) -> int:
297
+ home = _operation_home(args, profile=args.profile)
298
+ exit_code, result = _invoke_core(_export_core_args(args, home))
299
+ return _finish_operation("export", args, home, exit_code, result)
300
+
301
+
302
+ def _run_inspect(args: argparse.Namespace) -> int:
303
+ home = _operation_home(args)
304
+ exit_code, result = _invoke_core(_inspect_core_args(args))
305
+ return _finish_operation("inspect", args, home, exit_code, result)
306
+
307
+
308
+ def _import_options(args: argparse.Namespace) -> ImportOptions:
309
+ environment: dict[str, dict[str, str]] = {}
310
+ for section, version in (("dsh", args.dsh_version), ("node", args.node_version)):
311
+ if version:
312
+ environment[section] = {"version": version}
313
+ if args.os_name or args.os_version:
314
+ environment["os"] = {
315
+ key: value
316
+ for key, value in (("name", args.os_name), ("version", args.os_version))
317
+ if value
318
+ }
319
+ available_secrets = set() if args.no_process_secrets else set(os.environ)
320
+ available_secrets.update(args.secret)
321
+ return ImportOptions(
322
+ current_environment=environment,
323
+ available_secrets=available_secrets,
324
+ reference_plugin_sources=core_cli._reference_sources(args.reference_plugin),
325
+ allow_network_reference_install=not args.offline,
326
+ target_profile_name=args.target_profile,
327
+ overwrite_existing_profile=args.overwrite,
328
+ overwrite_confirmation="OVERWRITE" if args.confirm_overwrite else None,
329
+ )
330
+
331
+
332
+ def _run_dry_import(args: argparse.Namespace, home: Path) -> tuple[int, dict[str, Any]]:
333
+ try:
334
+ plan = create_import_plan(args.pack, home, options=_import_options(args))
335
+ except (PackImportError, ValueError, OSError) as error:
336
+ return 1, {"status": "failed", "error": error.as_dict() if isinstance(error, PackImportError) else {"message": str(error)}}
337
+ return 0, {"status": "dry-run", "readOnly": True, "plan": plan.as_dict()}
338
+
339
+
340
+ def _run_import(args: argparse.Namespace) -> int:
341
+ home = _dsh_home(args.dsh_home)
342
+ if args.dry_run:
343
+ exit_code, result = _run_dry_import(args, home)
344
+ envelope = _envelope("import", exit_code, result, operation_id=None, dry_run=True)
345
+ _print(envelope, as_json=args.json)
346
+ return exit_code
347
+ exit_code, result = _invoke_core(_import_core_args(args, home))
348
+ return _finish_operation("import", args, home, exit_code, result)
349
+
350
+
351
+ def _run_verify(args: argparse.Namespace) -> int:
352
+ home = _dsh_home(args.dsh_home)
353
+ exit_code, result = _invoke_core(_verify_core_args(args, home))
354
+ return _finish_operation("verify", args, home, exit_code, result)
355
+
356
+
357
+ def _run_delete_profile(args: argparse.Namespace) -> int:
358
+ home = _dsh_home(args.dsh_home)
359
+ exit_code, result = _invoke_core(_delete_core_args(args, home))
360
+ return _finish_operation("delete-profile", args, home, exit_code, result)
361
+
362
+
363
+ def _run_report(args: argparse.Namespace) -> int:
364
+ try:
365
+ report = _load_operation(_dsh_home(args.dsh_home), args.operation_id)
366
+ except ValueError as error:
367
+ value = {"status": "FAIL", "error": {"message": str(error)}}
368
+ _print(value, as_json=args.json)
369
+ return 1
370
+ _print(report, as_json=args.json)
371
+ # Reading a report succeeded even when the recorded operation failed.
372
+ # The original operation exit code remains available in the report.
373
+ return 0
374
+
375
+
376
+ def _add_runtime_options(parser: argparse.ArgumentParser) -> None:
377
+ parser.add_argument("--dsh-home", help="DSH_HOME used for Profile resolution and operation reports")
378
+ parser.add_argument("--dsh-version", help="current DSH version evidence")
379
+ parser.add_argument("--node-version", help="current Node version evidence")
380
+ parser.add_argument("--os-name", help="current OS name evidence")
381
+ parser.add_argument("--os-version", help="current OS version evidence")
382
+ parser.add_argument("--secret", action="append", default=[], help="declare an available Secret name")
383
+ parser.add_argument("--no-process-secrets", action="store_true", help="do not use current process environment names")
384
+
385
+
386
+ def _build_parser() -> argparse.ArgumentParser:
387
+ parser = argparse.ArgumentParser(prog="dsh-crate", description="User-facing DSH Crate commands backed by the existing Core")
388
+ subparsers = parser.add_subparsers(dest="command", required=True)
389
+
390
+ export = subparsers.add_parser("export", help="export one Profile to a .dshcrate")
391
+ export.add_argument("--profile", required=True, help="Profile name or Profile directory")
392
+ export.add_argument("-o", "--output", required=True, help="output .dshcrate path")
393
+ export.add_argument("--environment-json", help="JSON file containing Pack environment")
394
+ export.add_argument("--dsh-version", default="unknown", help="DSH version evidence")
395
+ export.add_argument("--node-version", default="unknown", help="Node version evidence")
396
+ export.add_argument("--os-name", default="unknown", help="OS name evidence")
397
+ export.add_argument("--os-version", default="unknown", help="OS version evidence")
398
+ export.add_argument("--os-arch", default="unknown", help="OS architecture evidence")
399
+ export.add_argument("--embed", action="append", default=[], metavar="PLUGIN")
400
+ export.add_argument("--reference-only", action="append", default=[], metavar="PLUGIN")
401
+ export.add_argument("--include-installed-bundles", action="store_true", help="include direct installed Bundle inventory in the exported Profile composition")
402
+ export.add_argument("--required-secret", action="append", default=[], metavar="NAME")
403
+ export.add_argument("--json", action="store_true", help="emit machine-readable JSON")
404
+ export.add_argument("--dsh-home", help="DSH_HOME used for Profile-name resolution and reports")
405
+ export.set_defaults(handler=_run_export)
406
+
407
+ inspect = subparsers.add_parser("inspect", help="read-only Pack preflight")
408
+ inspect.add_argument("pack", help="input .dshcrate")
409
+ _add_runtime_options(inspect)
410
+ inspect.add_argument("--plugin", action="append", default=[], metavar="NAME[@VERSION]")
411
+ inspect.add_argument("--check-reference-plugins", action="store_true")
412
+ inspect.add_argument("--json", action="store_true", help="emit machine-readable JSON")
413
+ inspect.set_defaults(handler=_run_inspect)
414
+
415
+ import_parser = subparsers.add_parser("import", help="import a Pack as a new Profile")
416
+ import_parser.add_argument("pack", help="input .dshcrate")
417
+ _add_runtime_options(import_parser)
418
+ import_parser.add_argument("--dry-run", action="store_true", help="preflight and plan only; never write the target DSH_HOME")
419
+ import_parser.add_argument("--offline", action="store_true", help="disable npm/GitHub fallback for missing reference-only plugins")
420
+ import_parser.add_argument("--target-profile", help="new target Profile name; duplicate names are rejected")
421
+ import_parser.add_argument("--overwrite", action="store_true", help="replace an existing Profile after confirmation")
422
+ import_parser.add_argument("--confirm-overwrite", action="store_true", help="confirm the destructive overwrite operation")
423
+ import_parser.add_argument("--reference-plugin", action="append", default=[], metavar="NAME=PACKAGE_DIR")
424
+ import_parser.add_argument("--json", action="store_true", help="emit machine-readable JSON")
425
+ import_parser.set_defaults(handler=_run_import)
426
+
427
+ verify = subparsers.add_parser("verify", help="verify a prepared Profile")
428
+ verify.add_argument("--profile", required=True, help="Profile name under DSH_HOME/profiles")
429
+ verify.add_argument("--mode", choices=("web", "headless"), default="web")
430
+ verify.add_argument("--runner-config", help="JSON config for an explicit subprocess VerifyAdapter")
431
+ verify.add_argument("--plugin-contract", action="append", default=[])
432
+ _add_runtime_options(verify)
433
+ verify.add_argument("--json", action="store_true", help="emit machine-readable JSON")
434
+ verify.set_defaults(handler=_run_verify)
435
+
436
+ report = subparsers.add_parser("report", help="read a persisted operation report")
437
+ report.add_argument("operation_id", help="operation id returned by a non-dry-run command")
438
+ report.add_argument("--dsh-home", help="DSH_HOME containing .dsh-pack/operations")
439
+ report.add_argument("--json", action="store_true", help="emit machine-readable JSON")
440
+ report.set_defaults(handler=_run_report)
441
+
442
+ delete = subparsers.add_parser("delete-profile", help="delete one Profile after explicit confirmation")
443
+ delete.add_argument("--profile", required=True, help="Profile name under DSH_HOME/profiles")
444
+ delete.add_argument("--confirm-delete", action="store_true", help="confirm the destructive deletion")
445
+ delete.add_argument("--json", action="store_true", help="emit machine-readable JSON")
446
+ delete.add_argument("--dsh-home", help="DSH_HOME containing the Profile and operation reports")
447
+ delete.set_defaults(handler=_run_delete_profile)
448
+ return parser
449
+
450
+
451
+ def main(argv: Sequence[str] | None = None) -> int:
452
+ # The Web bridge may resolve the installed ``dsh-pack`` console script.
453
+ # Keep that existing integration on the raw Core contract; the bridge
454
+ # sets this marker explicitly and never consumes the user envelope.
455
+ if os.environ.get("DSH_PACK_CORE_MODE") == "1":
456
+ return core_cli.main(argv)
457
+ args = _build_parser().parse_args(argv)
458
+ return args.handler(args)
459
+
460
+
461
+ if __name__ == "__main__": # pragma: no cover - exercised by the console script
462
+ raise SystemExit(main())