thevoidforge 21.0.11 → 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.
Files changed (107) hide show
  1. package/dist/.claude/commands/ai.md +69 -0
  2. package/dist/.claude/commands/architect.md +121 -0
  3. package/dist/.claude/commands/assemble.md +201 -0
  4. package/dist/.claude/commands/assess.md +75 -0
  5. package/dist/.claude/commands/blueprint.md +135 -0
  6. package/dist/.claude/commands/build.md +116 -0
  7. package/dist/.claude/commands/campaign.md +201 -0
  8. package/dist/.claude/commands/cultivation.md +166 -0
  9. package/dist/.claude/commands/current.md +128 -0
  10. package/dist/.claude/commands/dangerroom.md +74 -0
  11. package/dist/.claude/commands/debrief.md +178 -0
  12. package/dist/.claude/commands/deploy.md +99 -0
  13. package/dist/.claude/commands/devops.md +143 -0
  14. package/dist/.claude/commands/gauntlet.md +140 -0
  15. package/dist/.claude/commands/git.md +104 -0
  16. package/dist/.claude/commands/grow.md +146 -0
  17. package/dist/.claude/commands/imagine.md +126 -0
  18. package/dist/.claude/commands/portfolio.md +50 -0
  19. package/dist/.claude/commands/prd.md +113 -0
  20. package/dist/.claude/commands/qa.md +107 -0
  21. package/dist/.claude/commands/review.md +151 -0
  22. package/dist/.claude/commands/security.md +100 -0
  23. package/dist/.claude/commands/test.md +96 -0
  24. package/dist/.claude/commands/thumper.md +116 -0
  25. package/dist/.claude/commands/treasury.md +100 -0
  26. package/dist/.claude/commands/ux.md +118 -0
  27. package/dist/.claude/commands/vault.md +189 -0
  28. package/dist/.claude/commands/void.md +108 -0
  29. package/dist/CHANGELOG.md +1918 -0
  30. package/dist/CLAUDE.md +250 -0
  31. package/dist/HOLOCRON.md +856 -0
  32. package/dist/VERSION.md +123 -0
  33. package/dist/docs/NAMING_REGISTRY.md +478 -0
  34. package/dist/docs/methods/AI_INTELLIGENCE.md +276 -0
  35. package/dist/docs/methods/ASSEMBLER.md +142 -0
  36. package/dist/docs/methods/BACKEND_ENGINEER.md +165 -0
  37. package/dist/docs/methods/BUILD_JOURNAL.md +185 -0
  38. package/dist/docs/methods/BUILD_PROTOCOL.md +426 -0
  39. package/dist/docs/methods/CAMPAIGN.md +568 -0
  40. package/dist/docs/methods/CONTEXT_MANAGEMENT.md +189 -0
  41. package/dist/docs/methods/DEEP_CURRENT.md +184 -0
  42. package/dist/docs/methods/DEVOPS_ENGINEER.md +295 -0
  43. package/dist/docs/methods/FIELD_MEDIC.md +261 -0
  44. package/dist/docs/methods/FORGE_ARTIST.md +108 -0
  45. package/dist/docs/methods/FORGE_KEEPER.md +268 -0
  46. package/dist/docs/methods/GAUNTLET.md +344 -0
  47. package/dist/docs/methods/GROWTH_STRATEGIST.md +466 -0
  48. package/dist/docs/methods/HEARTBEAT.md +168 -0
  49. package/dist/docs/methods/MCP_INTEGRATION.md +139 -0
  50. package/dist/docs/methods/MUSTER.md +148 -0
  51. package/dist/docs/methods/PRD_GENERATOR.md +186 -0
  52. package/dist/docs/methods/PRODUCT_DESIGN_FRONTEND.md +250 -0
  53. package/dist/docs/methods/QA_ENGINEER.md +337 -0
  54. package/dist/docs/methods/RELEASE_MANAGER.md +145 -0
  55. package/dist/docs/methods/SECURITY_AUDITOR.md +320 -0
  56. package/dist/docs/methods/SUB_AGENTS.md +335 -0
  57. package/dist/docs/methods/SYSTEMS_ARCHITECT.md +171 -0
  58. package/dist/docs/methods/TESTING.md +359 -0
  59. package/dist/docs/methods/THUMPER.md +175 -0
  60. package/dist/docs/methods/TIME_VAULT.md +120 -0
  61. package/dist/docs/methods/TREASURY.md +184 -0
  62. package/dist/docs/methods/TROUBLESHOOTING.md +265 -0
  63. package/dist/docs/patterns/README.md +52 -0
  64. package/dist/docs/patterns/ad-billing-adapter.ts +537 -0
  65. package/dist/docs/patterns/ad-platform-adapter.ts +421 -0
  66. package/dist/docs/patterns/ai-classifier.ts +195 -0
  67. package/dist/docs/patterns/ai-eval.ts +272 -0
  68. package/dist/docs/patterns/ai-orchestrator.ts +341 -0
  69. package/dist/docs/patterns/ai-router.ts +194 -0
  70. package/dist/docs/patterns/ai-tool-schema.ts +237 -0
  71. package/dist/docs/patterns/api-route.ts +241 -0
  72. package/dist/docs/patterns/backtest-engine.ts +499 -0
  73. package/dist/docs/patterns/browser-review.ts +292 -0
  74. package/dist/docs/patterns/combobox.tsx +300 -0
  75. package/dist/docs/patterns/component.tsx +262 -0
  76. package/dist/docs/patterns/daemon-process.ts +338 -0
  77. package/dist/docs/patterns/data-pipeline.ts +297 -0
  78. package/dist/docs/patterns/database-migration.ts +466 -0
  79. package/dist/docs/patterns/e2e-test.ts +629 -0
  80. package/dist/docs/patterns/error-handling.ts +312 -0
  81. package/dist/docs/patterns/execution-safety.ts +601 -0
  82. package/dist/docs/patterns/financial-transaction.ts +342 -0
  83. package/dist/docs/patterns/funding-plan.ts +462 -0
  84. package/dist/docs/patterns/game-entity.ts +137 -0
  85. package/dist/docs/patterns/game-loop.ts +113 -0
  86. package/dist/docs/patterns/game-state.ts +143 -0
  87. package/dist/docs/patterns/job-queue.ts +225 -0
  88. package/dist/docs/patterns/kongo-integration.ts +164 -0
  89. package/dist/docs/patterns/middleware.ts +363 -0
  90. package/dist/docs/patterns/mobile-screen.tsx +139 -0
  91. package/dist/docs/patterns/mobile-service.ts +167 -0
  92. package/dist/docs/patterns/multi-tenant.ts +382 -0
  93. package/dist/docs/patterns/oauth-token-lifecycle.ts +223 -0
  94. package/dist/docs/patterns/outbound-rate-limiter.ts +260 -0
  95. package/dist/docs/patterns/prompt-template.ts +195 -0
  96. package/dist/docs/patterns/revenue-source-adapter.ts +311 -0
  97. package/dist/docs/patterns/service.ts +224 -0
  98. package/dist/docs/patterns/sse-endpoint.ts +118 -0
  99. package/dist/docs/patterns/stablecoin-adapter.ts +511 -0
  100. package/dist/docs/patterns/third-party-script.ts +68 -0
  101. package/dist/scripts/thumper/gom-jabbar.sh +241 -0
  102. package/dist/scripts/thumper/relay.sh +610 -0
  103. package/dist/scripts/thumper/scan.sh +359 -0
  104. package/dist/scripts/thumper/thumper.sh +190 -0
  105. package/dist/scripts/thumper/water-rings.sh +76 -0
  106. package/package.json +1 -1
  107. 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