thevoidforge 21.0.10 → 21.0.12
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/dist/.claude/commands/ai.md +69 -0
- package/dist/.claude/commands/architect.md +121 -0
- package/dist/.claude/commands/assemble.md +201 -0
- package/dist/.claude/commands/assess.md +75 -0
- package/dist/.claude/commands/blueprint.md +135 -0
- package/dist/.claude/commands/build.md +116 -0
- package/dist/.claude/commands/campaign.md +201 -0
- package/dist/.claude/commands/cultivation.md +166 -0
- package/dist/.claude/commands/current.md +128 -0
- package/dist/.claude/commands/dangerroom.md +74 -0
- package/dist/.claude/commands/debrief.md +178 -0
- package/dist/.claude/commands/deploy.md +99 -0
- package/dist/.claude/commands/devops.md +143 -0
- package/dist/.claude/commands/gauntlet.md +140 -0
- package/dist/.claude/commands/git.md +104 -0
- package/dist/.claude/commands/grow.md +146 -0
- package/dist/.claude/commands/imagine.md +126 -0
- package/dist/.claude/commands/portfolio.md +50 -0
- package/dist/.claude/commands/prd.md +113 -0
- package/dist/.claude/commands/qa.md +107 -0
- package/dist/.claude/commands/review.md +151 -0
- package/dist/.claude/commands/security.md +100 -0
- package/dist/.claude/commands/test.md +96 -0
- package/dist/.claude/commands/thumper.md +116 -0
- package/dist/.claude/commands/treasury.md +100 -0
- package/dist/.claude/commands/ux.md +118 -0
- package/dist/.claude/commands/vault.md +189 -0
- package/dist/.claude/commands/void.md +108 -0
- package/dist/CHANGELOG.md +1918 -0
- package/dist/CLAUDE.md +250 -0
- package/dist/HOLOCRON.md +856 -0
- package/dist/VERSION.md +123 -0
- package/dist/docs/NAMING_REGISTRY.md +478 -0
- package/dist/docs/methods/AI_INTELLIGENCE.md +276 -0
- package/dist/docs/methods/ASSEMBLER.md +142 -0
- package/dist/docs/methods/BACKEND_ENGINEER.md +165 -0
- package/dist/docs/methods/BUILD_JOURNAL.md +185 -0
- package/dist/docs/methods/BUILD_PROTOCOL.md +426 -0
- package/dist/docs/methods/CAMPAIGN.md +568 -0
- package/dist/docs/methods/CONTEXT_MANAGEMENT.md +189 -0
- package/dist/docs/methods/DEEP_CURRENT.md +184 -0
- package/dist/docs/methods/DEVOPS_ENGINEER.md +295 -0
- package/dist/docs/methods/FIELD_MEDIC.md +261 -0
- package/dist/docs/methods/FORGE_ARTIST.md +108 -0
- package/dist/docs/methods/FORGE_KEEPER.md +268 -0
- package/dist/docs/methods/GAUNTLET.md +344 -0
- package/dist/docs/methods/GROWTH_STRATEGIST.md +466 -0
- package/dist/docs/methods/HEARTBEAT.md +168 -0
- package/dist/docs/methods/MCP_INTEGRATION.md +139 -0
- package/dist/docs/methods/MUSTER.md +148 -0
- package/dist/docs/methods/PRD_GENERATOR.md +186 -0
- package/dist/docs/methods/PRODUCT_DESIGN_FRONTEND.md +250 -0
- package/dist/docs/methods/QA_ENGINEER.md +337 -0
- package/dist/docs/methods/RELEASE_MANAGER.md +145 -0
- package/dist/docs/methods/SECURITY_AUDITOR.md +320 -0
- package/dist/docs/methods/SUB_AGENTS.md +335 -0
- package/dist/docs/methods/SYSTEMS_ARCHITECT.md +171 -0
- package/dist/docs/methods/TESTING.md +359 -0
- package/dist/docs/methods/THUMPER.md +175 -0
- package/dist/docs/methods/TIME_VAULT.md +120 -0
- package/dist/docs/methods/TREASURY.md +184 -0
- package/dist/docs/methods/TROUBLESHOOTING.md +265 -0
- package/dist/docs/patterns/README.md +52 -0
- package/dist/docs/patterns/ad-billing-adapter.ts +537 -0
- package/dist/docs/patterns/ad-platform-adapter.ts +421 -0
- package/dist/docs/patterns/ai-classifier.ts +195 -0
- package/dist/docs/patterns/ai-eval.ts +272 -0
- package/dist/docs/patterns/ai-orchestrator.ts +341 -0
- package/dist/docs/patterns/ai-router.ts +194 -0
- package/dist/docs/patterns/ai-tool-schema.ts +237 -0
- package/dist/docs/patterns/api-route.ts +241 -0
- package/dist/docs/patterns/backtest-engine.ts +499 -0
- package/dist/docs/patterns/browser-review.ts +292 -0
- package/dist/docs/patterns/combobox.tsx +300 -0
- package/dist/docs/patterns/component.tsx +262 -0
- package/dist/docs/patterns/daemon-process.ts +338 -0
- package/dist/docs/patterns/data-pipeline.ts +297 -0
- package/dist/docs/patterns/database-migration.ts +466 -0
- package/dist/docs/patterns/e2e-test.ts +629 -0
- package/dist/docs/patterns/error-handling.ts +312 -0
- package/dist/docs/patterns/execution-safety.ts +601 -0
- package/dist/docs/patterns/financial-transaction.ts +342 -0
- package/dist/docs/patterns/funding-plan.ts +462 -0
- package/dist/docs/patterns/game-entity.ts +137 -0
- package/dist/docs/patterns/game-loop.ts +113 -0
- package/dist/docs/patterns/game-state.ts +143 -0
- package/dist/docs/patterns/job-queue.ts +225 -0
- package/dist/docs/patterns/kongo-integration.ts +164 -0
- package/dist/docs/patterns/middleware.ts +363 -0
- package/dist/docs/patterns/mobile-screen.tsx +139 -0
- package/dist/docs/patterns/mobile-service.ts +167 -0
- package/dist/docs/patterns/multi-tenant.ts +382 -0
- package/dist/docs/patterns/oauth-token-lifecycle.ts +223 -0
- package/dist/docs/patterns/outbound-rate-limiter.ts +260 -0
- package/dist/docs/patterns/prompt-template.ts +195 -0
- package/dist/docs/patterns/revenue-source-adapter.ts +311 -0
- package/dist/docs/patterns/service.ts +224 -0
- package/dist/docs/patterns/sse-endpoint.ts +118 -0
- package/dist/docs/patterns/stablecoin-adapter.ts +511 -0
- package/dist/docs/patterns/third-party-script.ts +68 -0
- package/dist/scripts/thumper/gom-jabbar.sh +241 -0
- package/dist/scripts/thumper/relay.sh +610 -0
- package/dist/scripts/thumper/scan.sh +359 -0
- package/dist/scripts/thumper/thumper.sh +190 -0
- package/dist/scripts/thumper/water-rings.sh +76 -0
- package/dist/scripts/voidforge.js +1 -1
- package/package.json +1 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# relay.sh — The Sandworm — Chani's message transport
|
|
3
|
+
# Polls Telegram for thumper beats and rides them into Claude Code
|
|
4
|
+
# Runs as a background daemon, started by thumper.sh
|
|
5
|
+
#
|
|
6
|
+
# Note: -e (errexit) intentionally omitted — a long-running daemon must not
|
|
7
|
+
# exit on transient curl failures or non-zero returns from inject_text.
|
|
8
|
+
set -uo pipefail
|
|
9
|
+
|
|
10
|
+
# Security: refuse to run as root
|
|
11
|
+
if [[ "$(id -u)" -eq 0 ]]; then
|
|
12
|
+
echo "❌ Cannot run as root. Use a normal user account." >&2
|
|
13
|
+
exit 1
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
18
|
+
CONFIG_DIR="$PROJECT_ROOT/.voidforge/thumper"
|
|
19
|
+
CONFIG_FILE="$CONFIG_DIR/sietch.env"
|
|
20
|
+
CHANNEL_FLAG="$CONFIG_DIR/.thumper.active"
|
|
21
|
+
PID_FILE="$CONFIG_DIR/.worm.pid"
|
|
22
|
+
LOG_FILE="$CONFIG_DIR/worm.log"
|
|
23
|
+
LAST_ID_FILE="$CONFIG_DIR/.last_thumper_id"
|
|
24
|
+
|
|
25
|
+
# ─── Security: Input Sanitization ──────────────────────────────
|
|
26
|
+
|
|
27
|
+
MAX_MESSAGE_LENGTH=8192
|
|
28
|
+
|
|
29
|
+
sanitize_text() {
|
|
30
|
+
# Strip ALL control chars (0x00-0x1F) and DEL (0x7F), collapse newlines to spaces
|
|
31
|
+
# This includes tab (0x09) which can trigger autocomplete in terminals
|
|
32
|
+
printf '%s' "$1" | tr -d '\000-\037\177' | tr '\n' ' '
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# ─── Helpers ───────────────────────────────────────────────────
|
|
36
|
+
|
|
37
|
+
log() {
|
|
38
|
+
echo "[$(date -u +"%Y-%m-%dT%H:%M:%SZ")] $*" >> "$LOG_FILE"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
send_telegram() {
|
|
42
|
+
local text="$1"
|
|
43
|
+
curl -s --connect-timeout 5 --max-time 10 \
|
|
44
|
+
-X POST \
|
|
45
|
+
-d chat_id="$CHAT_ID" \
|
|
46
|
+
--data-urlencode text="$text" \
|
|
47
|
+
-d parse_mode="Markdown" \
|
|
48
|
+
"https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" >/dev/null 2>&1 || \
|
|
49
|
+
curl -s --connect-timeout 5 --max-time 10 \
|
|
50
|
+
-X POST \
|
|
51
|
+
-d chat_id="$CHAT_ID" \
|
|
52
|
+
--data-urlencode text="$text" \
|
|
53
|
+
"https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" >/dev/null 2>&1 || true
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# ─── Startup Checks ───────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
if [[ ! -f "$CONFIG_FILE" ]]; then
|
|
59
|
+
echo "❌ No sietch vault found. Run /thumper setup first." >&2
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
source "$CONFIG_FILE"
|
|
64
|
+
|
|
65
|
+
if [[ "${SETUP_COMPLETE:-}" != "true" ]]; then
|
|
66
|
+
echo "❌ Setup incomplete. Run /thumper setup." >&2
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
if [[ -f "$PID_FILE" ]]; then
|
|
71
|
+
existing_pid=$(cat "$PID_FILE" 2>/dev/null || echo "")
|
|
72
|
+
if [[ -n "$existing_pid" ]] && [[ "$existing_pid" != "$$" ]] && kill -0 "$existing_pid" 2>/dev/null; then
|
|
73
|
+
echo "⚠️ Sandworm already riding (PID $existing_pid). Stop it first." >&2
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
API_BASE="https://api.telegram.org/bot${BOT_TOKEN}"
|
|
79
|
+
|
|
80
|
+
# ─── Transport Pre-flight ─────────────────────────────────────
|
|
81
|
+
case "${INJECT_METHOD:-}" in
|
|
82
|
+
TMUX_SENDKEYS)
|
|
83
|
+
if ! command -v tmux >/dev/null 2>&1; then
|
|
84
|
+
echo "❌ TMUX_SENDKEYS configured but tmux not found. Re-run /thumper setup." >&2
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
if [[ -z "${TMUX:-}" ]] && ! tmux has-session -t "${TMUX_SESSION:-0}" 2>/dev/null; then
|
|
88
|
+
echo "❌ tmux session '${TMUX_SESSION:-0}' not found. Start tmux or re-run /thumper setup." >&2
|
|
89
|
+
exit 1
|
|
90
|
+
fi
|
|
91
|
+
;;
|
|
92
|
+
PTY_INJECT)
|
|
93
|
+
if [[ ! -d "/proc" ]]; then
|
|
94
|
+
echo "❌ PTY_INJECT configured but /proc not found. Re-run /thumper setup." >&2
|
|
95
|
+
exit 1
|
|
96
|
+
fi
|
|
97
|
+
;;
|
|
98
|
+
OSASCRIPT)
|
|
99
|
+
if [[ "${OSTYPE:-}" != darwin* ]]; then
|
|
100
|
+
echo "❌ OSASCRIPT configured but not on macOS. Re-run /thumper setup." >&2
|
|
101
|
+
exit 1
|
|
102
|
+
fi
|
|
103
|
+
;;
|
|
104
|
+
esac
|
|
105
|
+
|
|
106
|
+
echo "$$" > "$PID_FILE.tmp" && mv "$PID_FILE.tmp" "$PID_FILE"
|
|
107
|
+
|
|
108
|
+
# Rotate log if over 1MB
|
|
109
|
+
if [[ -f "$LOG_FILE" ]]; then
|
|
110
|
+
log_size=$(stat -f%z "$LOG_FILE" 2>/dev/null || stat -c%s "$LOG_FILE" 2>/dev/null || echo "0")
|
|
111
|
+
if [[ "$log_size" -gt 1048576 ]]; then
|
|
112
|
+
mv "$LOG_FILE" "${LOG_FILE}.1"
|
|
113
|
+
fi
|
|
114
|
+
fi
|
|
115
|
+
touch "$LOG_FILE"
|
|
116
|
+
chmod 600 "$LOG_FILE"
|
|
117
|
+
|
|
118
|
+
# ─── Cleanup on exit ──────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
cleanup() {
|
|
121
|
+
rm -f "$PID_FILE"
|
|
122
|
+
log "🔇 Sandworm dormant (PID $$)"
|
|
123
|
+
}
|
|
124
|
+
trap cleanup EXIT SIGTERM SIGINT
|
|
125
|
+
|
|
126
|
+
# ─── Offset Management ─────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
advance_offset() {
|
|
129
|
+
LAST_ID="$1"
|
|
130
|
+
echo "$LAST_ID" > "$LAST_ID_FILE.tmp" && mv "$LAST_ID_FILE.tmp" "$LAST_ID_FILE"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
# ─── Source Gom Jabbar Protocol ────────────────────────────────
|
|
134
|
+
|
|
135
|
+
source "$SCRIPT_DIR/gom-jabbar.sh"
|
|
136
|
+
|
|
137
|
+
# ─── JSON Parsing (outputs: update_id \t msg_id \t chat_id \t text) ──
|
|
138
|
+
|
|
139
|
+
parse_updates() {
|
|
140
|
+
local json="$1"
|
|
141
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
142
|
+
echo "$json" | python3 -c "
|
|
143
|
+
import sys, json
|
|
144
|
+
try:
|
|
145
|
+
data = json.load(sys.stdin)
|
|
146
|
+
if data.get('ok') and data.get('result'):
|
|
147
|
+
for update in data['result']:
|
|
148
|
+
uid = update.get('update_id', 0)
|
|
149
|
+
msg = update.get('message', {})
|
|
150
|
+
msg_id = msg.get('message_id', 0)
|
|
151
|
+
chat = msg.get('chat', {})
|
|
152
|
+
chat_id = str(chat.get('id', ''))
|
|
153
|
+
text = msg.get('text', '')
|
|
154
|
+
if text:
|
|
155
|
+
print(f'{uid}\t{msg_id}\t{chat_id}\t{text}')
|
|
156
|
+
except Exception:
|
|
157
|
+
pass
|
|
158
|
+
" 2>/dev/null
|
|
159
|
+
elif command -v jq >/dev/null 2>&1; then
|
|
160
|
+
echo "$json" | jq -r '
|
|
161
|
+
.result[]? |
|
|
162
|
+
select(.message.text != null) |
|
|
163
|
+
[.update_id, .message.message_id, (.message.chat.id | tostring), .message.text] |
|
|
164
|
+
join("\t")
|
|
165
|
+
' 2>/dev/null
|
|
166
|
+
else
|
|
167
|
+
# Minimal grep/sed fallback — only processes the LAST update to avoid
|
|
168
|
+
# row misalignment between separate grep extractions.
|
|
169
|
+
# Install python3 or jq for reliable multi-message parsing.
|
|
170
|
+
local uid text_val
|
|
171
|
+
uid=$(echo "$json" | grep -o '"update_id":[0-9]*' | tail -1 | sed 's/"update_id"://')
|
|
172
|
+
text_val=$(echo "$json" | grep -o '"text":"[^"]*"' | tail -1 | sed 's/"text":"//;s/"$//')
|
|
173
|
+
if [[ -n "$uid" ]] && [[ -n "$text_val" ]]; then
|
|
174
|
+
printf '%s\t0\t%s\t%s\n' "$uid" "$CHAT_ID" "$text_val"
|
|
175
|
+
fi
|
|
176
|
+
fi
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
# ─── Command Center (Inline Keyboard) ─────────────────────────
|
|
180
|
+
|
|
181
|
+
send_command_menu() {
|
|
182
|
+
local keyboard='{"inline_keyboard":[
|
|
183
|
+
[{"text":"🚀 Campaign","callback_data":"menu:campaign"},{"text":"🔨 Build","callback_data":"menu:build"},{"text":"🛡️ Gauntlet","callback_data":"menu:gauntlet"}],
|
|
184
|
+
[{"text":"🦇 QA","callback_data":"menu:qa"},{"text":"🧝 UX","callback_data":"menu:ux"},{"text":"⚔️ Security","callback_data":"menu:security"}],
|
|
185
|
+
[{"text":"🏗️ Architect","callback_data":"menu:architect"},{"text":"📦 DevOps","callback_data":"menu:devops"},{"text":"🧪 Test","callback_data":"menu:test"}],
|
|
186
|
+
[{"text":"📋 Review","callback_data":"menu:review"},{"text":"🔖 Git","callback_data":"menu:git"},{"text":"🩺 Debrief","callback_data":"menu:debrief"}],
|
|
187
|
+
[{"text":"🌀 Void","callback_data":"menu:void"},{"text":"🪱 Thumper","callback_data":"menu:thumper"},{"text":"🎨 Imagine","callback_data":"menu:imagine"}]
|
|
188
|
+
]}'
|
|
189
|
+
curl -s --connect-timeout 5 --max-time 10 \
|
|
190
|
+
-X POST "${API_BASE}/sendMessage" \
|
|
191
|
+
-H "Content-Type: application/json" \
|
|
192
|
+
-d "{\"chat_id\":\"$CHAT_ID\",\"text\":\"🏜️ *VoidForge Command Center*\n\nTap a command to see its options:\",\"parse_mode\":\"Markdown\",\"reply_markup\":$keyboard}" >/dev/null 2>&1 || true
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
get_submenu() {
|
|
196
|
+
local cmd="$1"
|
|
197
|
+
case "$cmd" in
|
|
198
|
+
campaign)
|
|
199
|
+
echo '{"text":"🚀 *Campaign* — Sisko'\''s War Room\n\nBuild the entire PRD mission by mission.","keyboard":{"inline_keyboard":[
|
|
200
|
+
[{"text":"/campaign","callback_data":"run:/campaign"}],
|
|
201
|
+
[{"text":"/campaign --blitz","callback_data":"run:/campaign --blitz"}],
|
|
202
|
+
[{"text":"/campaign --blitz --fast","callback_data":"run:/campaign --blitz --fast"}],
|
|
203
|
+
[{"text":"/campaign --resume","callback_data":"run:/campaign --resume"}],
|
|
204
|
+
[{"text":"/campaign --plan","callback_data":"run:/campaign --plan"}],
|
|
205
|
+
[{"text":"← Back","callback_data":"menu:back"}]
|
|
206
|
+
]}}';;
|
|
207
|
+
build)
|
|
208
|
+
echo '{"text":"🔨 *Build* — Execute Protocol\n\nFull 13-phase build from PRD.","keyboard":{"inline_keyboard":[
|
|
209
|
+
[{"text":"/build","callback_data":"run:/build"}],
|
|
210
|
+
[{"text":"/build --phase 6-7","callback_data":"run:/build --phase 6-7"}],
|
|
211
|
+
[{"text":"← Back","callback_data":"menu:back"}]
|
|
212
|
+
]}}';;
|
|
213
|
+
gauntlet)
|
|
214
|
+
echo '{"text":"🛡️ *Gauntlet* — Thanos'\''s Review\n\n5 rounds, 30+ agents, every domain.","keyboard":{"inline_keyboard":[
|
|
215
|
+
[{"text":"/gauntlet","callback_data":"run:/gauntlet"}],
|
|
216
|
+
[{"text":"/gauntlet --quick","callback_data":"run:/gauntlet --quick"}],
|
|
217
|
+
[{"text":"/gauntlet --ux-extra","callback_data":"run:/gauntlet --ux-extra"}],
|
|
218
|
+
[{"text":"/gauntlet --security-only","callback_data":"run:/gauntlet --security-only"}],
|
|
219
|
+
[{"text":"← Back","callback_data":"menu:back"}]
|
|
220
|
+
]}}';;
|
|
221
|
+
qa)
|
|
222
|
+
echo '{"text":"🦇 *QA* — Batman'\''s Pass\n\nEvery edge case, every boundary.","keyboard":{"inline_keyboard":[
|
|
223
|
+
[{"text":"/qa","callback_data":"run:/qa"}],
|
|
224
|
+
[{"text":"← Back","callback_data":"menu:back"}]
|
|
225
|
+
]}}';;
|
|
226
|
+
ux)
|
|
227
|
+
echo '{"text":"🧝 *UX* — Galadriel'\''s Pass\n\nUsability, a11y, enchantment.","keyboard":{"inline_keyboard":[
|
|
228
|
+
[{"text":"/ux","callback_data":"run:/ux"}],
|
|
229
|
+
[{"text":"← Back","callback_data":"menu:back"}]
|
|
230
|
+
]}}';;
|
|
231
|
+
security)
|
|
232
|
+
echo '{"text":"⚔️ *Security* — Kenobi'\''s Audit\n\nOWASP, injection, auth, secrets.","keyboard":{"inline_keyboard":[
|
|
233
|
+
[{"text":"/security","callback_data":"run:/security"}],
|
|
234
|
+
[{"text":"← Back","callback_data":"menu:back"}]
|
|
235
|
+
]}}';;
|
|
236
|
+
architect)
|
|
237
|
+
echo '{"text":"🏗️ *Architect* — Picard'\''s Review\n\nSchema, scaling, boundaries.","keyboard":{"inline_keyboard":[
|
|
238
|
+
[{"text":"/architect","callback_data":"run:/architect"}],
|
|
239
|
+
[{"text":"/architect --plan","callback_data":"run:/architect --plan"}],
|
|
240
|
+
[{"text":"← Back","callback_data":"menu:back"}]
|
|
241
|
+
]}}';;
|
|
242
|
+
devops)
|
|
243
|
+
echo '{"text":"📦 *DevOps* — Kusanagi'\''s Audit\n\nDeploy, monitor, backup.","keyboard":{"inline_keyboard":[
|
|
244
|
+
[{"text":"/devops","callback_data":"run:/devops"}],
|
|
245
|
+
[{"text":"← Back","callback_data":"menu:back"}]
|
|
246
|
+
]}}';;
|
|
247
|
+
test)
|
|
248
|
+
echo '{"text":"🧪 *Test* — Batman'\''s Test Mode\n\nCoverage, architecture, write tests.","keyboard":{"inline_keyboard":[
|
|
249
|
+
[{"text":"/test","callback_data":"run:/test"}],
|
|
250
|
+
[{"text":"← Back","callback_data":"menu:back"}]
|
|
251
|
+
]}}';;
|
|
252
|
+
review)
|
|
253
|
+
echo '{"text":"📋 *Review* — Picard'\''s Code Review\n\nPattern compliance, quality.","keyboard":{"inline_keyboard":[
|
|
254
|
+
[{"text":"/review","callback_data":"run:/review"}],
|
|
255
|
+
[{"text":"← Back","callback_data":"menu:back"}]
|
|
256
|
+
]}}';;
|
|
257
|
+
git)
|
|
258
|
+
echo '{"text":"🔖 *Git* — Coulson'\''s Release\n\nVersion, changelog, commit.","keyboard":{"inline_keyboard":[
|
|
259
|
+
[{"text":"/git","callback_data":"run:/git"}],
|
|
260
|
+
[{"text":"← Back","callback_data":"menu:back"}]
|
|
261
|
+
]}}';;
|
|
262
|
+
debrief)
|
|
263
|
+
echo '{"text":"🩺 *Debrief* — Bashir'\''s Analysis\n\nPost-mortem and upstream feedback.","keyboard":{"inline_keyboard":[
|
|
264
|
+
[{"text":"/debrief","callback_data":"run:/debrief"}],
|
|
265
|
+
[{"text":"/debrief --submit","callback_data":"run:/debrief --submit"}],
|
|
266
|
+
[{"text":"/debrief --inbox","callback_data":"run:/debrief --inbox"}],
|
|
267
|
+
[{"text":"/debrief --campaign","callback_data":"run:/debrief --campaign"}],
|
|
268
|
+
[{"text":"← Back","callback_data":"menu:back"}]
|
|
269
|
+
]}}';;
|
|
270
|
+
void)
|
|
271
|
+
echo '{"text":"🌀 *Void* — Bombadil'\''s Sync\n\nUpdate methodology from upstream.","keyboard":{"inline_keyboard":[
|
|
272
|
+
[{"text":"/void","callback_data":"run:/void"}],
|
|
273
|
+
[{"text":"← Back","callback_data":"menu:back"}]
|
|
274
|
+
]}}';;
|
|
275
|
+
thumper)
|
|
276
|
+
echo '{"text":"🪱 *Thumper* — Bridge Control\n\nTelegram ↔ Claude Code.","keyboard":{"inline_keyboard":[
|
|
277
|
+
[{"text":"/thumper on","callback_data":"run:/thumper on"}],
|
|
278
|
+
[{"text":"/thumper off","callback_data":"run:/thumper off"}],
|
|
279
|
+
[{"text":"/thumper status","callback_data":"run:/thumper status"}],
|
|
280
|
+
[{"text":"← Back","callback_data":"menu:back"}]
|
|
281
|
+
]}}';;
|
|
282
|
+
imagine)
|
|
283
|
+
echo '{"text":"🎨 *Imagine* — Celebrimbor'\''s Forge\n\nAI image generation from PRD.","keyboard":{"inline_keyboard":[
|
|
284
|
+
[{"text":"/imagine","callback_data":"run:/imagine"}],
|
|
285
|
+
[{"text":"/imagine --scan","callback_data":"run:/imagine --scan"}],
|
|
286
|
+
[{"text":"← Back","callback_data":"menu:back"}]
|
|
287
|
+
]}}';;
|
|
288
|
+
esac
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
handle_callback_query() {
|
|
292
|
+
local callback_id="$1" data="$2" message_id="$3"
|
|
293
|
+
|
|
294
|
+
# Acknowledge the callback immediately (removes loading spinner)
|
|
295
|
+
curl -s --connect-timeout 5 --max-time 5 \
|
|
296
|
+
-X POST "${API_BASE}/answerCallbackQuery" \
|
|
297
|
+
-d "callback_query_id=$callback_id" >/dev/null 2>&1 || true
|
|
298
|
+
|
|
299
|
+
if [[ "$data" == "menu:back" ]]; then
|
|
300
|
+
# Edit message back to main menu
|
|
301
|
+
local keyboard='{"inline_keyboard":[
|
|
302
|
+
[{"text":"🚀 Campaign","callback_data":"menu:campaign"},{"text":"🔨 Build","callback_data":"menu:build"},{"text":"🛡️ Gauntlet","callback_data":"menu:gauntlet"}],
|
|
303
|
+
[{"text":"🦇 QA","callback_data":"menu:qa"},{"text":"🧝 UX","callback_data":"menu:ux"},{"text":"⚔️ Security","callback_data":"menu:security"}],
|
|
304
|
+
[{"text":"🏗️ Architect","callback_data":"menu:architect"},{"text":"📦 DevOps","callback_data":"menu:devops"},{"text":"🧪 Test","callback_data":"menu:test"}],
|
|
305
|
+
[{"text":"📋 Review","callback_data":"menu:review"},{"text":"🔖 Git","callback_data":"menu:git"},{"text":"🩺 Debrief","callback_data":"menu:debrief"}],
|
|
306
|
+
[{"text":"🌀 Void","callback_data":"menu:void"},{"text":"🪱 Thumper","callback_data":"menu:thumper"},{"text":"🎨 Imagine","callback_data":"menu:imagine"}]
|
|
307
|
+
]}'
|
|
308
|
+
curl -s --connect-timeout 5 --max-time 10 \
|
|
309
|
+
-X POST "${API_BASE}/editMessageText" \
|
|
310
|
+
-H "Content-Type: application/json" \
|
|
311
|
+
-d "{\"chat_id\":\"$CHAT_ID\",\"message_id\":$message_id,\"text\":\"🏜️ *VoidForge Command Center*\n\nTap a command to see its options:\",\"parse_mode\":\"Markdown\",\"reply_markup\":$keyboard}" >/dev/null 2>&1 || true
|
|
312
|
+
|
|
313
|
+
elif [[ "$data" == menu:* ]]; then
|
|
314
|
+
# Show submenu for the selected command
|
|
315
|
+
local cmd="${data#menu:}"
|
|
316
|
+
local submenu_json
|
|
317
|
+
submenu_json=$(get_submenu "$cmd")
|
|
318
|
+
if [[ -n "$submenu_json" ]]; then
|
|
319
|
+
local sub_text sub_keyboard
|
|
320
|
+
sub_text=$(echo "$submenu_json" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['text'])" 2>/dev/null)
|
|
321
|
+
sub_keyboard=$(echo "$submenu_json" | python3 -c "import sys,json; d=json.load(sys.stdin); print(json.dumps(d['keyboard']))" 2>/dev/null)
|
|
322
|
+
curl -s --connect-timeout 5 --max-time 10 \
|
|
323
|
+
-X POST "${API_BASE}/editMessageText" \
|
|
324
|
+
-H "Content-Type: application/json" \
|
|
325
|
+
-d "{\"chat_id\":\"$CHAT_ID\",\"message_id\":$message_id,\"text\":\"$sub_text\",\"parse_mode\":\"Markdown\",\"reply_markup\":$sub_keyboard}" >/dev/null 2>&1 || true
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
elif [[ "$data" == run:* ]]; then
|
|
329
|
+
# Send the command as a text message, then inject into Claude Code
|
|
330
|
+
local run_cmd="${data#run:}"
|
|
331
|
+
log "COMMAND CENTER: $run_cmd"
|
|
332
|
+
send_telegram "⚡ Sending: \`$run_cmd\`"
|
|
333
|
+
local sanitized
|
|
334
|
+
sanitized=$(sanitize_text "$run_cmd")
|
|
335
|
+
if [[ -n "$sanitized" ]]; then
|
|
336
|
+
inject_text "$sanitized"
|
|
337
|
+
fi
|
|
338
|
+
fi
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
# Parse callback queries from updates (outputs: update_id \t callback_id \t message_id \t data)
|
|
342
|
+
parse_callback_queries() {
|
|
343
|
+
local json="$1"
|
|
344
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
345
|
+
echo "$json" | python3 -c "
|
|
346
|
+
import sys, json
|
|
347
|
+
try:
|
|
348
|
+
data = json.load(sys.stdin)
|
|
349
|
+
if data.get('ok') and data.get('result'):
|
|
350
|
+
for update in data['result']:
|
|
351
|
+
cb = update.get('callback_query')
|
|
352
|
+
if cb:
|
|
353
|
+
uid = update.get('update_id', 0)
|
|
354
|
+
cb_id = cb.get('id', '')
|
|
355
|
+
msg = cb.get('message', {})
|
|
356
|
+
msg_id = msg.get('message_id', 0)
|
|
357
|
+
cb_data = cb.get('data', '')
|
|
358
|
+
if cb_data:
|
|
359
|
+
print(f'{uid}\t{cb_id}\t{msg_id}\t{cb_data}')
|
|
360
|
+
except Exception:
|
|
361
|
+
pass
|
|
362
|
+
" 2>/dev/null
|
|
363
|
+
fi
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
# ─── Transport Vector Injection ────────────────────────────────
|
|
367
|
+
|
|
368
|
+
inject_text() {
|
|
369
|
+
local text="$1"
|
|
370
|
+
case "$INJECT_METHOD" in
|
|
371
|
+
PTY_INJECT)
|
|
372
|
+
local claude_pid
|
|
373
|
+
claude_pid=$(pgrep -f "node.*claude" 2>/dev/null | head -1 || true)
|
|
374
|
+
[[ -z "$claude_pid" ]] && claude_pid=$(pgrep -f "claude" 2>/dev/null | head -1 || true)
|
|
375
|
+
if [[ -z "$claude_pid" ]]; then
|
|
376
|
+
log "ERROR: Cannot locate Claude process"
|
|
377
|
+
send_telegram "⚠️ The Voice finds no one to command. Is Claude Code running?"
|
|
378
|
+
return 1
|
|
379
|
+
fi
|
|
380
|
+
local tty_path
|
|
381
|
+
tty_path=$(readlink "/proc/$claude_pid/fd/0" 2>/dev/null || echo "")
|
|
382
|
+
if [[ "$tty_path" != /dev/pts/* ]] && [[ "$tty_path" != /dev/tty* ]]; then
|
|
383
|
+
log "ERROR: TTY path not a terminal device: $tty_path"
|
|
384
|
+
send_telegram "⚠️ Worm path error: invalid TTY for PID $claude_pid"
|
|
385
|
+
return 1
|
|
386
|
+
fi
|
|
387
|
+
printf '%s\n' "$text" > "$tty_path"
|
|
388
|
+
;;
|
|
389
|
+
TMUX_SENDKEYS)
|
|
390
|
+
local session="${TMUX_SESSION:-0}"
|
|
391
|
+
if ! tmux has-session -t "$session" 2>/dev/null; then
|
|
392
|
+
log "ERROR: tmux session '$session' not found"
|
|
393
|
+
send_telegram "⚠️ tmux session '$session' not found. Is tmux running?"
|
|
394
|
+
return 1
|
|
395
|
+
fi
|
|
396
|
+
tmux send-keys -l -t "$session" "$text"
|
|
397
|
+
tmux send-keys -t "$session" Enter
|
|
398
|
+
;;
|
|
399
|
+
OSASCRIPT)
|
|
400
|
+
local tmpfile
|
|
401
|
+
tmpfile=$(mktemp)
|
|
402
|
+
printf '%s' "$text" > "$tmpfile"
|
|
403
|
+
local term="${TERM_PROGRAM:-Terminal}"
|
|
404
|
+
local osascript_ok=0
|
|
405
|
+
if [[ "$term" == "iTerm.app" ]] || [[ "$term" == "iTerm2" ]]; then
|
|
406
|
+
osascript <<APPLESCRIPT 2>/dev/null && osascript_ok=1
|
|
407
|
+
set textContent to read POSIX file "$tmpfile" as «class utf8»
|
|
408
|
+
tell application "iTerm2"
|
|
409
|
+
tell current session of current window
|
|
410
|
+
write text textContent
|
|
411
|
+
end tell
|
|
412
|
+
end tell
|
|
413
|
+
APPLESCRIPT
|
|
414
|
+
else
|
|
415
|
+
osascript <<APPLESCRIPT 2>/dev/null && osascript_ok=1
|
|
416
|
+
set textContent to read POSIX file "$tmpfile" as «class utf8»
|
|
417
|
+
tell application "Terminal"
|
|
418
|
+
activate
|
|
419
|
+
tell application "System Events"
|
|
420
|
+
keystroke textContent
|
|
421
|
+
key code 36
|
|
422
|
+
end tell
|
|
423
|
+
end tell
|
|
424
|
+
APPLESCRIPT
|
|
425
|
+
fi
|
|
426
|
+
rm -f "$tmpfile"
|
|
427
|
+
if [[ "$osascript_ok" -ne 1 ]]; then
|
|
428
|
+
log "ERROR: osascript injection failed"
|
|
429
|
+
send_telegram "⚠️ The Voice cannot reach your terminal."
|
|
430
|
+
return 1
|
|
431
|
+
fi
|
|
432
|
+
;;
|
|
433
|
+
*)
|
|
434
|
+
log "ERROR: Unknown worm path: $INJECT_METHOD"
|
|
435
|
+
return 1
|
|
436
|
+
;;
|
|
437
|
+
esac
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
# ─── Initialize Offset ────────────────────────────────────────
|
|
441
|
+
|
|
442
|
+
LAST_ID=0
|
|
443
|
+
if [[ -f "$LAST_ID_FILE" ]]; then
|
|
444
|
+
LAST_ID=$(cat "$LAST_ID_FILE" 2>/dev/null || echo "0")
|
|
445
|
+
fi
|
|
446
|
+
|
|
447
|
+
if [[ "$LAST_ID" = "0" ]]; then
|
|
448
|
+
INIT_RESPONSE=$(curl -s --connect-timeout 5 --max-time 10 \
|
|
449
|
+
"${API_BASE}/getUpdates?offset=-1" 2>/dev/null || echo "")
|
|
450
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
451
|
+
LAST_ID=$(echo "$INIT_RESPONSE" | python3 -c "
|
|
452
|
+
import sys, json
|
|
453
|
+
try:
|
|
454
|
+
data = json.load(sys.stdin)
|
|
455
|
+
updates = data.get('result', [])
|
|
456
|
+
if updates:
|
|
457
|
+
print(updates[-1].get('update_id', 0))
|
|
458
|
+
else:
|
|
459
|
+
print(0)
|
|
460
|
+
except Exception:
|
|
461
|
+
print(0)
|
|
462
|
+
" 2>/dev/null || echo "0")
|
|
463
|
+
fi
|
|
464
|
+
fi
|
|
465
|
+
|
|
466
|
+
CONSECUTIVE_ERRORS=0
|
|
467
|
+
MAX_BACKOFF=60
|
|
468
|
+
|
|
469
|
+
log "🪱 Sandworm awakened (worm path: $INJECT_METHOD, PID: $$)"
|
|
470
|
+
|
|
471
|
+
# Initialize Gom Jabbar
|
|
472
|
+
gom_jabbar_init
|
|
473
|
+
|
|
474
|
+
# ─── Main Loop ─────────────────────────────────────────────────
|
|
475
|
+
|
|
476
|
+
while true; do
|
|
477
|
+
if [[ ! -f "$CHANNEL_FLAG" ]]; then
|
|
478
|
+
log "Voice silenced. Shutting down."
|
|
479
|
+
break
|
|
480
|
+
fi
|
|
481
|
+
|
|
482
|
+
RESPONSE=$(curl -s --connect-timeout 10 --max-time 40 \
|
|
483
|
+
"${API_BASE}/getUpdates?offset=$((LAST_ID + 1))&timeout=30" 2>/dev/null || echo "")
|
|
484
|
+
|
|
485
|
+
if [[ -z "$RESPONSE" ]]; then
|
|
486
|
+
CONSECUTIVE_ERRORS=$((CONSECUTIVE_ERRORS + 1))
|
|
487
|
+
if [[ $CONSECUTIVE_ERRORS -ge 3 ]]; then
|
|
488
|
+
backoff=$((CONSECUTIVE_ERRORS * 5))
|
|
489
|
+
[[ $backoff -gt $MAX_BACKOFF ]] && backoff=$MAX_BACKOFF
|
|
490
|
+
log "WARNING: $CONSECUTIVE_ERRORS consecutive failures. Backing off ${backoff}s."
|
|
491
|
+
sleep "$backoff"
|
|
492
|
+
fi
|
|
493
|
+
continue
|
|
494
|
+
fi
|
|
495
|
+
|
|
496
|
+
if echo "$RESPONSE" | grep -q '"ok":false'; then
|
|
497
|
+
error_code=$(echo "$RESPONSE" | grep -o '"error_code":[0-9]*' | head -1 | sed 's/.*://')
|
|
498
|
+
if [[ "$error_code" = "401" ]]; then
|
|
499
|
+
log "FATAL: Bot token rejected (401). Re-run /thumper setup."
|
|
500
|
+
break
|
|
501
|
+
fi
|
|
502
|
+
CONSECUTIVE_ERRORS=$((CONSECUTIVE_ERRORS + 1))
|
|
503
|
+
log "ERROR: API error (code: ${error_code:-unknown})"
|
|
504
|
+
continue
|
|
505
|
+
fi
|
|
506
|
+
|
|
507
|
+
CONSECUTIVE_ERRORS=0
|
|
508
|
+
|
|
509
|
+
while IFS=$'\t' read -r update_id msg_id msg_chat_id text; do
|
|
510
|
+
[[ -z "$update_id" ]] && continue
|
|
511
|
+
[[ -z "$text" ]] && continue
|
|
512
|
+
|
|
513
|
+
if [[ "$msg_chat_id" != "$CHAT_ID" ]]; then
|
|
514
|
+
log "REJECTED: Unauthorized thumper beat (update $update_id)"
|
|
515
|
+
advance_offset "$update_id"
|
|
516
|
+
continue
|
|
517
|
+
fi
|
|
518
|
+
|
|
519
|
+
if [[ "$text" == /thumper* ]]; then
|
|
520
|
+
log "SKIPPED: /thumper command (loop prevention)"
|
|
521
|
+
advance_offset "$update_id"
|
|
522
|
+
continue
|
|
523
|
+
fi
|
|
524
|
+
|
|
525
|
+
# ─── Gom Jabbar Gate ────────────────────────────────────
|
|
526
|
+
auth_state=$(gom_jabbar_verify_active)
|
|
527
|
+
|
|
528
|
+
case "$auth_state" in
|
|
529
|
+
LOCKED)
|
|
530
|
+
advance_offset "$update_id"
|
|
531
|
+
continue
|
|
532
|
+
;;
|
|
533
|
+
PENDING|CHALLENGE)
|
|
534
|
+
# This message might be the passphrase — do NOT sanitize
|
|
535
|
+
# (passphrase may intentionally contain special chars)
|
|
536
|
+
if gom_jabbar_check "$text"; then
|
|
537
|
+
# Delete passphrase from chat — if deletion fails,
|
|
538
|
+
# gom_jabbar_delete_message invalidates the session (ADR-004).
|
|
539
|
+
# Only send success if deletion succeeded.
|
|
540
|
+
if gom_jabbar_delete_message "$msg_id"; then
|
|
541
|
+
if [[ "$auth_state" == "PENDING" ]]; then
|
|
542
|
+
send_telegram "$(printf '%s\n%s\n\n%s' \
|
|
543
|
+
'✅ Passphrase accepted.' \
|
|
544
|
+
'Your message has been deleted from this chat.' \
|
|
545
|
+
'The Voice carries. 🪱')"
|
|
546
|
+
else
|
|
547
|
+
send_telegram "✅ You are human. The Voice carries. 🪱"
|
|
548
|
+
fi
|
|
549
|
+
fi
|
|
550
|
+
# If delete failed, session was invalidated — no success msg
|
|
551
|
+
else
|
|
552
|
+
gom_jabbar_delete_message "$msg_id"
|
|
553
|
+
gom_jabbar_fail
|
|
554
|
+
fi
|
|
555
|
+
# ADR-002: No message queuing during auth — drop, don't queue
|
|
556
|
+
advance_offset "$update_id"
|
|
557
|
+
continue
|
|
558
|
+
;;
|
|
559
|
+
AUTHENTICATED)
|
|
560
|
+
gom_jabbar_touch
|
|
561
|
+
;;
|
|
562
|
+
esac
|
|
563
|
+
|
|
564
|
+
# ─── Sanitize and validate ──────────────────────────────
|
|
565
|
+
if [[ ${#text} -gt $MAX_MESSAGE_LENGTH ]]; then
|
|
566
|
+
log "REJECTED: oversized thumper beat (${#text} chars, update $update_id)"
|
|
567
|
+
advance_offset "$update_id"
|
|
568
|
+
continue
|
|
569
|
+
fi
|
|
570
|
+
|
|
571
|
+
text=$(sanitize_text "$text")
|
|
572
|
+
|
|
573
|
+
if [[ -z "$text" ]]; then
|
|
574
|
+
log "SKIPPED: empty after sanitization (update $update_id)"
|
|
575
|
+
advance_offset "$update_id"
|
|
576
|
+
continue
|
|
577
|
+
fi
|
|
578
|
+
|
|
579
|
+
if [[ ! -f "$CHANNEL_FLAG" ]]; then
|
|
580
|
+
break
|
|
581
|
+
fi
|
|
582
|
+
|
|
583
|
+
log "THUMPER BEAT received (update $update_id, ${#text} chars)"
|
|
584
|
+
|
|
585
|
+
# Intercept /help — show command center instead of injecting into Claude
|
|
586
|
+
if [[ "$text" == "/help" ]] || [[ "$text" == "/help@"* ]]; then
|
|
587
|
+
log "COMMAND CENTER requested (update $update_id)"
|
|
588
|
+
send_command_menu
|
|
589
|
+
advance_offset "$update_id"
|
|
590
|
+
continue
|
|
591
|
+
fi
|
|
592
|
+
|
|
593
|
+
if inject_text "$text"; then
|
|
594
|
+
log "VOICE CARRIED via $INJECT_METHOD (update $update_id)"
|
|
595
|
+
advance_offset "$update_id"
|
|
596
|
+
else
|
|
597
|
+
log "VOICE FAILED (update $update_id) — will retry"
|
|
598
|
+
break
|
|
599
|
+
fi
|
|
600
|
+
|
|
601
|
+
done < <(parse_updates "$RESPONSE")
|
|
602
|
+
|
|
603
|
+
# Process callback queries (inline keyboard button taps)
|
|
604
|
+
while IFS=$'\t' read -r cb_update_id cb_id cb_msg_id cb_data; do
|
|
605
|
+
[[ -z "$cb_update_id" ]] && continue
|
|
606
|
+
[[ -z "$cb_data" ]] && continue
|
|
607
|
+
handle_callback_query "$cb_id" "$cb_data" "$cb_msg_id"
|
|
608
|
+
advance_offset "$cb_update_id"
|
|
609
|
+
done < <(parse_callback_queries "$RESPONSE")
|
|
610
|
+
done
|