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,359 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# scan.sh — Reading the sand — Chani's environment scanner
|
|
3
|
+
# Detects runtime environment, collects Telegram credentials, writes sietch vault
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
9
|
+
CONFIG_DIR="$PROJECT_ROOT/.voidforge/thumper"
|
|
10
|
+
CONFIG_FILE="$CONFIG_DIR/sietch.env"
|
|
11
|
+
|
|
12
|
+
# ─── Non-Interactive Mode (--token and --chat-id args) ───────
|
|
13
|
+
# Allows /thumper setup to run from Claude Code's Bash tool,
|
|
14
|
+
# which doesn't support interactive stdin (read -r -p).
|
|
15
|
+
# Usage: scan.sh --token 123:ABC --chat-id 456
|
|
16
|
+
ARG_TOKEN=""
|
|
17
|
+
ARG_CHAT_ID=""
|
|
18
|
+
while [[ $# -gt 0 ]]; do
|
|
19
|
+
case "$1" in
|
|
20
|
+
--token) ARG_TOKEN="$2"; shift 2 ;;
|
|
21
|
+
--chat-id) ARG_CHAT_ID="$2"; shift 2 ;;
|
|
22
|
+
*) shift ;;
|
|
23
|
+
esac
|
|
24
|
+
done
|
|
25
|
+
|
|
26
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
27
|
+
echo "❌ curl is required but not found. Install curl and re-run."
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# ─── JSON Parsing Helper ──────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
json_extract() {
|
|
34
|
+
local json="$1" dotpath="$2"
|
|
35
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
36
|
+
echo "$json" | python3 -c "
|
|
37
|
+
import sys, json
|
|
38
|
+
try:
|
|
39
|
+
d = json.load(sys.stdin)
|
|
40
|
+
for key in sys.argv[1].split('.'):
|
|
41
|
+
d = d[key] if isinstance(d, dict) else d[int(key)]
|
|
42
|
+
print(d)
|
|
43
|
+
except Exception:
|
|
44
|
+
pass
|
|
45
|
+
" "$dotpath" 2>/dev/null
|
|
46
|
+
elif command -v jq >/dev/null 2>&1; then
|
|
47
|
+
echo "$json" | jq -r ".$dotpath" 2>/dev/null
|
|
48
|
+
else
|
|
49
|
+
echo ""
|
|
50
|
+
fi
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
# ─── Setup ─────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
echo ""
|
|
56
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
57
|
+
echo "🏜️ Reading the Sand — Chani's Scanner"
|
|
58
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
59
|
+
echo ""
|
|
60
|
+
|
|
61
|
+
# ─── Non-Interactive Fast Path ────────────────────────────────
|
|
62
|
+
# Skip all prompts when --token and --chat-id are both provided
|
|
63
|
+
if [[ -n "$ARG_TOKEN" ]] && [[ -n "$ARG_CHAT_ID" ]]; then
|
|
64
|
+
BOT_TOKEN="$ARG_TOKEN"
|
|
65
|
+
CHAT_ID="$ARG_CHAT_ID"
|
|
66
|
+
HAS_BOT="yes"
|
|
67
|
+
|
|
68
|
+
echo "🏜️ Non-interactive mode — validating credentials..."
|
|
69
|
+
API_BASE="https://api.telegram.org/bot${BOT_TOKEN}"
|
|
70
|
+
VALIDATE=$(curl -s --connect-timeout 5 --max-time 10 "${API_BASE}/getMe" 2>/dev/null || echo "")
|
|
71
|
+
if echo "$VALIDATE" | grep -q '"ok":true'; then
|
|
72
|
+
BOT_NAME=$(json_extract "$VALIDATE" "result.username" 2>/dev/null || echo "unknown")
|
|
73
|
+
echo "✅ Voice validated: @${BOT_NAME}"
|
|
74
|
+
echo "✅ Chat ID: $CHAT_ID"
|
|
75
|
+
else
|
|
76
|
+
echo "❌ Invalid bot token or Telegram unreachable."
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
else
|
|
80
|
+
# ─── Interactive Mode ──────────────────────────────────────
|
|
81
|
+
|
|
82
|
+
if [[ -f "$CONFIG_FILE" ]]; then
|
|
83
|
+
echo "⚠️ Existing sietch vault found at $CONFIG_FILE"
|
|
84
|
+
read -r -p "Overwrite? (yes/no): " OVERWRITE
|
|
85
|
+
if [[ "$OVERWRITE" != "yes" ]]; then
|
|
86
|
+
echo "Scan aborted. Sietch vault preserved."
|
|
87
|
+
exit 0
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# ─── Step 1: Telegram Bot ─────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
read -r -p "Do you have an existing Telegram bot? (yes/no): " HAS_BOT
|
|
94
|
+
|
|
95
|
+
if [[ "$HAS_BOT" != "yes" ]]; then
|
|
96
|
+
echo ""
|
|
97
|
+
echo "┌──────────────────────────────────────┐"
|
|
98
|
+
echo "│ Summon Your Voice (Create a Bot) │"
|
|
99
|
+
echo "└──────────────────────────────────────┘"
|
|
100
|
+
echo ""
|
|
101
|
+
echo " 1. Open Telegram"
|
|
102
|
+
echo " 2. Search for @BotFather and start a chat"
|
|
103
|
+
echo " 3. Send the message: /newbot"
|
|
104
|
+
echo " 4. Choose a name for your bot (display name)"
|
|
105
|
+
echo " 5. Choose a username ending in 'bot' (e.g. mydev_bot)"
|
|
106
|
+
echo " 6. BotFather will give you a token like: 123456:ABC-DEF..."
|
|
107
|
+
echo " 7. Copy that token, then send ANY message to your new bot"
|
|
108
|
+
echo " (type \"hello\" — required before the bridge can find you)"
|
|
109
|
+
echo ""
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
read -r -p "Paste your bot token here: " BOT_TOKEN
|
|
113
|
+
|
|
114
|
+
if [[ -z "$BOT_TOKEN" ]]; then
|
|
115
|
+
echo "❌ No token provided. Scan aborted."
|
|
116
|
+
exit 1
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
echo ""
|
|
120
|
+
echo "🔍 Validating your Voice..."
|
|
121
|
+
API_BASE="https://api.telegram.org/bot${BOT_TOKEN}"
|
|
122
|
+
VALIDATE=$(curl -s --connect-timeout 5 --max-time 10 "${API_BASE}/getMe" 2>/dev/null || echo "")
|
|
123
|
+
|
|
124
|
+
if echo "$VALIDATE" | grep -q '"ok":true'; then
|
|
125
|
+
BOT_NAME=$(json_extract "$VALIDATE" "result.username" 2>/dev/null || echo "unknown")
|
|
126
|
+
echo "✅ Voice validated: @${BOT_NAME}"
|
|
127
|
+
else
|
|
128
|
+
echo "❌ Invalid bot token or Telegram unreachable."
|
|
129
|
+
exit 1
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
CHAT_ID=""
|
|
133
|
+
if [[ "$HAS_BOT" == "yes" ]]; then
|
|
134
|
+
read -r -p "Do you know your chat ID? (yes/no): " KNOWS_CHAT_ID
|
|
135
|
+
if [[ "$KNOWS_CHAT_ID" == "yes" ]]; then
|
|
136
|
+
read -r -p "Enter your chat ID: " CHAT_ID
|
|
137
|
+
fi
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
if [[ -z "$CHAT_ID" ]]; then
|
|
141
|
+
echo ""
|
|
142
|
+
echo "🔍 Scanning for your presence in the desert..."
|
|
143
|
+
|
|
144
|
+
auto_detect_chat_id() {
|
|
145
|
+
local updates
|
|
146
|
+
updates=$(curl -s --connect-timeout 5 --max-time 15 "${API_BASE}/getUpdates?limit=10" 2>/dev/null || echo "")
|
|
147
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
148
|
+
echo "$updates" | python3 -c "
|
|
149
|
+
import sys, json
|
|
150
|
+
try:
|
|
151
|
+
data = json.load(sys.stdin)
|
|
152
|
+
if data.get('ok') and data.get('result'):
|
|
153
|
+
for update in data['result']:
|
|
154
|
+
msg = update.get('message', {})
|
|
155
|
+
chat = msg.get('chat', {})
|
|
156
|
+
if chat.get('type') == 'private':
|
|
157
|
+
print(chat['id'])
|
|
158
|
+
break
|
|
159
|
+
except Exception: pass
|
|
160
|
+
" 2>/dev/null
|
|
161
|
+
elif command -v jq >/dev/null 2>&1; then
|
|
162
|
+
echo "$updates" | jq -r '[.result[]? | select(.message.chat.type == "private")] | first | .message.chat.id // empty' 2>/dev/null
|
|
163
|
+
else
|
|
164
|
+
echo "$updates" | grep -o '"chat":{"id":[0-9]*' | head -1 | sed 's/.*://'
|
|
165
|
+
fi
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
CHAT_ID=$(auto_detect_chat_id)
|
|
169
|
+
|
|
170
|
+
if [[ -z "$CHAT_ID" ]]; then
|
|
171
|
+
echo " No presence detected. Send any message to your bot now."
|
|
172
|
+
read -r -p " Press Enter when done..." _
|
|
173
|
+
sleep 2
|
|
174
|
+
CHAT_ID=$(auto_detect_chat_id)
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
if [[ -z "$CHAT_ID" ]]; then
|
|
178
|
+
echo " ⚠️ Auto-detection failed."
|
|
179
|
+
read -r -p " Enter your chat ID manually: " CHAT_ID
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
if [[ -z "$CHAT_ID" ]]; then
|
|
183
|
+
echo "❌ No chat ID provided. Scan aborted."
|
|
184
|
+
exit 1
|
|
185
|
+
fi
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
echo "✅ Presence confirmed. Chat ID: $CHAT_ID"
|
|
189
|
+
|
|
190
|
+
fi # end interactive mode (else branch of non-interactive check)
|
|
191
|
+
|
|
192
|
+
# ─── Step 2: Environment Scan ─────────────────────────────────
|
|
193
|
+
|
|
194
|
+
echo ""
|
|
195
|
+
echo "🏜️ Reading the sand..."
|
|
196
|
+
|
|
197
|
+
INJECT_METHOD=""
|
|
198
|
+
TMUX_SESSION_NAME=""
|
|
199
|
+
ENV_LABEL=""
|
|
200
|
+
|
|
201
|
+
detect_environment() {
|
|
202
|
+
# Priority 1: tmux (most reliable, cross-platform)
|
|
203
|
+
if command -v tmux >/dev/null 2>&1 && [[ -n "${TMUX:-}" ]]; then
|
|
204
|
+
INJECT_METHOD="TMUX_SENDKEYS"
|
|
205
|
+
TMUX_SESSION_NAME=$(tmux display-message -p '#S' 2>/dev/null || echo "0")
|
|
206
|
+
ENV_LABEL="TMUX session ($TMUX_SESSION_NAME)"
|
|
207
|
+
return
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
# Priority 2: Headless Linux SSH
|
|
211
|
+
if [[ -z "${DISPLAY:-}" ]] && [[ -z "${WAYLAND_DISPLAY:-}" ]] && \
|
|
212
|
+
[[ "$(tty 2>/dev/null || echo "")" == /dev/pts/* ]] && \
|
|
213
|
+
[[ "${OSTYPE:-}" == linux* ]] && [[ -d "/proc" ]]; then
|
|
214
|
+
INJECT_METHOD="PTY_INJECT"
|
|
215
|
+
ENV_LABEL="Headless desert (SSH)"
|
|
216
|
+
return
|
|
217
|
+
fi
|
|
218
|
+
|
|
219
|
+
# Priority 3: macOS with supported terminal (Terminal.app or iTerm2 only)
|
|
220
|
+
if [[ "${OSTYPE:-}" == darwin* ]] && [[ -z "${SSH_CLIENT:-}" ]] && [[ -z "${SSH_TTY:-}" ]]; then
|
|
221
|
+
local tp="${TERM_PROGRAM:-}"
|
|
222
|
+
if [[ "$tp" == "Apple_Terminal" ]] || [[ "$tp" == "iTerm.app" ]] || [[ "$tp" == "iTerm2" ]]; then
|
|
223
|
+
INJECT_METHOD="OSASCRIPT"
|
|
224
|
+
ENV_LABEL="Local oasis ($tp)"
|
|
225
|
+
return
|
|
226
|
+
fi
|
|
227
|
+
# macOS with unsupported terminal (VS Code, Warp, Alacritty, Kitty, etc.)
|
|
228
|
+
if [[ -n "$tp" ]]; then
|
|
229
|
+
INJECT_METHOD=""
|
|
230
|
+
ENV_LABEL="macOS — $tp (OSASCRIPT not supported for this terminal)"
|
|
231
|
+
echo ""
|
|
232
|
+
echo "⚠️ Your terminal ($tp) is not supported by OSASCRIPT injection."
|
|
233
|
+
echo " OSASCRIPT only works with Terminal.app and iTerm2."
|
|
234
|
+
echo ""
|
|
235
|
+
echo " Recommended: install tmux and run Claude Code inside a tmux session."
|
|
236
|
+
echo " Then re-run /thumper setup — tmux will be auto-detected."
|
|
237
|
+
echo ""
|
|
238
|
+
# Don't return — fall through to manual selection
|
|
239
|
+
fi
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
# Priority 4: Linux with /proc
|
|
243
|
+
if [[ "${OSTYPE:-}" == linux* ]] && [[ -d "/proc" ]]; then
|
|
244
|
+
INJECT_METHOD="PTY_INJECT"
|
|
245
|
+
ENV_LABEL="Linux desert"
|
|
246
|
+
return
|
|
247
|
+
fi
|
|
248
|
+
|
|
249
|
+
# Priority 5: Windows (Git Bash, MSYS2, Cygwin) — not supported
|
|
250
|
+
if [[ "${OSTYPE:-}" == msys* ]] || [[ "${OSTYPE:-}" == cygwin* ]]; then
|
|
251
|
+
echo ""
|
|
252
|
+
echo "⚠️ Windows native terminals (Git Bash, MSYS2) are not supported."
|
|
253
|
+
echo " The Voice requires a Unix PTY for message injection."
|
|
254
|
+
echo ""
|
|
255
|
+
echo " Recommended: use WSL (Windows Subsystem for Linux) with tmux."
|
|
256
|
+
echo " Claude Code in WSL + tmux will be auto-detected."
|
|
257
|
+
echo ""
|
|
258
|
+
fi
|
|
259
|
+
|
|
260
|
+
INJECT_METHOD=""
|
|
261
|
+
ENV_LABEL="Unknown terrain"
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
detect_environment
|
|
265
|
+
|
|
266
|
+
if [[ -z "$INJECT_METHOD" ]]; then
|
|
267
|
+
echo "⚠️ Cannot determine a viable worm path."
|
|
268
|
+
echo ""
|
|
269
|
+
echo " Supported terrain:"
|
|
270
|
+
echo " 1. Any platform with tmux (TMUX_SENDKEYS)"
|
|
271
|
+
echo " 2. Headless Linux over SSH (PTY_INJECT)"
|
|
272
|
+
echo " 3. macOS local terminal (OSASCRIPT)"
|
|
273
|
+
echo " 4. Linux with /proc filesystem (PTY_INJECT)"
|
|
274
|
+
echo ""
|
|
275
|
+
read -r -p " Select manually (1-4) or 'q' to abort: " MANUAL_CHOICE
|
|
276
|
+
case "$MANUAL_CHOICE" in
|
|
277
|
+
1) INJECT_METHOD="TMUX_SENDKEYS"; read -r -p " tmux session name: " TMUX_SESSION_NAME ;;
|
|
278
|
+
2|4) INJECT_METHOD="PTY_INJECT" ;;
|
|
279
|
+
3) INJECT_METHOD="OSASCRIPT" ;;
|
|
280
|
+
*) echo "Scan aborted."; exit 1 ;;
|
|
281
|
+
esac
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
echo "🔍 Terrain: $ENV_LABEL"
|
|
285
|
+
echo "⚡ Worm path: $INJECT_METHOD"
|
|
286
|
+
|
|
287
|
+
# Skip confirmation in non-interactive mode
|
|
288
|
+
if [[ -n "$ARG_TOKEN" ]] && [[ -n "$ARG_CHAT_ID" ]]; then
|
|
289
|
+
CONFIRM_ENV="yes"
|
|
290
|
+
else
|
|
291
|
+
read -r -p "Does this look right? (yes/no): " CONFIRM_ENV
|
|
292
|
+
fi
|
|
293
|
+
|
|
294
|
+
if [[ "$CONFIRM_ENV" != "yes" ]]; then
|
|
295
|
+
echo ""
|
|
296
|
+
echo "Select worm path:"
|
|
297
|
+
echo " 1. TMUX_SENDKEYS — tmux send-keys (requires tmux)"
|
|
298
|
+
echo " 2. PTY_INJECT — Direct PTY write (Linux with /proc)"
|
|
299
|
+
echo " 3. OSASCRIPT — macOS keystroke injection"
|
|
300
|
+
read -r -p "Choice (1-3): " MANUAL_CHOICE
|
|
301
|
+
case "$MANUAL_CHOICE" in
|
|
302
|
+
1) INJECT_METHOD="TMUX_SENDKEYS"; read -r -p "tmux session name: " TMUX_SESSION_NAME ;;
|
|
303
|
+
2) INJECT_METHOD="PTY_INJECT" ;;
|
|
304
|
+
3) INJECT_METHOD="OSASCRIPT" ;;
|
|
305
|
+
*) echo "Invalid choice."; exit 1 ;;
|
|
306
|
+
esac
|
|
307
|
+
fi
|
|
308
|
+
|
|
309
|
+
# ─── Step 3: Write Sietch Vault ────────────────────────────────
|
|
310
|
+
|
|
311
|
+
mkdir -p "$CONFIG_DIR"
|
|
312
|
+
chmod 700 "$CONFIG_DIR"
|
|
313
|
+
|
|
314
|
+
if [[ ! "$BOT_TOKEN" =~ ^[0-9]+:[A-Za-z0-9_-]+$ ]]; then
|
|
315
|
+
echo "⚠️ Warning: Bot token format is unusual. Verify it's correct."
|
|
316
|
+
fi
|
|
317
|
+
|
|
318
|
+
(
|
|
319
|
+
umask 077
|
|
320
|
+
{
|
|
321
|
+
echo "# Sietch vault — generated by Chani's scanner"
|
|
322
|
+
echo "# WARNING: Contains credentials. Never commit this file."
|
|
323
|
+
printf 'BOT_TOKEN=%q\n' "$BOT_TOKEN"
|
|
324
|
+
printf 'CHAT_ID=%q\n' "$CHAT_ID"
|
|
325
|
+
printf 'INJECT_METHOD=%q\n' "$INJECT_METHOD"
|
|
326
|
+
printf 'TMUX_SESSION=%q\n' "${TMUX_SESSION_NAME:-}"
|
|
327
|
+
printf 'TERM_PROGRAM=%q\n' "${TERM_PROGRAM:-}"
|
|
328
|
+
printf 'SETUP_COMPLETE=%q\n' "true"
|
|
329
|
+
printf 'SETUP_DATE=%q\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
330
|
+
printf 'CONFIG_VERSION=%q\n' "1"
|
|
331
|
+
} > "$CONFIG_FILE"
|
|
332
|
+
)
|
|
333
|
+
chmod 600 "$CONFIG_FILE"
|
|
334
|
+
|
|
335
|
+
# ─── Step 4: Summary ──────────────────────────────────────────
|
|
336
|
+
|
|
337
|
+
echo ""
|
|
338
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
339
|
+
echo "🏜️ Sietch Vault Sealed"
|
|
340
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
341
|
+
echo " Bot token: ****...${BOT_TOKEN: -4}"
|
|
342
|
+
echo " Chat ID: $CHAT_ID"
|
|
343
|
+
echo " Worm path: $INJECT_METHOD"
|
|
344
|
+
echo " Vault: $CONFIG_FILE"
|
|
345
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
346
|
+
echo ""
|
|
347
|
+
|
|
348
|
+
# Skip prompt in non-interactive mode — don't auto-start, let user /thumper on
|
|
349
|
+
if [[ -n "$ARG_TOKEN" ]] && [[ -n "$ARG_CHAT_ID" ]]; then
|
|
350
|
+
START_NOW="no"
|
|
351
|
+
else
|
|
352
|
+
read -r -p "Speak The Voice now? (yes/no): " START_NOW
|
|
353
|
+
fi
|
|
354
|
+
|
|
355
|
+
if [[ "$START_NOW" == "yes" ]]; then
|
|
356
|
+
bash "$SCRIPT_DIR/thumper.sh" on
|
|
357
|
+
else
|
|
358
|
+
echo "Run /thumper on when you're ready. The desert waits."
|
|
359
|
+
fi
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# thumper.sh — Main entrypoint for Chani's worm rider
|
|
3
|
+
# The Bene Gesserit Voice — command through speech across the desert
|
|
4
|
+
# Routes commands to scan.sh (setup), relay.sh (sandworm daemon), and status
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
# Security: refuse to run as root
|
|
9
|
+
if [[ "$(id -u)" -eq 0 ]]; then
|
|
10
|
+
echo "❌ Cannot run as root. Use a normal user account." >&2
|
|
11
|
+
exit 1
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
15
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
16
|
+
CONFIG_DIR="$PROJECT_ROOT/.voidforge/thumper"
|
|
17
|
+
CONFIG_FILE="$CONFIG_DIR/sietch.env"
|
|
18
|
+
CHANNEL_FLAG="$CONFIG_DIR/.thumper.active"
|
|
19
|
+
PID_FILE="$CONFIG_DIR/.worm.pid"
|
|
20
|
+
GOM_JABBAR_FILE="$CONFIG_DIR/.gom-jabbar"
|
|
21
|
+
|
|
22
|
+
usage() {
|
|
23
|
+
cat <<'EOF'
|
|
24
|
+
🏜️ /thumper — Chani's Worm Rider
|
|
25
|
+
|
|
26
|
+
Usage:
|
|
27
|
+
/thumper setup — First-time scan or re-configure
|
|
28
|
+
/thumper on — The Voice carries (open channel)
|
|
29
|
+
/thumper off — Silence in the desert (close channel)
|
|
30
|
+
/thumper status — Report channel state and worm path
|
|
31
|
+
EOF
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
ensure_config() {
|
|
35
|
+
if [[ ! -f "$CONFIG_FILE" ]]; then
|
|
36
|
+
echo "🏜️ No config found. Running first-time setup..."
|
|
37
|
+
bash "$SCRIPT_DIR/scan.sh"
|
|
38
|
+
fi
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
is_worm_running() {
|
|
42
|
+
if [[ -f "$PID_FILE" ]]; then
|
|
43
|
+
local pid
|
|
44
|
+
pid=$(cat "$PID_FILE" 2>/dev/null || echo "")
|
|
45
|
+
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
|
46
|
+
return 0
|
|
47
|
+
fi
|
|
48
|
+
fi
|
|
49
|
+
return 1
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
cmd_setup() {
|
|
53
|
+
bash "$SCRIPT_DIR/scan.sh"
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
cmd_on() {
|
|
57
|
+
ensure_config
|
|
58
|
+
|
|
59
|
+
if [[ -f "$CHANNEL_FLAG" ]] && is_worm_running; then
|
|
60
|
+
echo "🪱 The Voice already carries. Sandworm active."
|
|
61
|
+
return 0
|
|
62
|
+
fi
|
|
63
|
+
|
|
64
|
+
# Clean up stale state
|
|
65
|
+
rm -f "$CHANNEL_FLAG" "$PID_FILE" "$GOM_JABBAR_FILE"
|
|
66
|
+
|
|
67
|
+
mkdir -p "$CONFIG_DIR"
|
|
68
|
+
touch "$CHANNEL_FLAG"
|
|
69
|
+
|
|
70
|
+
# Source config for display before starting daemon
|
|
71
|
+
source "$CONFIG_FILE"
|
|
72
|
+
|
|
73
|
+
# Start sandworm daemon (relay.sh writes its own PID file)
|
|
74
|
+
nohup bash "$SCRIPT_DIR/relay.sh" >> "$CONFIG_DIR/worm.log" 2>&1 &
|
|
75
|
+
|
|
76
|
+
# Brief pause for relay.sh to write its PID file
|
|
77
|
+
sleep 1
|
|
78
|
+
|
|
79
|
+
echo "🪱 The Voice carries. Sandworm active."
|
|
80
|
+
echo "⚡ Transport: $INJECT_METHOD"
|
|
81
|
+
echo "🔮 Open your Telegram bot chat — you'll be asked to set a passphrase."
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
cmd_off() {
|
|
85
|
+
rm -f "$CHANNEL_FLAG"
|
|
86
|
+
|
|
87
|
+
if [[ -f "$PID_FILE" ]]; then
|
|
88
|
+
local pid
|
|
89
|
+
pid=$(cat "$PID_FILE" 2>/dev/null || echo "")
|
|
90
|
+
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
|
91
|
+
kill "$pid" 2>/dev/null || true
|
|
92
|
+
local i
|
|
93
|
+
for i in 1 2 3; do
|
|
94
|
+
kill -0 "$pid" 2>/dev/null || break
|
|
95
|
+
sleep 1
|
|
96
|
+
done
|
|
97
|
+
kill -0 "$pid" 2>/dev/null && kill -9 "$pid" 2>/dev/null || true
|
|
98
|
+
fi
|
|
99
|
+
rm -f "$PID_FILE"
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# Clean up Gom Jabbar state (passphrase hash is session-scoped)
|
|
103
|
+
rm -f "$GOM_JABBAR_FILE"
|
|
104
|
+
|
|
105
|
+
echo "🔇 Silence in the desert. Sandworm dormant."
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
cmd_status() {
|
|
109
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
110
|
+
echo "🏜️ Thumper Status Report"
|
|
111
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
112
|
+
|
|
113
|
+
if [[ ! -f "$CONFIG_FILE" ]]; then
|
|
114
|
+
echo "Sietch: ❌ No sietch vault found"
|
|
115
|
+
echo " Run /thumper setup to configure"
|
|
116
|
+
return 0
|
|
117
|
+
fi
|
|
118
|
+
|
|
119
|
+
source "$CONFIG_FILE"
|
|
120
|
+
echo "Sietch: ✅ $CONFIG_FILE"
|
|
121
|
+
echo "Worm path: $INJECT_METHOD"
|
|
122
|
+
[[ -n "${TMUX_SESSION:-}" ]] && echo "Session: $TMUX_SESSION"
|
|
123
|
+
echo "Setup: ${SETUP_DATE:-unknown}"
|
|
124
|
+
|
|
125
|
+
if [[ -f "$CHANNEL_FLAG" ]]; then
|
|
126
|
+
echo "Channel: 🟢 The Voice carries"
|
|
127
|
+
else
|
|
128
|
+
echo "Channel: 🔇 Silence in the desert"
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
if [[ -f "$PID_FILE" ]]; then
|
|
132
|
+
local pid
|
|
133
|
+
pid=$(cat "$PID_FILE" 2>/dev/null || echo "")
|
|
134
|
+
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
|
135
|
+
echo "Sandworm: 🪱 Running (PID $pid)"
|
|
136
|
+
else
|
|
137
|
+
echo "Sandworm: ⚠️ Stale PID ($pid) — worm not running"
|
|
138
|
+
fi
|
|
139
|
+
else
|
|
140
|
+
echo "Sandworm: ⬚ No PID file"
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Gom Jabbar state
|
|
144
|
+
if [[ -f "$GOM_JABBAR_FILE" ]]; then
|
|
145
|
+
source "$GOM_JABBAR_FILE"
|
|
146
|
+
case "${GJ_STATE:-}" in
|
|
147
|
+
AUTHENTICATED)
|
|
148
|
+
local now elapsed
|
|
149
|
+
now=$(date +%s)
|
|
150
|
+
elapsed=$(( now - ${GJ_LAST_ACTIVITY:-0} ))
|
|
151
|
+
local remaining=$(( 3600 - elapsed ))
|
|
152
|
+
if [[ $remaining -gt 0 ]]; then
|
|
153
|
+
echo "Gom Jabbar: ✅ Authenticated (${remaining}s until re-test)"
|
|
154
|
+
else
|
|
155
|
+
echo "Gom Jabbar: ⏳ Idle — re-test required"
|
|
156
|
+
fi
|
|
157
|
+
;;
|
|
158
|
+
PENDING) echo "Gom Jabbar: 🔮 Awaiting word of passage" ;;
|
|
159
|
+
CHALLENGE) echo "Gom Jabbar: 🔮 Re-test required" ;;
|
|
160
|
+
LOCKED) echo "Gom Jabbar: 💀 Locked (failed attempts)" ;;
|
|
161
|
+
*) echo "Gom Jabbar: ❓ Unknown state" ;;
|
|
162
|
+
esac
|
|
163
|
+
else
|
|
164
|
+
echo "Gom Jabbar: ⬚ Not initialized"
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
if [[ -f "$CONFIG_DIR/.last_thumper_id" ]]; then
|
|
168
|
+
echo "Last beat: $(cat "$CONFIG_DIR/.last_thumper_id" 2>/dev/null || echo "unknown")"
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
if [[ -f "$CONFIG_DIR/worm.log" ]]; then
|
|
172
|
+
local log_size
|
|
173
|
+
log_size=$(stat -f%z "$CONFIG_DIR/worm.log" 2>/dev/null || stat -c%s "$CONFIG_DIR/worm.log" 2>/dev/null || echo "?")
|
|
174
|
+
if [[ "$log_size" =~ ^[0-9]+$ ]] && [[ "$log_size" -gt 1048576 ]]; then
|
|
175
|
+
echo "Log: ${log_size} bytes (⚠️ >1MB — rotates on next worm start)"
|
|
176
|
+
elif [[ "$log_size" =~ ^[0-9]+$ ]]; then
|
|
177
|
+
echo "Log: ${log_size} bytes"
|
|
178
|
+
fi
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
case "${1:-}" in
|
|
185
|
+
setup) cmd_setup ;;
|
|
186
|
+
on) cmd_on ;;
|
|
187
|
+
off) cmd_off ;;
|
|
188
|
+
status) cmd_status ;;
|
|
189
|
+
*) usage ;;
|
|
190
|
+
esac
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# water-rings.sh — The Water Rings — Chani's stop hook
|
|
3
|
+
# Sends Claude Code's response to Telegram when a turn completes.
|
|
4
|
+
# "Among the Fremen, water rings record the dead — and the debts of the living."
|
|
5
|
+
#
|
|
6
|
+
# The Stop hook receives JSON on stdin with `last_assistant_message` — the
|
|
7
|
+
# actual response text. No need to read transcript files.
|
|
8
|
+
#
|
|
9
|
+
# Note: -e (errexit) intentionally omitted — this hook must never fail.
|
|
10
|
+
set -uo pipefail
|
|
11
|
+
|
|
12
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
13
|
+
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
14
|
+
CONFIG_DIR="$PROJECT_ROOT/.voidforge/thumper"
|
|
15
|
+
CONFIG_FILE="$CONFIG_DIR/sietch.env"
|
|
16
|
+
CHANNEL_FLAG="$CONFIG_DIR/.thumper.active"
|
|
17
|
+
|
|
18
|
+
# Silent exit if not configured or channel closed
|
|
19
|
+
[ -f "$CONFIG_FILE" ] || exit 0
|
|
20
|
+
[ -f "$CHANNEL_FLAG" ] || exit 0
|
|
21
|
+
|
|
22
|
+
source "$CONFIG_FILE"
|
|
23
|
+
[ "${SETUP_COMPLETE:-}" = "true" ] || exit 0
|
|
24
|
+
|
|
25
|
+
# Read stop hook metadata from stdin
|
|
26
|
+
HOOK_INPUT=""
|
|
27
|
+
if ! [ -t 0 ]; then
|
|
28
|
+
HOOK_INPUT=$(perl -e 'alarm 5; local $/; print <STDIN>' 2>/dev/null || head -c 65536 2>/dev/null || echo "")
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Extract last_assistant_message directly from hook metadata
|
|
32
|
+
MESSAGE=""
|
|
33
|
+
if [ -n "$HOOK_INPUT" ]; then
|
|
34
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
35
|
+
MESSAGE=$(echo "$HOOK_INPUT" | python3 -c "
|
|
36
|
+
import sys, json
|
|
37
|
+
try:
|
|
38
|
+
data = json.load(sys.stdin)
|
|
39
|
+
text = data.get('last_assistant_message', '')
|
|
40
|
+
if text:
|
|
41
|
+
if len(text) > 3600:
|
|
42
|
+
text = text[:3600] + '\n\n[...truncated]'
|
|
43
|
+
print(text)
|
|
44
|
+
except Exception:
|
|
45
|
+
pass
|
|
46
|
+
" 2>/dev/null)
|
|
47
|
+
elif command -v jq >/dev/null 2>&1; then
|
|
48
|
+
MESSAGE=$(echo "$HOOK_INPUT" | jq -r '.last_assistant_message // empty' 2>/dev/null | head -c 3600)
|
|
49
|
+
fi
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
if [ -n "$MESSAGE" ]; then
|
|
53
|
+
NOTIFICATION="$(printf '✅ Task complete\n\n%s\n\n─────────────────\n📡 Reply to continue' "$MESSAGE")"
|
|
54
|
+
else
|
|
55
|
+
NOTIFICATION="$(printf '✅ Claude Code finished — no summary available.\n\n─────────────────\n📡 Reply to continue')"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
_send_notification() {
|
|
59
|
+
local api_base="https://api.telegram.org/bot${BOT_TOKEN}"
|
|
60
|
+
# Try with Markdown first, fall back to plain text
|
|
61
|
+
curl -s --connect-timeout 5 --max-time 10 \
|
|
62
|
+
-X POST \
|
|
63
|
+
-d chat_id="$CHAT_ID" \
|
|
64
|
+
--data-urlencode text="$NOTIFICATION" \
|
|
65
|
+
-d parse_mode="Markdown" \
|
|
66
|
+
"${api_base}/sendMessage" >/dev/null 2>&1 || \
|
|
67
|
+
curl -s --connect-timeout 5 --max-time 10 \
|
|
68
|
+
-X POST \
|
|
69
|
+
-d chat_id="$CHAT_ID" \
|
|
70
|
+
--data-urlencode text="$NOTIFICATION" \
|
|
71
|
+
"${api_base}/sendMessage" >/dev/null 2>&1 || true
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_send_notification &
|
|
75
|
+
|
|
76
|
+
exit 0
|
|
@@ -31,7 +31,7 @@ async function getPackageVersion() {
|
|
|
31
31
|
try {
|
|
32
32
|
const raw = await readFile(pkgPath, 'utf-8');
|
|
33
33
|
const pkg = JSON.parse(raw);
|
|
34
|
-
if (pkg.name === 'voidforge' && pkg.version)
|
|
34
|
+
if ((pkg.name === 'thevoidforge' || pkg.name === 'voidforge') && pkg.version)
|
|
35
35
|
return pkg.version;
|
|
36
36
|
}
|
|
37
37
|
catch {
|