skillporter 2.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.
@@ -0,0 +1,16 @@
1
+ """skillporter — Universal package manager and diagnostics engine for AI-agent skills.
2
+
3
+ ONE SKILL. EVERY AGENT.
4
+
5
+ Install, validate, scan, synchronize, and manage Agent Skills across 19+ AI
6
+ coding and autonomous agent harnesses (Claude Code, OpenAI Codex, Cursor,
7
+ GitHub Copilot, Gemini CLI, Google Antigravity, Windsurf, Cline, Roo Code,
8
+ OpenCode, Zed, Replit Agent, JetBrains Junie, Hermes Agent, OpenClaw,
9
+ OpenHands, Goose, Continue, and Aider).
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ __version__ = "2.0.0"
15
+
16
+ __all__ = ["__version__"]
@@ -0,0 +1,4 @@
1
+ from skillporter.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
skillporter/atomic.py ADDED
@@ -0,0 +1,55 @@
1
+ """Crash-safe file writes.
2
+
3
+ A plain ``Path.write_text`` truncates the destination and then streams the new
4
+ content. If the process dies (OOM, Ctrl-C, power loss) between those two steps,
5
+ the user is left with a truncated or empty file — for skillporter that means a
6
+ corrupted state file, manifest or lockfile, which is precisely the
7
+ "failed operations must not leave the environment inconsistent" case.
8
+
9
+ ``atomic_write_text`` writes to a sibling temp file in the *same directory*
10
+ (same filesystem, so ``os.replace`` is atomic) and renames it into place.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import contextlib
16
+ import os
17
+ import tempfile
18
+ from pathlib import Path
19
+
20
+
21
+ def atomic_write_text(path: Path, content: str, *, encoding: str = "utf-8") -> None:
22
+ """Write *content* to *path* atomically.
23
+
24
+ The destination is either the previous content or the complete new content —
25
+ never a partial write. File permissions of an existing destination are
26
+ preserved so we do not silently widen them.
27
+ """
28
+ path = Path(path)
29
+ path.parent.mkdir(parents=True, exist_ok=True)
30
+
31
+ existing_mode: int | None = None
32
+ try:
33
+ if path.exists():
34
+ existing_mode = path.stat().st_mode & 0o777
35
+ except OSError:
36
+ existing_mode = None
37
+
38
+ fd, tmp_name = tempfile.mkstemp(
39
+ dir=str(path.parent), prefix=f".{path.name}.", suffix=".tmp"
40
+ )
41
+ tmp = Path(tmp_name)
42
+ try:
43
+ with os.fdopen(fd, "w", encoding=encoding, newline="\n") as fh:
44
+ fh.write(content)
45
+ fh.flush()
46
+ os.fsync(fh.fileno())
47
+ if existing_mode is not None:
48
+ with contextlib.suppress(OSError):
49
+ os.chmod(tmp, existing_mode)
50
+ os.replace(tmp, path)
51
+ except BaseException:
52
+ # Never leave a temp file behind, even on KeyboardInterrupt.
53
+ with contextlib.suppress(OSError):
54
+ tmp.unlink()
55
+ raise