raidlock 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 (126) hide show
  1. raidlock/__init__.py +11 -0
  2. raidlock/__main__.py +5 -0
  3. raidlock/_python.py +58 -0
  4. raidlock/achievements.py +156 -0
  5. raidlock/approvals.py +237 -0
  6. raidlock/bench.py +286 -0
  7. raidlock/bridge/__init__.py +186 -0
  8. raidlock/budgets.py +178 -0
  9. raidlock/cli.py +2153 -0
  10. raidlock/commands_install.py +114 -0
  11. raidlock/core/__init__.py +26 -0
  12. raidlock/core/advisories.py +16 -0
  13. raidlock/core/billing.py +54 -0
  14. raidlock/core/board.py +2251 -0
  15. raidlock/core/brief.py +111 -0
  16. raidlock/core/clock.py +40 -0
  17. raidlock/core/cost.py +198 -0
  18. raidlock/core/db.py +608 -0
  19. raidlock/core/errors.py +52 -0
  20. raidlock/core/exe.py +6 -0
  21. raidlock/core/hints.py +32 -0
  22. raidlock/core/identity.py +285 -0
  23. raidlock/core/lead.py +758 -0
  24. raidlock/core/lead_env.py +23 -0
  25. raidlock/core/lead_hook.py +42 -0
  26. raidlock/core/leases.py +19 -0
  27. raidlock/core/lock.py +99 -0
  28. raidlock/core/messages.py +98 -0
  29. raidlock/core/migrate.py +579 -0
  30. raidlock/core/model.py +126 -0
  31. raidlock/core/models.py +109 -0
  32. raidlock/core/paths.py +61 -0
  33. raidlock/core/portable.py +53 -0
  34. raidlock/core/receipts.py +73 -0
  35. raidlock/core/registry.py +39 -0
  36. raidlock/core/repos.py +250 -0
  37. raidlock/core/safeio.py +201 -0
  38. raidlock/core/status.py +52 -0
  39. raidlock/core/store.py +107 -0
  40. raidlock/core/tokens.py +77 -0
  41. raidlock/core/transcript.py +208 -0
  42. raidlock/duel.py +371 -0
  43. raidlock/foresight.py +191 -0
  44. raidlock/frames.py +162 -0
  45. raidlock/gitguard.py +753 -0
  46. raidlock/guard.py +239 -0
  47. raidlock/hooks.py +1526 -0
  48. raidlock/infer.py +205 -0
  49. raidlock/limits.py +685 -0
  50. raidlock/lore/__init__.py +22 -0
  51. raidlock/lore/approval.py +145 -0
  52. raidlock/lore/capture.py +146 -0
  53. raidlock/lore/cli.py +109 -0
  54. raidlock/lore/distill.py +17 -0
  55. raidlock/lore/frontmatter.py +56 -0
  56. raidlock/lore/inject.py +127 -0
  57. raidlock/lore/vault.py +264 -0
  58. raidlock/mcp/__init__.py +34 -0
  59. raidlock/mcp/resources.py +72 -0
  60. raidlock/mcp/server.py +184 -0
  61. raidlock/mcp/tools.py +178 -0
  62. raidlock/notify.py +144 -0
  63. raidlock/party.py +239 -0
  64. raidlock/pr.py +575 -0
  65. raidlock/prsync.py +318 -0
  66. raidlock/recipes/README.md +34 -0
  67. raidlock/recipes/__init__.py +105 -0
  68. raidlock/recipes/docs-refresh.md +15 -0
  69. raidlock/recipes/multi-repo-refactor.md +15 -0
  70. raidlock/recipes/security-audit.md +27 -0
  71. raidlock/recipes/upgrade-deps.md +15 -0
  72. raidlock/reputation.py +194 -0
  73. raidlock/resume.py +329 -0
  74. raidlock/resurrect.py +419 -0
  75. raidlock/routing.py +419 -0
  76. raidlock/runners/__init__.py +16 -0
  77. raidlock/runners/base.py +341 -0
  78. raidlock/runners/brief.py +176 -0
  79. raidlock/runners/claude_code.py +121 -0
  80. raidlock/runners/cli.py +110 -0
  81. raidlock/runners/codex.py +114 -0
  82. raidlock/runners/gemini.py +64 -0
  83. raidlock/runners/isolation.py +391 -0
  84. raidlock/runners/policy.py +123 -0
  85. raidlock/runners/runner.py +921 -0
  86. raidlock/runners/shell.py +35 -0
  87. raidlock/runners/verifycache.py +93 -0
  88. raidlock/runners/workspace.py +376 -0
  89. raidlock/scan.py +357 -0
  90. raidlock/snapshot.py +151 -0
  91. raidlock/treasury.py +210 -0
  92. raidlock/tui.py +223 -0
  93. raidlock/web/__init__.py +5 -0
  94. raidlock/web/achievements/view.css +19 -0
  95. raidlock/web/achievements/view.js +58 -0
  96. raidlock/web/bosses/view.css +19 -0
  97. raidlock/web/bosses/view.js +46 -0
  98. raidlock/web/cli.py +90 -0
  99. raidlock/web/data.py +687 -0
  100. raidlock/web/export.py +153 -0
  101. raidlock/web/frames/view.css +25 -0
  102. raidlock/web/frames/view.js +63 -0
  103. raidlock/web/graph/__init__.py +142 -0
  104. raidlock/web/graph/view.css +33 -0
  105. raidlock/web/graph/view.js +136 -0
  106. raidlock/web/raidmap/__init__.py +205 -0
  107. raidlock/web/raidmap/view.css +15 -0
  108. raidlock/web/raidmap/view.js +47 -0
  109. raidlock/web/replay/__init__.py +258 -0
  110. raidlock/web/replay/view.css +13 -0
  111. raidlock/web/replay/view.js +127 -0
  112. raidlock/web/server.py +292 -0
  113. raidlock/web/static/app.css +851 -0
  114. raidlock/web/static/app.js +2102 -0
  115. raidlock/web/static/fonts/OFL.txt +93 -0
  116. raidlock/web/static/fonts/cinzel-600.woff2 +0 -0
  117. raidlock/web/static/fonts/cinzel-700.woff2 +0 -0
  118. raidlock/web/static/icon-32.png +0 -0
  119. raidlock/web/static/icon-raid.svg +1 -0
  120. raidlock/web/static/icon.svg +1 -0
  121. raidlock/web/static/index.html +122 -0
  122. raidlock-0.1.0.dist-info/METADATA +186 -0
  123. raidlock-0.1.0.dist-info/RECORD +126 -0
  124. raidlock-0.1.0.dist-info/WHEEL +4 -0
  125. raidlock-0.1.0.dist-info/entry_points.txt +34 -0
  126. raidlock-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,114 @@
