gardusig-cli 0.1.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.
Files changed (93) hide show
  1. cli/__init__.py +3 -0
  2. cli/__main__.py +4 -0
  3. cli/cli.py +71 -0
  4. cli/commands/__init__.py +1 -0
  5. cli/commands/backup.py +49 -0
  6. cli/commands/bookmarks.py +21 -0
  7. cli/commands/chrome.py +77 -0
  8. cli/commands/contest.py +120 -0
  9. cli/commands/docker.py +386 -0
  10. cli/commands/drive.py +223 -0
  11. cli/commands/gh.py +419 -0
  12. cli/commands/git.py +790 -0
  13. cli/commands/links.py +81 -0
  14. cli/commands/notion.py +215 -0
  15. cli/commands/publish.py +63 -0
  16. cli/commands/restore.py +8 -0
  17. cli/integration/__init__.py +5 -0
  18. cli/integration/cli_api_checks.py +531 -0
  19. cli/integration/contest_integration.py +156 -0
  20. cli/integration/docker_integration.py +346 -0
  21. cli/integration/docker_mocks.py +125 -0
  22. cli/integration/git_mocks.py +59 -0
  23. cli/integration/integration_coverage.py +329 -0
  24. cli/integration/public_commands.py +82 -0
  25. cli/integration/public_endpoints.py +1151 -0
  26. cli/integration/tag_zip_integration.py +449 -0
  27. cli/integration/workflow_integration.py +804 -0
  28. cli/integration/workspaces.py +83 -0
  29. cli/internal/__init__.py +1 -0
  30. cli/internal/read/__init__.py +9 -0
  31. cli/internal/read/git.py +47 -0
  32. cli/internal/read/safety.py +99 -0
  33. cli/internal/write/__init__.py +13 -0
  34. cli/internal/write/gate.py +66 -0
  35. cli/internal/write/git.py +38 -0
  36. cli/models/__init__.py +1 -0
  37. cli/models/backup.py +10 -0
  38. cli/models/bookmark.py +14 -0
  39. cli/models/repository.py +9 -0
  40. cli/models/task.py +68 -0
  41. cli/providers/__init__.py +1 -0
  42. cli/providers/base.py +23 -0
  43. cli/providers/chrome.py +9 -0
  44. cli/providers/drive_client.py +56 -0
  45. cli/providers/gh.py +47 -0
  46. cli/providers/github.py +13 -0
  47. cli/providers/google_drive.py +31 -0
  48. cli/providers/icloud_drive.py +17 -0
  49. cli/providers/notion.py +244 -0
  50. cli/providers/onedrive.py +31 -0
  51. cli/providers/proton_drive.py +31 -0
  52. cli/services/__init__.py +3 -0
  53. cli/services/backup_repository.py +181 -0
  54. cli/services/backup_zip.py +65 -0
  55. cli/services/bookmark_sync.py +9 -0
  56. cli/services/contest_docker.py +193 -0
  57. cli/services/contest_runner.py +274 -0
  58. cli/services/contest_serde.py +30 -0
  59. cli/services/docker_runtime.py +332 -0
  60. cli/services/drive_sync.py +108 -0
  61. cli/services/gh_sequence.py +66 -0
  62. cli/services/gh_service.py +344 -0
  63. cli/services/git_archive.py +5 -0
  64. cli/services/git_review.py +34 -0
  65. cli/services/git_shortcuts.py +610 -0
  66. cli/services/notion_markdown.py +173 -0
  67. cli/services/notion_pairs.py +232 -0
  68. cli/services/notion_sync.py +220 -0
  69. cli/services/pypi_publish.py +80 -0
  70. cli/services/replica_deploy.py +128 -0
  71. cli/utils/__init__.py +12 -0
  72. cli/utils/catalog.py +179 -0
  73. cli/utils/config.py +316 -0
  74. cli/utils/confirm.py +18 -0
  75. cli/utils/external_client.py +143 -0
  76. cli/utils/fs.py +13 -0
  77. cli/utils/hashing.py +12 -0
  78. cli/utils/http.py +10 -0
  79. cli/utils/logger.py +15 -0
  80. cli/utils/process.py +62 -0
  81. cli/utils/quick_defaults.py +40 -0
  82. cli/utils/retry.py +30 -0
  83. cli/utils/yaml.py +22 -0
  84. cli/utils/zip.py +18 -0
  85. cli/workflows/__init__.py +1 -0
  86. cli/workflows/backup.py +1 -0
  87. cli/workflows/restore.py +1 -0
  88. gardusig_cli-0.1.0.dist-info/METADATA +274 -0
  89. gardusig_cli-0.1.0.dist-info/RECORD +93 -0
  90. gardusig_cli-0.1.0.dist-info/WHEEL +5 -0
  91. gardusig_cli-0.1.0.dist-info/entry_points.txt +2 -0
  92. gardusig_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  93. gardusig_cli-0.1.0.dist-info/top_level.txt +1 -0
