pywrit 0.1.6__tar.gz → 0.1.7__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pywrit
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Writ — the gate before the write. Allow/deny gate with a tamper-evident audit log
5
5
  Project-URL: Homepage, https://withwrit.com
6
6
  Project-URL: Documentation, https://docs.withwrit.com
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
  # Distribution name: pywrit (approved by Chad 2026-09-27).
7
7
  # The bare name "writ" is taken on PyPI.
8
8
  name = "pywrit"
9
- version = "0.1.6"
9
+ version = "0.1.7"
10
10
  description = "Writ — the gate before the write. Allow/deny gate with a tamper-evident audit log"
11
11
  readme = "README.md"
12
12
  requires-python = ">=3.9"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pywrit
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Writ — the gate before the write. Allow/deny gate with a tamper-evident audit log
5
5
  Project-URL: Homepage, https://withwrit.com
6
6
  Project-URL: Documentation, https://docs.withwrit.com
@@ -11035,6 +11035,15 @@ def scan_cmd(args):
11035
11035
  mpath = _pathlib.Path(args.manifest_out)
11036
11036
  mpath.write_text(_json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
11037
11037
  print(" manifest written: %s" % mpath)
11038
+ if getattr(args, "push_manifest", False):
11039
+ if not args.key:
11040
+ print("error: --push-manifest needs --key", file=sys.stderr)
11041
+ return 2
11042
+ code, text = request("POST", "/v1/manifests", manifest, key=args.key)
11043
+ if code != 0:
11044
+ print(text, file=sys.stderr)
11045
+ return code
11046
+ print(" manifest submitted: %s" % text)
11038
11047
 
11039
11048
  if getattr(args, "push_policy", False):
11040
11049
  if not args.key:
@@ -11242,6 +11251,13 @@ def _require_sponsor_token(token):
11242
11251
  return True
11243
11252
 
11244
11253
 
11254
+ def _load_json_file(path):
11255
+ p = _pathlib.Path(path)
11256
+ if not p.exists():
11257
+ raise FileNotFoundError("file not found: %s" % path)
11258
+ return json.loads(p.read_text(encoding="utf-8"))
11259
+
11260
+
11245
11261
  # ---------------------------------------------------------------------------
11246
11262
  # Agent Action Report
11247
11263
  # ---------------------------------------------------------------------------
@@ -11640,6 +11656,8 @@ def main():
11640
11656
  help="API key (for --push-policy / --manifest-out)")
11641
11657
  scan.add_argument("--manifest-out", default="",
11642
11658
  help="Write a signed CI attestation manifest to PATH")
11659
+ scan.add_argument("--push-manifest", action="store_true",
11660
+ help="After writing --manifest-out, POST the signed manifest to the gate (needs --key)")
11643
11661
  scan.add_argument("--manifest-key", default=os.environ.get("WRIT_MANIFEST_KEY", ""),
11644
11662
  help="HMAC key for signing the manifest (default: WRIT_MANIFEST_KEY env)")