1
+ import contextlib
2
+ import os
3
+
4
+ from .core.errors import Precondition
5
+
6
+ MARKER = "raidlock-managed: install-commands"
7
+ CONTENT = f"""---
8
+ name: raid
9
+ description: Run a raidlock board command (up, who, claimable, show <id>, claim <id>)
10
+ argument-hint: up | who | claimable | show <id> | claim <id>
11
+ allowed-tools: Bash(raidlock *)
12
+ disable-model-invocation: true
13
+ {MARKER}
14
+ ---
15
+
16
+ !`raidlock $ARGUMENTS`
17
+
18
+ Relay the output above in one or two lines, keeping its `Next:` step. Run nothing else.
19
+ """
20
+
21
+
22
+ def config_dir(env=None):
23
+ env = os.environ if env is None else env
24
+ return env.get("CLAUDE_CONFIG_DIR") or os.path.join(os.path.expanduser("~"), ".claude")
25
+
26
+
27
+ def commands_dir(env=None):
28
+ return os.path.join(config_dir(env), "skills", "raid")
29
+
30
+
31
+ def target(env=None):
32
+ return os.path.join(commands_dir(env), "SKILL.md")
33
+
34
+
35
+ def legacy_target(env=None):
36
+ return os.path.join(config_dir(env), "commands", "raid.md")
37
+
38
+
39
+ def _read(path):
40
+ try:
41
+ with open(path, encoding="utf-8") as f:
42
+ return f.read()
43
+ except FileNotFoundError:
44
+ return None
45
+
46
+
47
+ def _ours(text):
48
+ head = text.split("\n---", 1)[0] if text.startswith("---") else ""
49
+ return MARKER in head.splitlines()
50
+
51
+
52
+ def _drop_legacy(env):
53
+ path = legacy_target(env)
54
+ current = _read(path)
55
+ if current is not None and _ours(current):
56
+ os.remove(path)
57
+
58
+
59
+ def install(env=None):
60
+ _drop_legacy(env)
61
+ path = target(env)
62
+ current = _read(path)
63
+ if current is not None and not _ours(current):
64
+ raise Precondition(
65
+ f"{path} exists and was not written by raidlock; left untouched",
66
+ path=path,
67
+ hint="move it away, then run raidlock install-commands",
68
+ )
69
+ if current == CONTENT:
70
+ return {"action": "unchanged", "path": path}
71
+ os.makedirs(os.path.dirname(path), exist_ok=True)
72
+ tmp = f"{path}.{os.getpid()}.tmp"
73
+ with open(tmp, "w", encoding="utf-8", newline="\n") as f:
74
+ f.write(CONTENT)
75
+ os.replace(tmp, path)
76
+ return {"action": "updated" if current is not None else "installed", "path": path}
77
+
78
+
79
+ def uninstall(env=None):
80
+ _drop_legacy(env)
81
+ path = target(env)
82
+ current = _read(path)
83
+ if current is None:
84
+ return {"action": "absent", "path": path}
85
+ if not _ours(current):
86
+ raise Precondition(f"{path} was not written by raidlock; left untouched", path=path)
87
+ os.remove(path)
88
+ with contextlib.suppress(OSError):
89
+ os.rmdir(os.path.dirname(path))
90
+ return {"action": "removed", "path": path}
91
+
92
+
93
+ TEXT = {
94
+ "installed": "Installed /raid at {path}. Next: type /raid who in Claude Code",
95
+ "updated": "Updated /raid at {path}. Next: type /raid who in Claude Code",
96
+ "unchanged": "/raid is already installed at {path}.",
97
+ "removed": "Removed /raid ({path}).",
98
+ "absent": "/raid is not installed ({path}).",
99
+ }
100
+
101
+
102
+ def _setup(parser):
103
+ parser.add_argument("--uninstall", action="store_true", help="remove the /raid command this installed")
104
+
105
+
106
+ def _handler(ctx, args):
107
+ result = uninstall() if args.uninstall else install()
108
+ return result, TEXT[result["action"]].format(path=result["path"])
109
+
110
+
111
+ def register_cli(add_command):
112
+ add_command(
113
+ "install-commands", "add a personal /raid skill to Claude Code (--uninstall removes it)", _setup, _handler
114
+ )
@@ -0,0 +1,26 @@
1
+ """raidlock core: the only writer of `.board/`. CLI, hooks, runners, the web UI and the MCP server import from here."""
2
+
3
+ from .board import Board, find_board_dir
4
+ from .cost import Usage
5
+ from .errors import Denied, IdentityError, Precondition, RaidlockError, Timeout
6
+ from .identity import Caller
7
+ from .paths import canonical, contains, overlaps
8
+ from .registry import synced_folder
9
+ from .transcript import redact
10
+
11
+ __all__ = [
12
+ "Board",
13
+ "Caller",
14
+ "Usage",
15
+ "RaidlockError",
16
+ "Denied",
17
+ "Precondition",
18
+ "Timeout",
19
+ "IdentityError",
20
+ "find_board_dir",
21
+ "canonical",
22
+ "contains",
23
+ "overlaps",
24
+ "redact",
25
+ "synced_folder",
26
+ ]
@@ -0,0 +1,16 @@
1
+ """Best-effort pre-claim advice that cannot veto a claim."""
2
+
3
+ from importlib import import_module
4
+
5
+ PRE_CLAIM = (("raidlock.foresight", "pre_claim"),)
6
+
7
+
8
+ def pre_claim_warnings(board, card_id, checkout=None):
9
+ warnings = []
10
+ for module, name in PRE_CLAIM:
11
+ try:
12
+ hook = getattr(import_module(module), name)
13
+ warnings.extend(hook(board, card_id, checkout=checkout))
14
+ except Exception as exc:
15
+ warnings.append(f"Warning: {module} advice skipped: {exc}")
16
+ return warnings
@@ -0,0 +1,54 @@
1
+ import json
2
+ import os
3
+
4
+ MODES = ("api", "plan", "unknown")
5
+
6
+
7
+ def _read_json(path):
8
+ try:
9
+ with open(path, encoding="utf-8") as f:
10
+ data = json.load(f)
11
+ return data if isinstance(data, dict) else {}
12
+ except (OSError, ValueError):
13
+ return {}
14
+
15
+
16
+ def _codex(env):
17
+ if env.get("OPENAI_API_KEY") or env.get("CODEX_API_KEY"):
18
+ return "api", "env"
19
+ base = env.get("CODEX_HOME") or os.path.join(os.path.expanduser("~"), ".codex")
20
+ auth = _read_json(os.path.join(base, "auth.json"))
21
+ mode = str(auth.get("auth_mode", "")).lower()
22
+ if mode == "chatgpt" or auth.get("tokens"):
23
+ return "plan", "config"
24
+ if mode in ("apikey", "api_key") or auth.get("OPENAI_API_KEY"):
25
+ return "api", "config"
26
+ return "unknown", "unknown"
27
+
28
+
29
+ def _claude(env):
30
+ if env.get("ANTHROPIC_API_KEY") or env.get("CLAUDE_CODE_USE_BEDROCK") or env.get("CLAUDE_CODE_USE_VERTEX"):
31
+ return "api", "env"
32
+ base = env.get("CLAUDE_CONFIG_DIR")
33
+ state = os.path.join(base, ".claude.json") if base else os.path.join(os.path.expanduser("~"), ".claude.json")
34
+ if _read_json(state).get("oauthAccount"):
35
+ return "plan", "config"
36
+ return "unknown", "unknown"
37
+
38
+
39
+ def _gemini(env):
40
+ if env.get("GEMINI_API_KEY") or env.get("GOOGLE_API_KEY") or env.get("GOOGLE_GENAI_USE_VERTEXAI"):
41
+ return "api", "env"
42
+ return "unknown", "unknown"
43
+
44
+
45
+ def detect(runtime, declared=None, env=None):
46
+ """Billing mode of an agent: api (pay per token), plan (subscription, $0 marginal) or unknown.
47
+ Order: declared, RAIDLOCK_BILLING, runtime credentials (never read beyond the auth mode)."""
48
+ env = os.environ if env is None else env
49
+ for value, source in ((declared, "declared"), (env.get("RAIDLOCK_BILLING"), "env")):
50
+ if value in MODES:
51
+ return {"billing": value, "billing_source": source}
52
+ detector = {"codex-cli": _codex, "claude-code": _claude, "gemini-cli": _gemini}.get(runtime)
53
+ mode, source = detector(env) if detector else ("unknown", "unknown")
54
+ return {"billing": mode, "billing_source": source}