cli/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """cli: git shortcuts and backup/sync workflows."""
2
+
3
+ __version__ = "0.1.0"
cli/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from cli.cli import run
2
+
3
+ if __name__ == "__main__":
4
+ run()
cli/cli.py ADDED
@@ -0,0 +1,71 @@
1
+ from __future__ import annotations
2
+
3
+ import typer
4
+
5
+ from cli import __version__
6
+ from cli.commands.backup import backup_app
7
+ from cli.commands.bookmarks import bookmarks_app
8
+ from cli.commands.chrome import chrome_app
9
+ from cli.commands.contest import contest_app
10
+ from cli.commands.docker import docker_app
11
+ from cli.commands.drive import drive_app
12
+ from cli.commands.gh import gh_app
13
+ from cli.commands.git import git_app
14
+ from cli.commands.links import links_app
15
+ from cli.commands.notion import notion_app
16
+ from cli.commands.publish import publish_app
17
+ from cli.commands.restore import restore_app
18
+ from cli.utils.logger import setup_logging
19
+
20
+ app = typer.Typer(
21
+ name="cli",
22
+ help="Git shortcuts and drive (tag zips) for macOS. Run `cli links` for docs and scripts.",
23
+ no_args_is_help=True,
24
+ )
25
+
26
+ app.add_typer(links_app, name="links")
27
+ app.add_typer(git_app, name="git")
28
+ app.add_typer(gh_app, name="gh")
29
+ app.add_typer(backup_app, name="backup", hidden=True)
30
+ app.add_typer(restore_app, name="restore")
31
+ app.add_typer(drive_app, name="drive")
32
+ app.add_typer(notion_app, name="notion")
33
+ app.add_typer(chrome_app, name="chrome")
34
+ app.add_typer(bookmarks_app, name="bookmarks", hidden=True)
35
+ app.add_typer(docker_app, name="docker")
36
+ app.add_typer(contest_app, name="contest")
37
+ app.add_typer(publish_app, name="publish")
38
+
39
+
40
+ @app.callback(invoke_without_command=True)
41
+ def main(
42
+ ctx: typer.Context,
43
+ version: bool = typer.Option(False, "--version", "-V", help="Show version and exit."),
44
+ verbose: bool = typer.Option(False, "--verbose", "-v"),
45
+ ) -> None:
46
+ setup_logging(verbose=verbose)
47
+ if version:
48
+ typer.echo(__version__)
49
+ raise typer.Exit()
50
+ if ctx.invoked_subcommand is None and not version:
51
+ typer.echo(ctx.get_help())
52
+ typer.echo("\nFull index: cli links | docs/README.md")
53
+ raise typer.Exit()
54
+ ctx.obj = {"verbose": verbose}
55
+
56
+
57
+ def run() -> None:
58
+ """CLI entrypoint — surfaces ExternalCallError as a clean user message."""
59
+ from cli.utils.config import load_local_env
60
+ from cli.utils.external_client import ExternalCallError
61
+
62
+ load_local_env()
63
+ try:
64
+ app()
65
+ except ExternalCallError as exc:
66
+ typer.echo(exc.user_message, err=True)
67
+ raise typer.Exit(1) from exc
68
+
69
+
70
+ # Short alias: cli g <cmd> == cli git <cmd>
71
+ app.add_typer(git_app, name="g", hidden=True)
@@ -0,0 +1 @@
1
+ """CLI command modules."""
cli/commands/backup.py ADDED
@@ -0,0 +1,49 @@
1
+ """Hidden aliases for legacy `cli backup` → use `cli drive` instead."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from cli.commands.drive import delete_cmd, ingest_cmd, list_cmd, status_cmd
8
+
9
+ backup_app = typer.Typer(
10
+ help="(deprecated) use cli drive",
11
+ no_args_is_help=True,
12
+ hidden=True,
13
+ )
14
+ repository_app = typer.Typer(help="(deprecated)", hidden=True)
15
+
16
+
17
+ @backup_app.command("status")
18
+ def backup_status_alias() -> None:
19
+ """Deprecated: use `cli drive status`."""
20
+ status_cmd()
21
+
22
+
23
+ @repository_app.command("sync")
24
+ def repository_sync_alias(
25
+ path: str | None = typer.Argument(None),
26
+ ) -> None:
27
+ """Deprecated: use `cli drive ingest`."""
28
+ ingest_cmd(path)
29
+
30
+
31
+ @repository_app.command("list")
32
+ def repository_list_alias(
33
+ path: str | None = typer.Argument(None),
34
+ ) -> None:
35
+ """Deprecated: use `cli drive list`."""
36
+ list_cmd(path)
37
+
38
+
39
+ @repository_app.command("delete")
40
+ def repository_delete_alias(
41
+ path: str = typer.Argument(...),
42
+ tag: str = typer.Argument(...),
43
+ yes: bool = typer.Option(False, "--yes", "-y"),
44
+ ) -> None:
45
+ """Deprecated: use `cli drive delete`."""
46
+ delete_cmd(path, tag, yes=yes)
47
+
48
+
49
+ backup_app.add_typer(repository_app, name="repository")
@@ -0,0 +1,21 @@
1
+ """Hidden legacy alias: `cli bookmarks` → `cli chrome bookmarks`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from cli.commands.chrome import bookmarks_deploy_to_chrome, bookmarks_ingest_from_chrome
8
+
9
+ bookmarks_app = typer.Typer(help="(deprecated) use cli chrome bookmarks", hidden=True)
10
+
11
+
12
+ @bookmarks_app.command("export")
13
+ def legacy_export() -> None:
14
+ """Deprecated: remote-centric Chrome→local; use `cli chrome bookmarks ingest`."""
15
+ bookmarks_ingest_from_chrome()
16
+
17
+
18
+ @bookmarks_app.command("import")
19
+ def legacy_import() -> None:
20
+ """Deprecated: remote-centric local→Chrome; use `cli chrome bookmarks deploy`."""
21
+ bookmarks_deploy_to_chrome()
cli/commands/chrome.py ADDED
@@ -0,0 +1,77 @@
1
+ """Chrome browser integrations (bookmarks today; more later)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import subprocess
7
+
8
+ import typer
9
+ from rich import print as rprint
10
+
11
+ from cli.utils.config import bookmarks_file_path, chrome_downloads_dir, project_root
12
+
13
+ chrome_app = typer.Typer(help="Chrome browser — bookmarks and future integrations.", no_args_is_help=True)
14
+ bookmarks_app = typer.Typer(help="Bookmark ingest / deploy (local-centric).", no_args_is_help=True)
15
+
16
+
17
+ def _bookmarks_env() -> dict[str, str]:
18
+ env = os.environ.copy()
19
+ env["CLI_ROOT"] = str(project_root())
20
+ env["CLI_BOOKMARKS_FILE"] = str(bookmarks_file_path())
21
+ env["CLI_DOWNLOADS_DIR"] = str(chrome_downloads_dir())
22
+ return env
23
+
24
+
25
+ def _run_bookmarks_script(name: str) -> None:
26
+ script = project_root() / "scripts" / "chrome" / name
27
+ if not script.is_file():
28
+ raise typer.Exit(f"Script not found: {script}")
29
+ try:
30
+ subprocess.run([str(script)], env=_bookmarks_env(), check=True)
31
+ except subprocess.CalledProcessError as exc:
32
+ raise typer.Exit(exc.returncode) from exc
33
+
34
+
35
+ def bookmarks_ingest_from_chrome() -> None:
36
+ """Chrome → local: ingest bookmarks HTML into configured path."""
37
+ dest = bookmarks_file_path()
38
+ _run_bookmarks_script("export-bookmarks.sh")
39
+ rprint(f"[green]ingested[/green] → {dest}")
40
+
41
+
42
+ def bookmarks_deploy_to_chrome() -> None:
43
+ """Local → Chrome: deploy backup file into Chrome."""
44
+ src = bookmarks_file_path()
45
+ if not src.is_file():
46
+ raise typer.Exit(
47
+ f"Backup not found: {src}\nRun `cli chrome bookmarks ingest` first."
48
+ )
49
+ _run_bookmarks_script("import-bookmarks.sh")
50
+ rprint(f"[green]deployed[/green] to Chrome from {src}")
51
+
52
+
53
+ @bookmarks_app.command("ingest")
54
+ def bookmarks_ingest_cmd() -> None:
55
+ """Chrome → local: ingest bookmarks HTML to configured path."""
56
+ bookmarks_ingest_from_chrome()
57
+
58
+
59
+ @bookmarks_app.command("deploy")
60
+ def bookmarks_deploy_cmd() -> None:
61
+ """Local → Chrome: deploy backup into Chrome."""
62
+ bookmarks_deploy_to_chrome()
63
+
64
+
65
+ @bookmarks_app.command("import", hidden=True)
66
+ def legacy_import_cmd() -> None:
67
+ """Deprecated: remote-centric local→Chrome; use `cli chrome bookmarks deploy`."""
68
+ bookmarks_deploy_to_chrome()
69
+
70
+
71
+ @bookmarks_app.command("export", hidden=True)
72
+ def legacy_export_cmd() -> None:
73
+ """Deprecated: remote-centric Chrome→local; use `cli chrome bookmarks ingest`."""
74
+ bookmarks_ingest_from_chrome()
75
+
76
+
77
+ chrome_app.add_typer(bookmarks_app, name="bookmarks")
@@ -0,0 +1,120 @@
1
+ """Competitive programming validation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import typer
8
+ from rich import print as rprint
9
+ from rich.table import Table
10
+
11
+ from cli.services.contest_docker import RunStatus
12
+ from cli.services.contest_runner import (
13
+ ContestValidateResult,
14
+ resolve_options,
15
+ validate_contest,
16
+ )
17
+
18
+ contest_app = typer.Typer(
19
+ help="Competitive programming validation (generator, brute, fast).",
20
+ no_args_is_help=True,
21
+ )
22
+
23
+
24
+ def _status_style(status: RunStatus) -> str:
25
+ if status == RunStatus.OK:
26
+ return "green"
27
+ if status == RunStatus.TLE:
28
+ return "yellow"
29
+ return "red"
30
+
31
+
32
+ def _print_outcome_row(
33
+ table: Table,
34
+ tier: str,
35
+ role: str,
36
+ outcome,
37
+ ) -> None:
38
+ table.add_row(
39
+ tier,
40
+ role,
41
+ f"[{_status_style(outcome.status)}]{outcome.status.value}[/]",
42
+ f"{outcome.seconds:.2f}s",
43
+ )
44
+
45
+
46
+ def _print_result(result: ContestValidateResult) -> None:
47
+ if result.error and result.small is None and result.large is None:
48
+ rprint(f"[red]error:[/red] {result.error}")
49
+ rprint("[red]FAIL[/red]")
50
+ return
51
+
52
+ table = Table(title="Contest validate", show_header=True, header_style="bold")
53
+ table.add_column("Tier")
54
+ table.add_column("Role")
55
+ table.add_column("Status")
56
+ table.add_column("Time", justify="right")
57
+
58
+ if result.small:
59
+ _print_outcome_row(table, "small", "brute", result.small.brute)
60
+ _print_outcome_row(table, "small", "fast", result.small.fast)
61
+ match = result.small.outputs_match
62
+ if match is not None:
63
+ style = "green" if match else "red"
64
+ table.add_row(
65
+ "small",
66
+ "compare",
67
+ f"[{style}]{'match' if match else 'mismatch'}[/]",
68
+ "",
69
+ )
70
+
71
+ if result.large:
72
+ _print_outcome_row(table, "large", "brute", result.large.brute)
73
+ _print_outcome_row(table, "large", "fast", result.large.fast)
74
+
75
+ rprint(table)
76
+
77
+ if result.small_diff:
78
+ rprint("\n[red]small tier diff:[/red]")
79
+ rprint(result.small_diff)
80
+
81
+ for warning in result.warnings:
82
+ rprint(f"[yellow]warning:[/yellow] {warning}")
83
+
84
+ if result.error:
85
+ rprint(f"[red]error:[/red] {result.error}")
86
+
87
+ if result.passed:
88
+ rprint("[green]PASS[/green]")
89
+ else:
90
+ rprint("[red]FAIL[/red]")
91
+
92
+
93
+ @contest_app.command("validate")
94
+ def validate_cmd(
95
+ fast: Path | None = typer.Option(None, "--fast", help="Path to fast C++ solution."),
96
+ brute: Path | None = typer.Option(None, "--brute", help="Path to Python brute solution."),
97
+ generator: Path | None = typer.Option(None, "--generator", help="Path to Python input generator."),
98
+ config: Path | None = typer.Option(None, "--config", help="Optional YAML config with paths and limits."),
99
+ timeout: float | None = typer.Option(None, "--timeout", help="Per-container time limit in seconds."),
100
+ memory_mb: int | None = typer.Option(None, "--memory-mb", help="Per-container memory limit in MB."),
101
+ image: str | None = typer.Option(None, "--image", help="Docker image for solution runs."),
102
+ ) -> None:
103
+ """Validate fast C++ vs brute Python on small (compare) and large (stress) tiers."""
104
+ try:
105
+ options = resolve_options(
106
+ fast=fast,
107
+ brute=brute,
108
+ generator=generator,
109
+ config=config,
110
+ timeout=timeout,
111
+ memory_mb=memory_mb,
112
+ image=image,
113
+ )
114
+ except (ValueError, FileNotFoundError) as exc:
115
+ rprint(f"[red]error:[/red] {exc}")
116
+ raise typer.Exit(1) from exc
117
+
118
+ result = validate_contest(options)
119
+ _print_result(result)
120
+ raise typer.Exit(0 if result.passed else 1)