pantheon-opencode 1.2.0 → 1.2.1
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.
- package/README.md +101 -3
- package/bin/pantheon-init.mjs +79 -0
- package/commands/pantheon-vision.md +67 -0
- package/opencode.json +13 -9
- package/package.json +12 -5
- package/scripts/_pantheon_paths.py +0 -2
- package/scripts/doctor.mjs +4 -2
- package/scripts/install/health-check.mjs +101 -1
- package/scripts/install/model-picker.mjs +118 -0
- package/scripts/install/opencode.mjs +102 -19
- package/scripts/install/venv.mjs +25 -2
- package/scripts/install-mcp.mjs +98 -13
- package/scripts/mcp_persistence_server.py +1 -1
- package/scripts/secret-scan.mjs +88 -0
- package/scripts/uninstall.mjs +3 -3
- package/scripts/validate-routing.mjs +15 -0
- package/src/agents/hermes.md +2 -2
- package/src/agents/mnemosyne.md +1 -1
- package/src/agents/zeus.md +1 -1
- package/src/mcp/_pantheon_paths.py +0 -2
- package/src/mcp/install-mcp.mjs +6 -913
- package/src/mcp/mcp_persistence_server.py +2 -1
- package/src/mcp/pantheon_vision_server.py +495 -0
- package/src/mcp/requirements-vision.txt +3 -0
- package/src/pantheon/auto-wake.ts +91 -0
- package/src/pantheon/background-job-board.ts +568 -0
- package/src/pantheon/file-persistence.ts +95 -0
- package/src/pantheon/presets.d.mts +108 -0
- package/src/pantheon/presets.mjs +554 -0
- package/src/pantheon/vision.ts +1437 -0
- package/src/plugin.ts +69 -25
- package/src/plugins/tui/package.json +3 -3
- package/src/plugins/tui/src/index.tsx +866 -121
- package/src/routing.yml +148 -0
- package/src/skills/context-compression/SKILL.md +1 -1
- package/src/skills/loop-engineering/SKILL.md +1 -1
- package/src/skills/orchestration-workflow/SKILL.md +11 -11
- package/scripts/scrub_secrets.py +0 -281
- package/src/plugins/tui/dist/tui.d.ts +0 -8
- package/src/plugins/tui/dist/tui.d.ts.map +0 -1
- package/src/plugins/tui/dist/tui.js +0 -609
- package/src/plugins/tui/dist/tui.js.map +0 -1
- package/src/plugins/tui/dist/tui.tsx +0 -444
package/src/routing.yml
CHANGED
|
@@ -1,4 +1,152 @@
|
|
|
1
1
|
version: 1.0
|
|
2
|
+
presets:
|
|
3
|
+
go-deepseek:
|
|
4
|
+
description: "DeepSeek tiering via OpenCode Zen: V4 Pro for planners/reviewers (free on Zen), V4 Flash for implementers, V4 Flash Free for scouts. Vision turns route to MiniMax M3 (opencode-go; Qwen3.7 Plus image input fails on the Go gateway — opencode#29956). Prices verified 2026-08."
|
|
5
|
+
vision:
|
|
6
|
+
model: opencode-go/minimax-m3
|
|
7
|
+
reasoning_effort: medium
|
|
8
|
+
providers:
|
|
9
|
+
opencode:
|
|
10
|
+
baseURL: https://opencode.ai/zen/v1
|
|
11
|
+
apiKeyEnv: PANTHEON_OPENCODE_API_KEY
|
|
12
|
+
opencode-go:
|
|
13
|
+
baseURL: https://opencode.ai/zen/go/v1
|
|
14
|
+
apiKeyEnv: PANTHEON_OPENCODE_API_KEY
|
|
15
|
+
agents:
|
|
16
|
+
athena: { model: opencode/deepseek-v4-pro, reasoning_effort: high, fallback_models: [opencode/mimo-v2.5] }
|
|
17
|
+
themis: { model: opencode/deepseek-v4-pro, reasoning_effort: high, fallback_models: [opencode/mimo-v2.5] }
|
|
18
|
+
zeus: { model: opencode/deepseek-v4-flash, reasoning_effort: medium, fallback_models: [opencode/mimo-v2.5] }
|
|
19
|
+
hermes: { model: opencode/deepseek-v4-flash, reasoning_effort: medium, fallback_models: [opencode/mimo-v2.5] }
|
|
20
|
+
aphrodite: { model: opencode/deepseek-v4-flash, reasoning_effort: medium, fallback_models: [opencode/mimo-v2.5] }
|
|
21
|
+
demeter: { model: opencode/deepseek-v4-flash, reasoning_effort: medium, fallback_models: [opencode/mimo-v2.5] }
|
|
22
|
+
prometheus: { model: opencode/deepseek-v4-flash, reasoning_effort: medium, fallback_models: [opencode/mimo-v2.5] }
|
|
23
|
+
hephaestus: { model: opencode/deepseek-v4-flash, reasoning_effort: medium, fallback_models: [opencode/mimo-v2.5] }
|
|
24
|
+
apollo: { model: opencode/deepseek-v4-flash-free, reasoning_effort: low, fallback_models: [opencode/mimo-v2.5] }
|
|
25
|
+
nyx: { model: opencode/deepseek-v4-flash-free, reasoning_effort: low, fallback_models: [opencode/mimo-v2.5] }
|
|
26
|
+
gaia: { model: opencode/deepseek-v4-flash-free, reasoning_effort: low, fallback_models: [opencode/mimo-v2.5] }
|
|
27
|
+
iris: { model: opencode/deepseek-v4-flash-free, reasoning_effort: low, fallback_models: [opencode/mimo-v2.5] }
|
|
28
|
+
mnemosyne: { model: opencode/deepseek-v4-flash-free, reasoning_effort: low, fallback_models: [opencode/mimo-v2.5] }
|
|
29
|
+
talos: { model: opencode/deepseek-v4-flash-free, reasoning_effort: low, fallback_models: [opencode/mimo-v2.5] }
|
|
30
|
+
go-fast:
|
|
31
|
+
description: "All 14 agents on DeepSeek V4 Flash via OpenCode Go with reasoning_effort low everywhere. Fastest tier. Vision turns route to MiniMax M2.5 (mimo-v2.5). Prices verified 2026-08."
|
|
32
|
+
vision:
|
|
33
|
+
model: opencode-go/mimo-v2.5
|
|
34
|
+
reasoning_effort: low
|
|
35
|
+
providers:
|
|
36
|
+
opencode-go:
|
|
37
|
+
baseURL: https://opencode.ai/zen/go/v1
|
|
38
|
+
apiKeyEnv: PANTHEON_OPENCODE_API_KEY
|
|
39
|
+
agents:
|
|
40
|
+
athena: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
41
|
+
themis: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
42
|
+
zeus: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
43
|
+
hermes: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
44
|
+
aphrodite: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
45
|
+
demeter: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
46
|
+
prometheus: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
47
|
+
hephaestus: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
48
|
+
apollo: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
49
|
+
nyx: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
50
|
+
gaia: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
51
|
+
iris: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
52
|
+
mnemosyne: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
53
|
+
talos: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
54
|
+
go-claude:
|
|
55
|
+
description: "Anthropic Claude tiering: Opus 4.8 for planners/reviewers, Sonnet 5 for implementers, Haiku 4.5 for scouts. Vision turns route to Sonnet 5 (native image support). Prices verified 2026-07."
|
|
56
|
+
vision:
|
|
57
|
+
model: anthropic/claude-sonnet-5
|
|
58
|
+
reasoning_effort: medium
|
|
59
|
+
providers:
|
|
60
|
+
anthropic:
|
|
61
|
+
baseURL: https://api.anthropic.com
|
|
62
|
+
apiKeyEnv: PANTHEON_ANTHROPIC_API_KEY
|
|
63
|
+
agents:
|
|
64
|
+
athena: { model: anthropic/claude-opus-4-8, reasoning_effort: high }
|
|
65
|
+
themis: { model: anthropic/claude-opus-4-8, reasoning_effort: high }
|
|
66
|
+
zeus: { model: anthropic/claude-sonnet-5, reasoning_effort: medium }
|
|
67
|
+
hermes: { model: anthropic/claude-sonnet-5, reasoning_effort: medium }
|
|
68
|
+
aphrodite: { model: anthropic/claude-sonnet-5, reasoning_effort: medium }
|
|
69
|
+
demeter: { model: anthropic/claude-sonnet-5, reasoning_effort: medium }
|
|
70
|
+
prometheus: { model: anthropic/claude-sonnet-5, reasoning_effort: medium }
|
|
71
|
+
hephaestus: { model: anthropic/claude-sonnet-5, reasoning_effort: medium }
|
|
72
|
+
apollo: { model: anthropic/claude-haiku-4-5, reasoning_effort: low }
|
|
73
|
+
nyx: { model: anthropic/claude-haiku-4-5, reasoning_effort: low }
|
|
74
|
+
gaia: { model: anthropic/claude-haiku-4-5, reasoning_effort: low }
|
|
75
|
+
iris: { model: anthropic/claude-haiku-4-5, reasoning_effort: low }
|
|
76
|
+
mnemosyne: { model: anthropic/claude-haiku-4-5, reasoning_effort: low }
|
|
77
|
+
talos: { model: anthropic/claude-haiku-4-5, reasoning_effort: low }
|
|
78
|
+
go-openai:
|
|
79
|
+
description: "OpenAI GPT-5.6 family tiering: Sol for planners/reviewers, Terra for implementers, Luna Fast for scouts. Vision turns route to GPT-5.6 Sol. Prices verified 2026-07."
|
|
80
|
+
vision:
|
|
81
|
+
model: openai/gpt-5.6-sol
|
|
82
|
+
reasoning_effort: high
|
|
83
|
+
providers:
|
|
84
|
+
openai:
|
|
85
|
+
baseURL: https://api.openai.com/v1
|
|
86
|
+
apiKeyEnv: PANTHEON_OPENAI_API_KEY
|
|
87
|
+
agents:
|
|
88
|
+
athena: { model: openai/gpt-5.6-sol, reasoning_effort: high }
|
|
89
|
+
themis: { model: openai/gpt-5.6-sol, reasoning_effort: high }
|
|
90
|
+
zeus: { model: openai/gpt-5.6-terra, reasoning_effort: medium }
|
|
91
|
+
hermes: { model: openai/gpt-5.6-terra, reasoning_effort: medium }
|
|
92
|
+
aphrodite: { model: openai/gpt-5.6-terra, reasoning_effort: medium }
|
|
93
|
+
demeter: { model: openai/gpt-5.6-terra, reasoning_effort: medium }
|
|
94
|
+
prometheus: { model: openai/gpt-5.6-terra, reasoning_effort: medium }
|
|
95
|
+
hephaestus: { model: openai/gpt-5.6-terra, reasoning_effort: medium }
|
|
96
|
+
apollo: { model: openai/gpt-5.6-luna-fast, reasoning_effort: low }
|
|
97
|
+
nyx: { model: openai/gpt-5.6-luna-fast, reasoning_effort: low }
|
|
98
|
+
gaia: { model: openai/gpt-5.6-luna-fast, reasoning_effort: low }
|
|
99
|
+
iris: { model: openai/gpt-5.6-luna-fast, reasoning_effort: low }
|
|
100
|
+
mnemosyne: { model: openai/gpt-5.6-luna-fast, reasoning_effort: low }
|
|
101
|
+
talos: { model: openai/gpt-5.6-luna-fast, reasoning_effort: low }
|
|
102
|
+
go-premium:
|
|
103
|
+
description: "Pantheon premium lineup on OpenCode Go (subscription): GLM-5.1 orchestrator, DeepSeek V4 Pro planner, Qwen3.7 Max reviewer, Kimi K2.6 designer, MiniMax M2.7 implementers, DeepSeek V4 Flash scouts. Vision turns route to MiniMax M3 (multimodal; Qwen3.7 Plus image input fails on the Go gateway — opencode#29956). Ported from Oh-My-OpenAgent 'opencode-go' preset. Requires OpenCode Go subscription. Prices verified 2026-08."
|
|
104
|
+
vision:
|
|
105
|
+
model: opencode-go/minimax-m3
|
|
106
|
+
reasoning_effort: medium
|
|
107
|
+
providers:
|
|
108
|
+
opencode-go:
|
|
109
|
+
baseURL: https://opencode.ai/zen/go/v1
|
|
110
|
+
apiKeyEnv: PANTHEON_OPENCODE_API_KEY
|
|
111
|
+
agents:
|
|
112
|
+
zeus: { model: opencode-go/glm-5.1, reasoning_effort: medium }
|
|
113
|
+
athena: { model: opencode-go/deepseek-v4-pro, reasoning_effort: high }
|
|
114
|
+
themis: { model: opencode-go/qwen3.7-max, reasoning_effort: high }
|
|
115
|
+
hermes: { model: opencode-go/minimax-m2.7, reasoning_effort: medium }
|
|
116
|
+
aphrodite: { model: opencode-go/kimi-k2.6, reasoning_effort: medium }
|
|
117
|
+
demeter: { model: opencode-go/qwen3.7-plus, reasoning_effort: medium }
|
|
118
|
+
prometheus: { model: opencode-go/glm-5.2, reasoning_effort: medium }
|
|
119
|
+
hephaestus: { model: opencode-go/minimax-m2.7, reasoning_effort: medium }
|
|
120
|
+
apollo: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
121
|
+
nyx: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
122
|
+
gaia: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
123
|
+
iris: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
124
|
+
mnemosyne: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
125
|
+
talos: { model: opencode-go/deepseek-v4-flash, reasoning_effort: low }
|
|
126
|
+
go-free:
|
|
127
|
+
description: "Pantheon free lineup on OpenCode Zen free tier, $0: Big Pickle orchestrator, Nemotron 3 Ultra Free planner, North Mini Code scouts. Vision turns route to MiniMax M2.5 Free (mimo-v2.5-free, multimodal on Zen free). Ported from Oh-My-OpenAgent 'zen-free' preset. CAUTION: nemotron-3-ultra-free has known intermittent failures (opencode#38028) — switch to go-fast if flaky. Prices verified 2026-08."
|
|
128
|
+
vision:
|
|
129
|
+
model: opencode/mimo-v2.5-free
|
|
130
|
+
reasoning_effort: low
|
|
131
|
+
providers:
|
|
132
|
+
opencode:
|
|
133
|
+
baseURL: https://opencode.ai/zen/v1
|
|
134
|
+
apiKeyEnv: PANTHEON_OPENCODE_API_KEY
|
|
135
|
+
agents:
|
|
136
|
+
zeus: { model: opencode/big-pickle, reasoning_effort: medium }
|
|
137
|
+
athena: { model: opencode/nemotron-3-ultra-free, reasoning_effort: high }
|
|
138
|
+
themis: { model: opencode/deepseek-v4-flash-free, reasoning_effort: high }
|
|
139
|
+
hermes: { model: opencode/deepseek-v4-flash-free, reasoning_effort: medium }
|
|
140
|
+
aphrodite: { model: opencode/deepseek-v4-flash-free, reasoning_effort: medium }
|
|
141
|
+
demeter: { model: opencode/deepseek-v4-flash-free, reasoning_effort: medium }
|
|
142
|
+
prometheus: { model: opencode/deepseek-v4-flash-free, reasoning_effort: medium }
|
|
143
|
+
hephaestus: { model: opencode/deepseek-v4-flash-free, reasoning_effort: medium }
|
|
144
|
+
apollo: { model: opencode/north-mini-code-free, reasoning_effort: low }
|
|
145
|
+
nyx: { model: opencode/north-mini-code-free, reasoning_effort: low }
|
|
146
|
+
gaia: { model: opencode/north-mini-code-free, reasoning_effort: low }
|
|
147
|
+
iris: { model: opencode/north-mini-code-free, reasoning_effort: low }
|
|
148
|
+
mnemosyne: { model: opencode/north-mini-code-free, reasoning_effort: low }
|
|
149
|
+
talos: { model: opencode/north-mini-code-free, reasoning_effort: low }
|
|
2
150
|
agents:
|
|
3
151
|
zeus:
|
|
4
152
|
role: Central orchestrator
|
|
@@ -165,7 +165,7 @@ Score = 0.9×0.30 + 1.0×0.25 + 0.5×0.20 + 0.0×0.15 + 0.9×0.10
|
|
|
165
165
|
|
|
166
166
|
## 4. Semantic Summarization
|
|
167
167
|
|
|
168
|
-
Zeus generates contextual summaries for CRITICAL and HIGH entries using the semantic-summarize prompt template.
|
|
168
|
+
Zeus generates contextual summaries for CRITICAL and HIGH entries using the semantic-summarize prompt template.
|
|
169
169
|
Each summary costs ~50 input tokens per entry (uses the same model as Zeus). MEDIUM/LOW entries get mechanical 1-line compression without summarization.
|
|
170
170
|
|
|
171
171
|
### Template: CRITICAL (3 lines)
|
|
@@ -21,7 +21,7 @@ Controlled iterative refinement for complex problems. Each loop tightens the sol
|
|
|
21
21
|
Loop 1: Baseline → Implement simplest working version
|
|
22
22
|
↓ review
|
|
23
23
|
Loop 2: Improve → Optimize for <metric>
|
|
24
|
-
↓ review
|
|
24
|
+
↓ review
|
|
25
25
|
Loop 3: Polish → Edge cases, error handling, DX
|
|
26
26
|
↓ review
|
|
27
27
|
Done
|
|
@@ -14,9 +14,9 @@ Practical walkthrough for using the multi-agent system end-to-end. This is your
|
|
|
14
14
|
|
|
15
15
|
## Your First Feature (Real Example)
|
|
16
16
|
|
|
17
|
-
**Scenario:** You want to add JWT authentication to your app.
|
|
18
|
-
**Time:** ~6-8 hours total (spread across 1-2 days)
|
|
19
|
-
**Team:** All agents working together
|
|
17
|
+
**Scenario:** You want to add JWT authentication to your app.
|
|
18
|
+
**Time:** ~6-8 hours total (spread across 1-2 days)
|
|
19
|
+
**Team:** All agents working together
|
|
20
20
|
**Result:** Complete feature with 95% coverage, security audit passed, ready to deploy
|
|
21
21
|
|
|
22
22
|
---
|
|
@@ -287,14 +287,14 @@ Result: 10x faster queries (~2 hours)
|
|
|
287
287
|
|
|
288
288
|
## Pro Tips
|
|
289
289
|
|
|
290
|
-
🎯 **Plan First** — Saves 2-3x rework time
|
|
291
|
-
🎯 **Use Pause Points** — You control when to proceed
|
|
292
|
-
🎯 **Trust Themis** — Code review catches issues early
|
|
293
|
-
🎯 **Commit Atomically** — One phase = one commit
|
|
294
|
-
🎯 **Test First** — RED tests before code
|
|
295
|
-
🎯 **Coverage Matters** — <80% = blocked, no exceptions
|
|
296
|
-
🎯 **Security First** — Themis enforces OWASP compliance
|
|
297
|
-
🎯 **Background Dispatch** — Use OpenCode v1.16.2+ background agents to run independent phases in parallel without polling
|
|
290
|
+
🎯 **Plan First** — Saves 2-3x rework time
|
|
291
|
+
🎯 **Use Pause Points** — You control when to proceed
|
|
292
|
+
🎯 **Trust Themis** — Code review catches issues early
|
|
293
|
+
🎯 **Commit Atomically** — One phase = one commit
|
|
294
|
+
🎯 **Test First** — RED tests before code
|
|
295
|
+
🎯 **Coverage Matters** — <80% = blocked, no exceptions
|
|
296
|
+
🎯 **Security First** — Themis enforces OWASP compliance
|
|
297
|
+
🎯 **Background Dispatch** — Use OpenCode v1.16.2+ background agents to run independent phases in parallel without polling
|
|
298
298
|
|
|
299
299
|
---
|
|
300
300
|
|
package/scripts/scrub_secrets.py
DELETED
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
scrub-secrets.py — Credential scrubber for Memory Bank promotions.
|
|
4
|
-
|
|
5
|
-
Scrubs credentials, tokens, and secrets from text before promoting it
|
|
6
|
-
to permanent memory bank files. Uses Python 3.9+ stdlib only.
|
|
7
|
-
|
|
8
|
-
Usage:
|
|
9
|
-
python3 scripts/scrub-secrets.py --file path/to/file
|
|
10
|
-
# scrub in-place (with .bak backup)
|
|
11
|
-
python3 scripts/scrub-secrets.py --stdin
|
|
12
|
-
# read stdin, write scrubbed to stdout
|
|
13
|
-
python3 scripts/scrub-secrets.py --audit path/to/file
|
|
14
|
-
# only report, don't modify
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
from __future__ import annotations
|
|
18
|
-
|
|
19
|
-
import argparse
|
|
20
|
-
import os
|
|
21
|
-
import re
|
|
22
|
-
import sys
|
|
23
|
-
from re import Match
|
|
24
|
-
|
|
25
|
-
# ---------------------------------------------------------------------------
|
|
26
|
-
# Pattern definitions — order matters (private keys first to catch multi-line)
|
|
27
|
-
# ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def _make_redactor(pattern: str, replacement: str) -> tuple[re.Pattern, str]:
|
|
31
|
-
"""Compile a pattern with DOTALL for multi-line."""
|
|
32
|
-
return (re.compile(pattern, re.DOTALL), replacement)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
REDACTORS: list[tuple[re.Pattern, str]] = [
|
|
36
|
-
# SSH private keys (multi-line, before generic private key)
|
|
37
|
-
_make_redactor(
|
|
38
|
-
r"-----BEGIN\s+OPENSSH\s+PRIVATE\s+KEY-----"
|
|
39
|
-
r".*?-----END\s+OPENSSH\s+PRIVATE\s+KEY-----",
|
|
40
|
-
"[SSH PRIVATE KEY REDACTED]",
|
|
41
|
-
),
|
|
42
|
-
# Private keys (multi-line, must run before Bearer/JWT)
|
|
43
|
-
_make_redactor(
|
|
44
|
-
r"-----BEGIN\s+.*?PRIVATE\s+KEY-----"
|
|
45
|
-
r".*?-----END\s+.*?PRIVATE\s+KEY-----",
|
|
46
|
-
"[PRIVATE KEY REDACTED]",
|
|
47
|
-
),
|
|
48
|
-
# Certificates (multi-line)
|
|
49
|
-
_make_redactor(
|
|
50
|
-
r"-----BEGIN\s+CERTIFICATE-----"
|
|
51
|
-
r".*?-----END\s+CERTIFICATE-----",
|
|
52
|
-
"[CERTIFICATE REDACTED]",
|
|
53
|
-
),
|
|
54
|
-
# Bearer tokens
|
|
55
|
-
_make_redactor(
|
|
56
|
-
r"Bearer\s+[A-Za-z0-9\-._~+/]+=*",
|
|
57
|
-
"Bearer [REDACTED]",
|
|
58
|
-
),
|
|
59
|
-
# JWTs
|
|
60
|
-
_make_redactor(
|
|
61
|
-
r"eyJ[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_]+",
|
|
62
|
-
"[JWT REDACTED]",
|
|
63
|
-
),
|
|
64
|
-
# GitHub PATs
|
|
65
|
-
_make_redactor(
|
|
66
|
-
r"ghp_[A-Za-z0-9]{36}",
|
|
67
|
-
"[GITHUB TOKEN REDACTED]",
|
|
68
|
-
),
|
|
69
|
-
# OpenAI keys
|
|
70
|
-
_make_redactor(
|
|
71
|
-
r"sk-[A-Za-z0-9\-_]{10,}",
|
|
72
|
-
"[OPENAI KEY REDACTED]",
|
|
73
|
-
),
|
|
74
|
-
# API keys (api_key, api-key, apikey)
|
|
75
|
-
_make_redactor(
|
|
76
|
-
r"(api[_-]?key|apikey)\s*[=:]\s*['\"]?[A-Za-z0-9\-_]{8,}",
|
|
77
|
-
r"\1=[REDACTED]",
|
|
78
|
-
),
|
|
79
|
-
# Passwords / tokens / secrets
|
|
80
|
-
_make_redactor(
|
|
81
|
-
r"(token|secret|password)\s*[=:]\s*['\"]?[A-Za-z0-9\-_]{8,}",
|
|
82
|
-
r"\1=[REDACTED]",
|
|
83
|
-
),
|
|
84
|
-
# AWS Access Keys
|
|
85
|
-
_make_redactor(
|
|
86
|
-
r"AKIA[0-9A-Z]{16}",
|
|
87
|
-
"[AWS KEY REDACTED]",
|
|
88
|
-
),
|
|
89
|
-
# Google API Keys
|
|
90
|
-
_make_redactor(
|
|
91
|
-
r"AIza[0-9A-Za-z\-_]{35}",
|
|
92
|
-
"[GOOGLE API KEY REDACTED]",
|
|
93
|
-
),
|
|
94
|
-
# Slack tokens
|
|
95
|
-
_make_redactor(
|
|
96
|
-
r"xox[baprs]-[0-9a-zA-Z-]{20,}",
|
|
97
|
-
"[SLACK TOKEN REDACTED]",
|
|
98
|
-
),
|
|
99
|
-
# Heroku API Keys (UUID format)
|
|
100
|
-
_make_redactor(
|
|
101
|
-
r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
|
|
102
|
-
"[HEROKU API KEY REDACTED]",
|
|
103
|
-
),
|
|
104
|
-
# Database connection strings (non-greedy to prevent over-matching)
|
|
105
|
-
_make_redactor(
|
|
106
|
-
r"postgresql\+?:\/\/.*?:.*?@",
|
|
107
|
-
"[POSTGRESQL CONNECTION REDACTED]",
|
|
108
|
-
),
|
|
109
|
-
_make_redactor(
|
|
110
|
-
r"mysql\+?:\/\/.*?:.*?@",
|
|
111
|
-
"[MYSQL CONNECTION REDACTED]",
|
|
112
|
-
),
|
|
113
|
-
_make_redactor(
|
|
114
|
-
r"redis\+?:\/\/.*?:.*?@",
|
|
115
|
-
"[REDIS CONNECTION REDACTED]",
|
|
116
|
-
),
|
|
117
|
-
# .env export statements
|
|
118
|
-
_make_redactor(
|
|
119
|
-
r"export\s+\w+=['\"]?\S+",
|
|
120
|
-
"[ENV EXPORT REDACTED]",
|
|
121
|
-
),
|
|
122
|
-
]
|
|
123
|
-
|
|
124
|
-
# Classification dispatch: prefix → label
|
|
125
|
-
_CLASSIFIERS: list[tuple[str, str]] = [
|
|
126
|
-
("-----BEGIN OPENSSH", "ssh_private_key"),
|
|
127
|
-
("-----BEGIN CERTIFICATE", "certificate"),
|
|
128
|
-
("-----BEGIN", "private_key"),
|
|
129
|
-
("Bearer ", "bearer_token"),
|
|
130
|
-
("eyJ", "jwt"),
|
|
131
|
-
("ghp_", "github_pat"),
|
|
132
|
-
("sk-", "openai_key"),
|
|
133
|
-
("xox", "slack_token"),
|
|
134
|
-
("AKIA", "aws_key"),
|
|
135
|
-
("AIza", "google_api_key"),
|
|
136
|
-
("postgresql://", "postgresql_conn"),
|
|
137
|
-
("mysql://", "mysql_conn"),
|
|
138
|
-
("redis://", "redis_conn"),
|
|
139
|
-
("export ", "env_export"),
|
|
140
|
-
]
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
def _classify_match(matched: str) -> str:
|
|
144
|
-
"""Return a human-readable label for a matched secret."""
|
|
145
|
-
for prefix, label in _CLASSIFIERS:
|
|
146
|
-
if matched.startswith(prefix):
|
|
147
|
-
return label
|
|
148
|
-
if re.match(r"api[_-]?key|apikey", matched, re.IGNORECASE):
|
|
149
|
-
return "api_key"
|
|
150
|
-
if re.match(r"(token|secret|password)\s*=", matched, re.IGNORECASE):
|
|
151
|
-
return "credential"
|
|
152
|
-
if re.match(
|
|
153
|
-
r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
|
|
154
|
-
matched,
|
|
155
|
-
):
|
|
156
|
-
return "heroku_uuid"
|
|
157
|
-
return "unknown_secret"
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def scrub(content: str) -> tuple[str, list[dict]]:
|
|
161
|
-
"""Scrub secrets from *content*.
|
|
162
|
-
|
|
163
|
-
Returns (scrubbed_content, redactions) where each redaction dict has:
|
|
164
|
-
type — human-readable label for the matched secret
|
|
165
|
-
position — (start, end) tuple of character offsets in original
|
|
166
|
-
replacement — what the secret was replaced with
|
|
167
|
-
|
|
168
|
-
Each redactor runs independently so every match in the input is
|
|
169
|
-
captured exactly once. Overlapping matches across *different* patterns
|
|
170
|
-
are possible but extremely unlikely given the pattern design.
|
|
171
|
-
"""
|
|
172
|
-
redactions: list[dict] = []
|
|
173
|
-
scrubbed = content
|
|
174
|
-
|
|
175
|
-
for pattern, replacement in REDACTORS:
|
|
176
|
-
# Walk matches in reverse offset order so replacements don't shift
|
|
177
|
-
# positions of yet-unreplaced matches.
|
|
178
|
-
matches: list[Match] = list(pattern.finditer(scrubbed))
|
|
179
|
-
for m in reversed(matches):
|
|
180
|
-
start, end = m.start(), m.end()
|
|
181
|
-
matched_text = m.group(0)
|
|
182
|
-
resolved = m.expand(replacement) if "\\" in replacement else replacement
|
|
183
|
-
|
|
184
|
-
redactions.append(
|
|
185
|
-
{
|
|
186
|
-
"type": _classify_match(matched_text),
|
|
187
|
-
"position": (start, end),
|
|
188
|
-
"replacement": resolved,
|
|
189
|
-
}
|
|
190
|
-
)
|
|
191
|
-
scrubbed = scrubbed[:start] + resolved + scrubbed[end:]
|
|
192
|
-
|
|
193
|
-
# Restore original order (positional, ascending)
|
|
194
|
-
redactions.reverse()
|
|
195
|
-
return scrubbed, redactions
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
# ---------------------------------------------------------------------------
|
|
199
|
-
# CLI
|
|
200
|
-
# ---------------------------------------------------------------------------
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
def _audit(path: str) -> None:
|
|
204
|
-
"""Print what would be redacted without modifying the file."""
|
|
205
|
-
with open(path, encoding="utf-8") as fh:
|
|
206
|
-
content = fh.read()
|
|
207
|
-
_, redactions = scrub(content)
|
|
208
|
-
if not redactions:
|
|
209
|
-
print(f"[OK] {path}: no secrets found")
|
|
210
|
-
return
|
|
211
|
-
print(f"[SCAN] {path}: {len(redactions)} potential secret(s) found:")
|
|
212
|
-
for r in redactions:
|
|
213
|
-
print(f" [{r['type']}] at {r['position']} -> {r['replacement']}")
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
def _scrub_file(path: str) -> None:
|
|
217
|
-
"""Scrub file in-place, creating a .bak backup."""
|
|
218
|
-
with open(path, encoding="utf-8") as fh:
|
|
219
|
-
content = fh.read()
|
|
220
|
-
scrubbed, redactions = scrub(content)
|
|
221
|
-
if not redactions:
|
|
222
|
-
print(f"[OK] {path}: no secrets found, file unchanged")
|
|
223
|
-
return
|
|
224
|
-
# Write backup
|
|
225
|
-
bak = path + ".bak"
|
|
226
|
-
if not os.path.exists(bak):
|
|
227
|
-
os.rename(path, bak)
|
|
228
|
-
else:
|
|
229
|
-
print(f"[WARN] {bak} already exists, skipping backup")
|
|
230
|
-
with open(path, "w", encoding="utf-8") as fh:
|
|
231
|
-
fh.write(scrubbed)
|
|
232
|
-
print(
|
|
233
|
-
f"[SECURE] {path}: {len(redactions)} secret(s) redacted "
|
|
234
|
-
f"({' + '.join(r['type'] for r in redactions)})"
|
|
235
|
-
)
|
|
236
|
-
print(f" Backup: {bak}")
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
def _scrub_stdin() -> None:
|
|
240
|
-
"""Read stdin, write scrubbed output to stdout."""
|
|
241
|
-
content = sys.stdin.read()
|
|
242
|
-
scrubbed, redactions = scrub(content)
|
|
243
|
-
for r in redactions:
|
|
244
|
-
print(f" [REDACTED {r['type']}]", file=sys.stderr)
|
|
245
|
-
sys.stdout.write(scrubbed)
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
def main() -> None:
|
|
249
|
-
parser = argparse.ArgumentParser(
|
|
250
|
-
description="Scrub secrets from text before promoting to "
|
|
251
|
-
"permanent memory bank files.",
|
|
252
|
-
)
|
|
253
|
-
group = parser.add_mutually_exclusive_group(required=True)
|
|
254
|
-
group.add_argument(
|
|
255
|
-
"--file",
|
|
256
|
-
type=str,
|
|
257
|
-
help="Scrub file in-place (with .bak backup)",
|
|
258
|
-
)
|
|
259
|
-
group.add_argument(
|
|
260
|
-
"--stdin",
|
|
261
|
-
action="store_true",
|
|
262
|
-
help="Read stdin, write scrubbed to stdout",
|
|
263
|
-
)
|
|
264
|
-
group.add_argument(
|
|
265
|
-
"--audit",
|
|
266
|
-
type=str,
|
|
267
|
-
help="Only report what would be redacted",
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
args = parser.parse_args()
|
|
271
|
-
|
|
272
|
-
if args.file:
|
|
273
|
-
_scrub_file(args.file)
|
|
274
|
-
elif args.stdin:
|
|
275
|
-
_scrub_stdin()
|
|
276
|
-
elif args.audit:
|
|
277
|
-
_audit(args.audit)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
if __name__ == "__main__":
|
|
281
|
-
main()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tui.d.ts","names":[],"sources":["../src/index.tsx"],"mappings":";;cAsbM,QAAQ;EAAoB"}
|