11645
11663
  scan.add_argument("--commit-sha", default="",
@@ -11662,6 +11680,44 @@ def main():
11662
11680
  manifest_key = sub.add_parser("manifest-key", help="Fetch the tenant's CI manifest HMAC key")
11663
11681
  manifest_key.add_argument("--key", default=config.api_key())
11664
11682
 
11683
+ manifests = sub.add_parser("manifests", help="List, fetch, or submit CI attestation manifests")
11684
+ manifests_sub = manifests.add_subparsers(dest="manifests_cmd", required=True)
11685
+ ms_list = manifests_sub.add_parser("list", help="List repos with their latest manifest summary")
11686
+ ms_list.add_argument("--key", default=config.api_key())
11687
+ ms_get = manifests_sub.add_parser("get", help="Fetch the full manifest + drift for a repo")
11688
+ ms_get.add_argument("repo_id")
11689
+ ms_get.add_argument("--key", default=config.api_key())
11690
+ ms_push = manifests_sub.add_parser("push", help="Submit a signed manifest produced by `writ scan --manifest-out`")
11691
+ ms_push.add_argument("file")
11692
+ ms_push.add_argument("--key", default=config.api_key())
11693
+
11694
+ github = sub.add_parser("github", help="Manage the Writ GitHub App integration")
11695
+ github_sub = github.add_subparsers(dest="github_cmd", required=True)
11696
+ gh_inst = github_sub.add_parser("installations", help="List connected installations")
11697
+ gh_inst.add_argument("--key", default=config.api_key())
11698
+ gh_repos = github_sub.add_parser("repos", help="List repos and latest scan summaries")
11699
+ gh_repos.add_argument("--key", default=config.api_key())
11700
+ gh_repo = github_sub.add_parser("repo", help="Get a repo's latest findings")
11701
+ gh_repo.add_argument("repo_id")
11702
+ gh_repo.add_argument("--key", default=config.api_key())
11703
+ gh_thresh = github_sub.add_parser("threshold", help="Set/clear the per-installation check-run fail threshold")
11704
+ gh_thresh.add_argument("installation_id")
11705
+ gh_thresh.add_argument("threshold", nargs="?", default=None,
11706
+ help="Ungated count that flips the check run to failure; omit to clear")
11707
+ gh_thresh.add_argument("--key", default=config.api_key())
11708
+ gh_pc = github_sub.add_parser("pr-comments", help="Enable or disable PR comments for an installation")
11709
+ gh_pc.add_argument("installation_id")
11710
+ gh_pc.add_argument("enabled", choices=["on", "off"])
11711
+ gh_pc.add_argument("--key", default=config.api_key())
11712
+ gh_rescan = github_sub.add_parser("rescan", help="Rescan a repository now")
11713
+ gh_rescan.add_argument("repo_id")
11714
+ gh_rescan.add_argument("--totp-code", default="", help="TOTP step-up code")
11715
+ gh_rescan.add_argument("--key", default=config.api_key())
11716
+ gh_disc = github_sub.add_parser("disconnect", help="Disconnect a GitHub App installation")
11717
+ gh_disc.add_argument("installation_id")
11718
+ gh_disc.add_argument("--totp-code", default="", help="TOTP step-up code")
11719
+ gh_disc.add_argument("--key", default=config.api_key())
11720
+
11665
11721
  args = parser.parse_args()
11666
11722
  if args.cmd == "key":
11667
11723
  payload = {"email": args.email}
@@ -11707,7 +11763,8 @@ def main():
11707
11763
  return 0
11708
11764
 
11709
11765
  _KEY_COMMANDS = {"me", "policy", "check", "receipts", "stream",
11710
- "verify-chain", "invite", "token-verify", "manifest-key"}
11766
+ "verify-chain", "invite", "token-verify", "manifest-key",
11767
+ "manifests", "github"}
11711
11768
  if args.cmd in _KEY_COMMANDS and not _require_key(args.key):
11712
11769
  return 2
11713
11770
 
@@ -11759,6 +11816,39 @@ def main():
11759
11816
  return report_cmd(args)
11760
11817
  if args.cmd == "manifest-key":
11761
11818
  return _request_and_print("GET", "/v1/manifest-key", key=args.key)
11819
+ if args.cmd == "manifests":
11820
+ if args.manifests_cmd == "list":
11821
+ return _request_and_print("GET", "/v1/manifests", key=args.key)
11822
+ if args.manifests_cmd == "get":
11823
+ return _request_and_print("GET", "/v1/manifests/" + urllib.parse.quote(args.repo_id, safe=""), key=args.key)
11824
+ if args.manifests_cmd == "push":
11825
+ try:
11826
+ payload = _load_json_file(args.file)
11827
+ except (FileNotFoundError, ValueError) as exc:
11828
+ print("error: %s" % exc, file=sys.stderr)
11829
+ return 2
11830
+ return _request_and_print("POST", "/v1/manifests", payload, key=args.key)
11831
+ if args.cmd == "github":
11832
+ if args.github_cmd == "installations":
11833
+ return _request_and_print("GET", "/v1/github/installations", key=args.key)
11834
+ if args.github_cmd == "repos":
11835
+ return _request_and_print("GET", "/v1/github/repos", key=args.key)
11836
+ if args.github_cmd == "repo":
11837
+ return _request_and_print("GET", "/v1/github/repos/" + urllib.parse.quote(args.repo_id, safe=""), key=args.key)
11838
+ if args.github_cmd == "threshold":
11839
+ body = {"installationId": args.installation_id}
11840
+ if args.threshold is not None:
11841
+ body["threshold"] = int(args.threshold)
11842
+ return _request_and_print("POST", "/v1/github/check-fail-threshold", body, key=args.key)
11843
+ if args.github_cmd == "pr-comments":
11844
+ return _request_and_print("POST", "/v1/github/pr-comments",
11845
+ {"installationId": args.installation_id, "enabled": args.enabled == "on"}, key=args.key)
11846
+ if args.github_cmd == "rescan":
11847
+ return _request_and_print("POST", "/v1/github/repos/" + urllib.parse.quote(args.repo_id, safe="") + "/rescan",
11848
+ {"totpCode": args.totp_code}, key=args.key)
11849
+ if args.github_cmd == "disconnect":
11850
+ return _request_and_print("POST", "/v1/github/disconnect",
11851
+ {"installationId": args.installation_id, "totpCode": args.totp_code}, key=args.key)
11762
11852
  if args.cmd in ("revoke", "reinstate", "revoked"):
11763
11853
  if not _require_sponsor_token(args.sponsor_token):
11764
11854
  return 2
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes