blamcode 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- blamcode/__init__.py +9 -0
- blamcode/cli.py +122 -0
- blamcode/layer/README.md +43 -0
- blamcode/layer/config/agent/build.md +118 -0
- blamcode/layer/config/agent/debugger.md +32 -0
- blamcode/layer/config/agent/designer.md +31 -0
- blamcode/layer/config/agent/vision.md +26 -0
- blamcode/layer/config/agent/writer.md +28 -0
- blamcode/layer/config/command/ask.md +41 -0
- blamcode/layer/config/command/blam.md +24 -0
- blamcode/layer/config/command/explore.md +14 -0
- blamcode/layer/config/command/fix.md +14 -0
- blamcode/layer/config/command/open.md +18 -0
- blamcode/layer/config/command/review.md +14 -0
- blamcode/layer/config/opencode.json +53 -0
- blamcode/layer/config/themes/bangladeshi.json +67 -0
- blamcode/layer/config/themes/blamcode.json +217 -0
- blamcode/layer/config/tui.json +4 -0
- blamcode/layer/install.sh +752 -0
- blamcode/layer/scripts/__pycache__/patch-brand.cpython-312.pyc +0 -0
- blamcode/layer/scripts/blamcode +518 -0
- blamcode/layer/scripts/blamcode-browser +315 -0
- blamcode/layer/scripts/blamcode-menu +140 -0
- blamcode/layer/scripts/blamcode-uninstall +71 -0
- blamcode/layer/scripts/blamcode-vision +542 -0
- blamcode/layer/scripts/oc-settings.sh +138 -0
- blamcode/layer/scripts/patch-brand.py +267 -0
- blamcode/layer/skills/android-app/SKILL.md +53 -0
- blamcode/layer/skills/api-integration/SKILL.md +49 -0
- blamcode/layer/skills/bash-cli-expert/SKILL.md +48 -0
- blamcode/layer/skills/bot-development/SKILL.md +53 -0
- blamcode/layer/skills/clean-code-performance/SKILL.md +45 -0
- blamcode/layer/skills/database/SKILL.md +56 -0
- blamcode/layer/skills/debugging-fixes/SKILL.md +45 -0
- blamcode/layer/skills/deploy-hosting/SKILL.md +38 -0
- blamcode/layer/skills/docker/SKILL.md +74 -0
- blamcode/layer/skills/firebase-supabase/SKILL.md +61 -0
- blamcode/layer/skills/git-workflow/SKILL.md +63 -0
- blamcode/layer/skills/lets-scroll/SKILL.md +877 -0
- blamcode/layer/skills/lets-scroll/references/index-template.html +73 -0
- blamcode/layer/skills/lets-scroll/references/knockout.py +89 -0
- blamcode/layer/skills/lets-scroll/references/pipeline.md +312 -0
- blamcode/layer/skills/lets-scroll/references/prompts.md +194 -0
- blamcode/layer/skills/lets-scroll/references/scrub-engine.js +448 -0
- blamcode/layer/skills/project-structure/SKILL.md +74 -0
- blamcode/layer/skills/python-automation/SKILL.md +52 -0
- blamcode/layer/skills/react-next-best-practices/SKILL.md +54 -0
- blamcode/layer/skills/security-review/SKILL.md +48 -0
- blamcode/layer/skills/seo-basics/SKILL.md +44 -0
- blamcode/layer/skills/testing/SKILL.md +58 -0
- blamcode/layer/skills/ui-ux-responsive/SKILL.md +53 -0
- blamcode/layer/skills/website-builder/SKILL.md +47 -0
- blamcode-0.4.0.dist-info/METADATA +62 -0
- blamcode-0.4.0.dist-info/RECORD +58 -0
- blamcode-0.4.0.dist-info/WHEEL +5 -0
- blamcode-0.4.0.dist-info/entry_points.txt +2 -0
- blamcode-0.4.0.dist-info/licenses/LICENSE +21 -0
- blamcode-0.4.0.dist-info/top_level.txt +1 -0
|
Binary file
|
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
#!/data/data/com.termux/files/usr/bin/sh
|
|
2
|
+
# blamcode — wrapper that disables Android bionic TBI heap pointer tagging
|
|
3
|
+
#
|
|
4
|
+
# The real blamcode binary is blamcode.bin; this script LD_PRELOAD's libtagfix.so
|
|
5
|
+
# (a constructor that calls mallopt to turn off heap tagging) before exec'ing it.
|
|
6
|
+
# Without this, Bun/JSC's NaN-boxing clears the 0xB4 top-byte tag on heap
|
|
7
|
+
# pointers, causing bionic to SIGABRT on free(): "Pointer tag ... was truncated".
|
|
8
|
+
#
|
|
9
|
+
# Permission system: none — everything is allowed by config (beginner CLI
|
|
10
|
+
# design, no prompts). The wrapper does not enforce any permissions.
|
|
11
|
+
#
|
|
12
|
+
# Named sessions: blamcode session <name> — opens <base>/blamcode/<name>/ and
|
|
13
|
+
# resumes the previous chat history with --continue. blamcode session = list.
|
|
14
|
+
#
|
|
15
|
+
# Path resolution order (supports both standalone zip and installed package):
|
|
16
|
+
# zip: wrapper, blamcode.bin, libtagfix.so all live in the same dir
|
|
17
|
+
# installed: bin/blamcode, libexec/opencode/blamcode.bin, lib/libtagfix.so
|
|
18
|
+
# glibc Linux (Ubuntu proot etc.): ~/.local/bin/blamcode, ~/.local/libexec/blamcode/
|
|
19
|
+
|
|
20
|
+
set -e
|
|
21
|
+
|
|
22
|
+
dir="$(cd "$(dirname "$0")" && pwd)"
|
|
23
|
+
export ANDROID_ROOT="${ANDROID_ROOT:-/system}"
|
|
24
|
+
export TERMUX_VERSION="${TERMUX_VERSION:-blamcode-termux}"
|
|
25
|
+
# always have a sane HOME — some launch paths (widgets, menu, shared
|
|
26
|
+
# sessions) start us with an empty environment, and every ~/.config
|
|
27
|
+
# lookup in the TUI's child processes (vision key, yolo state) breaks
|
|
28
|
+
# when HOME is unset
|
|
29
|
+
: "${HOME:=/data/data/com.termux/files/home}"
|
|
30
|
+
export HOME
|
|
31
|
+
export TMPDIR="${BLAMCODE_TMPDIR:-${HOME}/tmp}"
|
|
32
|
+
export TEMP="$TMPDIR"
|
|
33
|
+
export TMP="$TMPDIR"
|
|
34
|
+
export OPENCODE_DISABLE_TUI_AUDIO="${OPENCODE_DISABLE_TUI_AUDIO:-1}"
|
|
35
|
+
mkdir -p "$TMPDIR" 2>/dev/null || true
|
|
36
|
+
|
|
37
|
+
# ---- blamcode update: reruns the delta installer ----
|
|
38
|
+
# (if the core is unchanged it is 0 MB — only the BLAMCODE layer refreshes)
|
|
39
|
+
if [ "${1:-}" = "update" ] || [ "${1:-}" = "upgrade" ]; then
|
|
40
|
+
shift
|
|
41
|
+
echo "blamcode: checking for updates (delta — 0 MB if the core is unchanged)"
|
|
42
|
+
UPDATER_URL="https://raw.githubusercontent.com/zyvo9/blamcode/main/install.sh"
|
|
43
|
+
if curl -fsSL --retry 3 --connect-timeout 15 --max-time 120 -o "$TMPDIR/blamcode-update.sh" "$UPDATER_URL" 2>/dev/null; then
|
|
44
|
+
exec sh "$TMPDIR/blamcode-update.sh" "$@"
|
|
45
|
+
fi
|
|
46
|
+
echo "blamcode: could not fetch the update script — check your internet" >&2
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
# ---- blamcode uninstall: removes BLAMCODE cleanly (needs the repo script) ----
|
|
51
|
+
if [ "${1:-}" = "uninstall" ]; then
|
|
52
|
+
shift
|
|
53
|
+
UNINSTALLER_URL="https://raw.githubusercontent.com/zyvo9/blamcode/main/scripts/blamcode-uninstall"
|
|
54
|
+
UNINSTALLER=""
|
|
55
|
+
# prefer a local copy (installed layout), else the current repo
|
|
56
|
+
for c in "$dir/../share/blamcode/scripts/blamcode-uninstall" "$dir/blamcode-uninstall" "$TMPDIR/blamcode-uninstall"; do
|
|
57
|
+
if [ -f "$c" ]; then UNINSTALLER="$c"; break; fi
|
|
58
|
+
done
|
|
59
|
+
if [ -z "$UNINSTALLER" ]; then
|
|
60
|
+
if curl -fsSL --retry 2 --connect-timeout 15 --max-time 60 -o "$TMPDIR/blamcode-uninstall" "$UNINSTALLER_URL" 2>/dev/null; then
|
|
61
|
+
UNINSTALLER="$TMPDIR/blamcode-uninstall"
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
if [ -n "$UNINSTALLER" ]; then
|
|
65
|
+
exec sh "$UNINSTALLER" "$@"
|
|
66
|
+
fi
|
|
67
|
+
echo "blamcode: could not fetch the uninstaller — check your internet" >&2
|
|
68
|
+
exit 1
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# ---- blamcode preview / blamcode share: local server + public HTTPS tunnel + auto-open ----
|
|
72
|
+
# Browsers block scripts/fetch/CSS on file:// URLs, so serving over HTTP/HTTPS fixes it.
|
|
73
|
+
# Usage:
|
|
74
|
+
# blamcode preview [folder] -> Local server (http://localhost:8080) + auto-open on device
|
|
75
|
+
# blamcode preview --share [folder] -> Local server + Instant live Public HTTPS link (Pinggy/LHR)
|
|
76
|
+
# blamcode share [folder] -> Same as preview --share
|
|
77
|
+
if [ "${1:-}" = "preview" ] || [ "${1:-}" = "open" ] || [ "${1:-}" = "share" ] || [ "${1:-}" = "public" ]; then
|
|
78
|
+
CMD_TYPE="$1"
|
|
79
|
+
shift
|
|
80
|
+
SHARE_MODE=1
|
|
81
|
+
PREVIEW_DIR=""
|
|
82
|
+
while [ "$#" -gt 0 ]; do
|
|
83
|
+
case "$1" in
|
|
84
|
+
--local|-l|--no-share) SHARE_MODE=0; shift ;;
|
|
85
|
+
--share|-s|--public|-p) SHARE_MODE=1; shift ;;
|
|
86
|
+
*) [ -z "$PREVIEW_DIR" ] && PREVIEW_DIR="$1"; shift ;;
|
|
87
|
+
esac
|
|
88
|
+
done
|
|
89
|
+
PREVIEW_DIR="${PREVIEW_DIR:-$PWD}"
|
|
90
|
+
[ -d "$PREVIEW_DIR" ] || { echo "blamcode: no such folder: $PREVIEW_DIR" >&2; exit 1; }
|
|
91
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
92
|
+
echo "blamcode: python3 is required for preview — install it (pkg install python)" >&2
|
|
93
|
+
exit 1
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
exec python3 - "$PREVIEW_DIR" "$SHARE_MODE" "$TMPDIR" "${BLAMCODE_PREVIEW_PORT:-8080}" <<'PYPREV'
|
|
97
|
+
import os, sys, socket, subprocess, time, re
|
|
98
|
+
|
|
99
|
+
preview_dir = os.path.abspath(sys.argv[1])
|
|
100
|
+
share_mode = (sys.argv[2] == "1")
|
|
101
|
+
tmpdir = sys.argv[3]
|
|
102
|
+
default_port = int(sys.argv[4]) if sys.argv[4].isdigit() else 8080
|
|
103
|
+
|
|
104
|
+
def is_port_in_use(port):
|
|
105
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
106
|
+
s.settimeout(0.2)
|
|
107
|
+
return s.connect_ex(('127.0.0.1', port)) == 0
|
|
108
|
+
|
|
109
|
+
def find_free_port(start_port):
|
|
110
|
+
for p in range(start_port, start_port + 20):
|
|
111
|
+
if not is_port_in_use(p):
|
|
112
|
+
return p
|
|
113
|
+
return start_port
|
|
114
|
+
|
|
115
|
+
def shutil_which(cmd):
|
|
116
|
+
if cmd.startswith("/") and os.path.isfile(cmd) and os.access(cmd, os.X_OK):
|
|
117
|
+
return True
|
|
118
|
+
for path in os.environ.get("PATH", "").split(os.pathsep):
|
|
119
|
+
exe = os.path.join(path, cmd)
|
|
120
|
+
if os.path.isfile(exe) and os.access(exe, os.X_OK):
|
|
121
|
+
return True
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
def open_browser(url):
|
|
125
|
+
open_cmds = [
|
|
126
|
+
["termux-open-url", url],
|
|
127
|
+
["am", "start", "-a", "android.intent.action.VIEW", "-d", url],
|
|
128
|
+
["/system/bin/am", "start", "-a", "android.intent.action.VIEW", "-d", url],
|
|
129
|
+
["termux-open", url],
|
|
130
|
+
["xdg-open", url]
|
|
131
|
+
]
|
|
132
|
+
for cmd in open_cmds:
|
|
133
|
+
if shutil_which(cmd[0]):
|
|
134
|
+
try:
|
|
135
|
+
subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
136
|
+
return True
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
port = find_free_port(default_port)
|
|
142
|
+
local_url = f"http://localhost:{port}/"
|
|
143
|
+
|
|
144
|
+
# Start local HTTP server in background
|
|
145
|
+
log_file = os.path.join(tmpdir, f"blamcode-preview-{port}.log")
|
|
146
|
+
log_fh = open(log_file, "w")
|
|
147
|
+
server_proc = subprocess.Popen(
|
|
148
|
+
[sys.executable, "-m", "http.server", str(port), "--bind", "127.0.0.1"],
|
|
149
|
+
cwd=preview_dir,
|
|
150
|
+
stdout=log_fh,
|
|
151
|
+
stderr=log_fh,
|
|
152
|
+
start_new_session=True
|
|
153
|
+
)
|
|
154
|
+
time.sleep(0.3)
|
|
155
|
+
|
|
156
|
+
public_url = None
|
|
157
|
+
if share_mode and shutil_which("ssh"):
|
|
158
|
+
tunnel_cmds = [
|
|
159
|
+
["ssh", "-p", "443", "-R0:localhost:" + str(port), "-o", "StrictHostKeyChecking=no", "-o", "ServerAliveInterval=30", "a.pinggy.io"],
|
|
160
|
+
["ssh", "-R", "80:localhost:" + str(port), "-o", "StrictHostKeyChecking=no", "nokey@localhost.run"]
|
|
161
|
+
]
|
|
162
|
+
for tcmd in tunnel_cmds:
|
|
163
|
+
try:
|
|
164
|
+
tproc = subprocess.Popen(
|
|
165
|
+
tcmd,
|
|
166
|
+
stdout=subprocess.PIPE,
|
|
167
|
+
stderr=subprocess.STDOUT,
|
|
168
|
+
text=True,
|
|
169
|
+
bufsize=1,
|
|
170
|
+
start_new_session=True
|
|
171
|
+
)
|
|
172
|
+
start_t = time.time()
|
|
173
|
+
while time.time() - start_t < 4.0:
|
|
174
|
+
line = tproc.stdout.readline()
|
|
175
|
+
if not line and tproc.poll() is not None:
|
|
176
|
+
break
|
|
177
|
+
m = re.search(r'https://[a-zA-Z0-9\-\.]+\.(?:pinggy\.link|lhr\.life|serveo\.net)[^\s]*', line)
|
|
178
|
+
if m:
|
|
179
|
+
public_url = m.group(0).rstrip('.')
|
|
180
|
+
break
|
|
181
|
+
if public_url:
|
|
182
|
+
break
|
|
183
|
+
except Exception:
|
|
184
|
+
pass
|
|
185
|
+
|
|
186
|
+
target_url = public_url if public_url else local_url
|
|
187
|
+
open_browser(target_url)
|
|
188
|
+
|
|
189
|
+
print("\n" + "=" * 50)
|
|
190
|
+
print(" 🚀 BLAMCODE Web Preview Ready!")
|
|
191
|
+
print("=" * 50)
|
|
192
|
+
if public_url:
|
|
193
|
+
print(f" 🌍 Public Live URL : {public_url}")
|
|
194
|
+
print(f" (Share this live HTTPS link with anyone!)")
|
|
195
|
+
print(f" 🏠 Local URL : {local_url}")
|
|
196
|
+
print(f" 📂 Folder : {preview_dir}")
|
|
197
|
+
print(f" 📱 Opened automatically in your device browser.")
|
|
198
|
+
print("=" * 50 + "\n")
|
|
199
|
+
|
|
200
|
+
sys.exit(0)
|
|
201
|
+
PYPREV
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
# ---- workspace and session resolver ----
|
|
205
|
+
# Structure:
|
|
206
|
+
# /storage/emulated/0/blamcode/
|
|
207
|
+
# ├── <project1>/ (blamcode <project1> or blamcode session <project1>)
|
|
208
|
+
# ├── <project2>/ (blamcode <project2>)
|
|
209
|
+
# └── default/ (blamcode)
|
|
210
|
+
#
|
|
211
|
+
# Every project works strictly inside its own dedicated subfolder
|
|
212
|
+
# so files from different projects never mix or conflict!
|
|
213
|
+
|
|
214
|
+
WS_BASE=""
|
|
215
|
+
if [ -d /storage/emulated/0 ]; then WS_BASE=/storage/emulated/0
|
|
216
|
+
elif [ -d /sdcard ]; then WS_BASE=/sdcard
|
|
217
|
+
fi
|
|
218
|
+
# ---- zyvo -> blamcode migration (one-time) ----
|
|
219
|
+
# old installs kept state + projects under the zyvo name — carry them over
|
|
220
|
+
if [ -d "$HOME/.config/zyvo" ] && [ ! -d "$HOME/.config/blamcode" ]; then
|
|
221
|
+
mv "$HOME/.config/zyvo" "$HOME/.config/blamcode" 2>/dev/null || true
|
|
222
|
+
fi
|
|
223
|
+
rm -f "$HOME/.config/opencode/command/zyvo.md" 2>/dev/null || true
|
|
224
|
+
if [ -n "${WS_BASE:-}" ] && [ -d "$WS_BASE/zyvo" ] && [ ! -d "$WS_BASE/blamcode" ]; then
|
|
225
|
+
mv "$WS_BASE/zyvo" "$WS_BASE/blamcode" 2>/dev/null || true
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
SDIR_ROOT="${WS_BASE:-$HOME}/blamcode"
|
|
229
|
+
mkdir -p "$SDIR_ROOT" 2>/dev/null || true
|
|
230
|
+
|
|
231
|
+
SESSION_MODE=0
|
|
232
|
+
SESSION_NAME=""
|
|
233
|
+
|
|
234
|
+
while [ "$#" -gt 0 ]; do
|
|
235
|
+
case "$1" in
|
|
236
|
+
--ask)
|
|
237
|
+
# force ASK mode on for this launch (and persist it)
|
|
238
|
+
mkdir -p "$HOME/.config/blamcode" 2>/dev/null || true
|
|
239
|
+
echo on > "$HOME/.config/blamcode/ask.mode" 2>/dev/null || true
|
|
240
|
+
shift; continue ;;
|
|
241
|
+
--yolo|-y|--safe)
|
|
242
|
+
# free/default mode — ASK off (yolo = the usual convention:
|
|
243
|
+
# no questions, full auto)
|
|
244
|
+
mkdir -p "$HOME/.config/blamcode" 2>/dev/null || true
|
|
245
|
+
echo off > "$HOME/.config/blamcode/ask.mode" 2>/dev/null || true
|
|
246
|
+
shift; continue ;;
|
|
247
|
+
session|sessions|ls|list)
|
|
248
|
+
shift
|
|
249
|
+
if [ "$#" -gt 0 ] && [ "$1" != "ls" ] && [ "$1" != "list" ]; then
|
|
250
|
+
SESSION_NAME="$1"; shift; SESSION_MODE=1
|
|
251
|
+
else
|
|
252
|
+
echo ""
|
|
253
|
+
echo "📁 BLAMCODE Projects ($SDIR_ROOT):"
|
|
254
|
+
if [ -d "$SDIR_ROOT" ]; then
|
|
255
|
+
count=0
|
|
256
|
+
for d in "$SDIR_ROOT"/*; do
|
|
257
|
+
if [ -d "$d" ]; then
|
|
258
|
+
b="$(basename "$d")"
|
|
259
|
+
[ "$b" != ".opencode" ] && echo " 🔹 $b" && count=$((count+1))
|
|
260
|
+
fi
|
|
261
|
+
done
|
|
262
|
+
[ $count -eq 0 ] && echo " (no project folders yet)"
|
|
263
|
+
fi
|
|
264
|
+
echo ""
|
|
265
|
+
echo "Open or create: blamcode <project-name>"
|
|
266
|
+
echo "Example: blamcode coffee-shop"
|
|
267
|
+
echo ""
|
|
268
|
+
exit 0
|
|
269
|
+
fi
|
|
270
|
+
break ;;
|
|
271
|
+
-*)
|
|
272
|
+
# Flag for opencode binary, stop parsing
|
|
273
|
+
break ;;
|
|
274
|
+
*)
|
|
275
|
+
if [ -d "$1" ]; then
|
|
276
|
+
# User passed an existing path (e.g. blamcode /path/to/folder)
|
|
277
|
+
cd "$1"
|
|
278
|
+
export OPENCODE_DEFAULT_DIR="$PWD"
|
|
279
|
+
shift
|
|
280
|
+
break
|
|
281
|
+
else
|
|
282
|
+
# User passed a project name directly (e.g. blamcode coffee-shop)
|
|
283
|
+
SESSION_NAME="$1"; shift; SESSION_MODE=1
|
|
284
|
+
break
|
|
285
|
+
fi
|
|
286
|
+
;;
|
|
287
|
+
esac
|
|
288
|
+
done
|
|
289
|
+
|
|
290
|
+
if [ -z "$SESSION_NAME" ]; then
|
|
291
|
+
case "$PWD" in
|
|
292
|
+
"$SDIR_ROOT"/*)
|
|
293
|
+
# Already inside a project subfolder
|
|
294
|
+
export OPENCODE_DEFAULT_DIR="$PWD"
|
|
295
|
+
;;
|
|
296
|
+
*)
|
|
297
|
+
if [ "$PWD" = "$HOME" ] || [ "$PWD" = "$SDIR_ROOT" ]; then
|
|
298
|
+
# Default dedicated folder so root is never cluttered
|
|
299
|
+
SDIR="$SDIR_ROOT/default"
|
|
300
|
+
mkdir -p "$SDIR" 2>/dev/null || true
|
|
301
|
+
cd "$SDIR"
|
|
302
|
+
export OPENCODE_DEFAULT_DIR="$SDIR"
|
|
303
|
+
else
|
|
304
|
+
export OPENCODE_DEFAULT_DIR="$PWD"
|
|
305
|
+
fi
|
|
306
|
+
;;
|
|
307
|
+
esac
|
|
308
|
+
else
|
|
309
|
+
case "$SESSION_NAME" in
|
|
310
|
+
""|"."|".."|*[!A-Za-z0-9._-]*)
|
|
311
|
+
echo "blamcode: project names may only contain a-z 0-9 . _ - (no spaces/symbols)" >&2
|
|
312
|
+
exit 1 ;;
|
|
313
|
+
esac
|
|
314
|
+
SDIR="$SDIR_ROOT/$SESSION_NAME"
|
|
315
|
+
mkdir -p "$SDIR" 2>/dev/null || true
|
|
316
|
+
if [ -d "$SDIR" ]; then
|
|
317
|
+
cd "$SDIR"
|
|
318
|
+
export OPENCODE_DEFAULT_DIR="$SDIR"
|
|
319
|
+
SESSION_MODE=1
|
|
320
|
+
else
|
|
321
|
+
echo "blamcode: could not create project folder ($SDIR)" >&2
|
|
322
|
+
exit 1
|
|
323
|
+
fi
|
|
324
|
+
fi
|
|
325
|
+
|
|
326
|
+
# ---- ask mode: persistent state + live /ask label ------------------------
|
|
327
|
+
# State: ~/.config/blamcode/ask.mode (on|off, default off). On every launch:
|
|
328
|
+
# 1. rewrite the /ask command description so the picker always shows
|
|
329
|
+
# the actionable label — "/ask off ..." when ON, "/ask on ..." when OFF
|
|
330
|
+
# 2. keep ~/.config/blamcode/ask-instructions.md in sync (ON = strict
|
|
331
|
+
# ask-before-every-step + stay-on-task instructions, OFF = empty).
|
|
332
|
+
# It is wired into the global opencode.json "instructions" list
|
|
333
|
+
# below, so ON applies to every new session.
|
|
334
|
+
# 3. migrate old yolo-mode installs (rename /yolo -> /ask)
|
|
335
|
+
BLAMCODE_CFG="$HOME/.config/blamcode"
|
|
336
|
+
mkdir -p "$BLAMCODE_CFG" 2>/dev/null || true
|
|
337
|
+
if [ ! -f "$BLAMCODE_CFG/ask.mode" ] && [ -f "$BLAMCODE_CFG/yolo.mode" ]; then
|
|
338
|
+
mv "$BLAMCODE_CFG/yolo.mode" "$BLAMCODE_CFG/ask.mode" 2>/dev/null || true
|
|
339
|
+
fi
|
|
340
|
+
rm -f "$HOME/.config/opencode/command/yolo.md" "$BLAMCODE_CFG/yolo-instructions.md" 2>/dev/null || true
|
|
341
|
+
BLAMCODE_ASK="$(cat "$BLAMCODE_CFG/ask.mode" 2>/dev/null || true)"
|
|
342
|
+
case "$BLAMCODE_ASK" in on|off) ;; *) BLAMCODE_ASK="off" ;; esac
|
|
343
|
+
export BLAMCODE_ASK
|
|
344
|
+
ACMD="$HOME/.config/opencode/command/ask.md"
|
|
345
|
+
if [ -f "$ACMD" ]; then
|
|
346
|
+
if [ "$BLAMCODE_ASK" = "on" ]; then
|
|
347
|
+
ADESC="/ask off — ASK ON: AI asks before every step, stays on your task"
|
|
348
|
+
else
|
|
349
|
+
ADESC="/ask on — ASK off: AI works freely (no interruptions)"
|
|
350
|
+
fi
|
|
351
|
+
sed -i "s|^description:.*|description: $ADESC|" "$ACMD" 2>/dev/null || true
|
|
352
|
+
fi
|
|
353
|
+
if [ "$BLAMCODE_ASK" = "on" ]; then
|
|
354
|
+
[ -s "$BLAMCODE_CFG/ask-instructions.md" ] || printf 'ASK MODE ON — MANDATORY persistent BLAMCODE instruction.\n1. STAY ON TASK — work ONLY on the user\x27s current request. Never drift to unrelated files, features, or fixes mid-work. Notice something unrelated? List it as an option for LATER — never touch it now.\n2. ASK BEFORE EVERY major step (file edit, tech/framework choice, design decision, fix strategy, project structure): present 2-4 options with a one-line reason each and WAIT for the user to pick. Keep asking at every step — that is the whole point of this mode.\n3. ONE task at a time — finish it or reach a decision point before anything else.\n4. Task DONE -> stop and report. Never start new work on your own.\n5. Unsure what the user wants? ASK — never guess and build.\n' > "$BLAMCODE_CFG/ask-instructions.md" 2>/dev/null || true
|
|
355
|
+
else
|
|
356
|
+
: > "$BLAMCODE_CFG/ask-instructions.md" 2>/dev/null || true
|
|
357
|
+
fi
|
|
358
|
+
|
|
359
|
+
# ---- ghost-config self-heal ----
|
|
360
|
+
# Old BLAMCODE versions wrote model ids in a format opencode rejects
|
|
361
|
+
# ("deepseek-v4-flash-free" bare → popup "…/ is not valid"). Those keys
|
|
362
|
+
# can survive in global config, the workspace project dir, or project
|
|
363
|
+
# .opencode/ agent files — strip them on every launch so the picker
|
|
364
|
+
# falls back to the stock default. Fast, silent, safe.
|
|
365
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
366
|
+
python3 - "$HOME" "${WS_BASE:-$HOME}" >/dev/null 2>&1 <<'PYGC' || true
|
|
367
|
+
import json, os, sys
|
|
368
|
+
home, base = sys.argv[1], sys.argv[2]
|
|
369
|
+
roots = [
|
|
370
|
+
os.path.join(home, ".config", "opencode"),
|
|
371
|
+
os.path.join(base, "blamcode"),
|
|
372
|
+
os.path.join(base, ".opencode"),
|
|
373
|
+
]
|
|
374
|
+
def clean_json(p):
|
|
375
|
+
try:
|
|
376
|
+
d = json.load(open(p))
|
|
377
|
+
except Exception:
|
|
378
|
+
return
|
|
379
|
+
if not isinstance(d, dict):
|
|
380
|
+
return
|
|
381
|
+
ch = False
|
|
382
|
+
for k in ("model", "small_model"):
|
|
383
|
+
if k in d:
|
|
384
|
+
del d[k]; ch = True
|
|
385
|
+
a = d.get("agent")
|
|
386
|
+
if isinstance(a, dict) and isinstance(a.get("build"), dict) and "model" in a["build"]:
|
|
387
|
+
del a["build"]["model"]; ch = True
|
|
388
|
+
if ch:
|
|
389
|
+
try:
|
|
390
|
+
json.dump(d, open(p, "w"), indent=2)
|
|
391
|
+
except Exception:
|
|
392
|
+
pass
|
|
393
|
+
def clean_md(p):
|
|
394
|
+
try:
|
|
395
|
+
lines = open(p).readlines()
|
|
396
|
+
except Exception:
|
|
397
|
+
return
|
|
398
|
+
out = [l for l in lines if not l.startswith("model:")]
|
|
399
|
+
if len(out) != len(lines):
|
|
400
|
+
try:
|
|
401
|
+
open(p, "w").writelines(out)
|
|
402
|
+
except Exception:
|
|
403
|
+
pass
|
|
404
|
+
for root in roots:
|
|
405
|
+
if not os.path.isdir(root):
|
|
406
|
+
continue
|
|
407
|
+
# stale /model command files are removed — the built-in /models picker
|
|
408
|
+
# is the only model command that should exist
|
|
409
|
+
cdir = os.path.join(root, "command")
|
|
410
|
+
if os.path.isdir(cdir):
|
|
411
|
+
for fn in ("model.md", "model.json", "model.jsonc"):
|
|
412
|
+
p = os.path.join(cdir, fn)
|
|
413
|
+
if os.path.exists(p):
|
|
414
|
+
try:
|
|
415
|
+
os.remove(p)
|
|
416
|
+
except Exception:
|
|
417
|
+
pass
|
|
418
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
419
|
+
dirnames[:] = [d for d in dirnames
|
|
420
|
+
if d not in ("node_modules", ".git", "skills", "themes")]
|
|
421
|
+
if dirpath.count(os.sep) - root.count(os.sep) > 3:
|
|
422
|
+
dirnames[:] = []
|
|
423
|
+
continue
|
|
424
|
+
for fn in filenames:
|
|
425
|
+
p = os.path.join(dirpath, fn)
|
|
426
|
+
if fn in ("opencode.json", "opencode.jsonc"):
|
|
427
|
+
clean_json(p)
|
|
428
|
+
elif fn == "config.json" and ".opencode" in dirpath:
|
|
429
|
+
clean_json(p)
|
|
430
|
+
elif fn.endswith(".md") and (".opencode" in dirpath or
|
|
431
|
+
dirpath.endswith(os.sep + "agent") or dirpath.endswith("/agent")):
|
|
432
|
+
clean_md(p)
|
|
433
|
+
|
|
434
|
+
# wire the persistent ask-mode instructions into the global config
|
|
435
|
+
# (idempotent — opencode appends every "instructions" file to the system
|
|
436
|
+
# prompt, so ASK ON applies to all sessions; the file is empty when OFF)
|
|
437
|
+
try:
|
|
438
|
+
gp = os.path.join(home, ".config", "opencode", "opencode.json")
|
|
439
|
+
try:
|
|
440
|
+
d = json.load(open(gp))
|
|
441
|
+
except Exception:
|
|
442
|
+
d = {}
|
|
443
|
+
if not isinstance(d, dict):
|
|
444
|
+
d = {}
|
|
445
|
+
ains = os.path.join(home, ".config", "blamcode", "ask-instructions.md")
|
|
446
|
+
ins = d.get("instructions")
|
|
447
|
+
if isinstance(ins, str):
|
|
448
|
+
ins = [ins]
|
|
449
|
+
elif not isinstance(ins, list):
|
|
450
|
+
ins = []
|
|
451
|
+
if ains not in ins:
|
|
452
|
+
ins.append(ains)
|
|
453
|
+
d["instructions"] = ins
|
|
454
|
+
json.dump(d, open(gp, "w"), indent=2)
|
|
455
|
+
except Exception:
|
|
456
|
+
pass
|
|
457
|
+
PYGC
|
|
458
|
+
fi
|
|
459
|
+
|
|
460
|
+
# Set the terminal title so the toolbar shows BLAMCODE
|
|
461
|
+
if ( : >/dev/tty ) 2>/dev/null; then printf '\033]0;BLAMCODE\007' >/dev/tty 2>/dev/null; fi
|
|
462
|
+
|
|
463
|
+
# Locate the native libraries we ship alongside the wrapper.
|
|
464
|
+
# In the flat layout they sit next to the wrapper; in the Termux package
|
|
465
|
+
# layout they are under ../lib; on glibc Linux under ~/.local/lib/blamcode.
|
|
466
|
+
NATIVE_LIB_DIR=""
|
|
467
|
+
for candidate in \
|
|
468
|
+
"$dir/../lib" \
|
|
469
|
+
"${PREFIX:-/data/data/com.termux/files/usr}/lib" \
|
|
470
|
+
"$HOME/.local/lib/blamcode" \
|
|
471
|
+
"$dir"
|
|
472
|
+
do
|
|
473
|
+
if [ -f "$candidate/libtagfix.so" ]; then
|
|
474
|
+
NATIVE_LIB_DIR="$candidate"
|
|
475
|
+
break
|
|
476
|
+
fi
|
|
477
|
+
done
|
|
478
|
+
|
|
479
|
+
if [ -n "$NATIVE_LIB_DIR" ]; then
|
|
480
|
+
export LD_PRELOAD="${NATIVE_LIB_DIR}/libtagfix.so${LD_PRELOAD:+:$LD_PRELOAD}"
|
|
481
|
+
export LD_LIBRARY_PATH="${NATIVE_LIB_DIR}${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
|
482
|
+
export OPENTUI_LIB_PATH="${NATIVE_LIB_DIR}/libopentui.so"
|
|
483
|
+
if [ -f "${NATIVE_LIB_DIR}/librust_pty_arm64.so" ]; then
|
|
484
|
+
export BUN_PTY_LIB="${NATIVE_LIB_DIR}/librust_pty_arm64.so"
|
|
485
|
+
fi
|
|
486
|
+
export OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER="${OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER:-true}"
|
|
487
|
+
if [ -x "$NATIVE_LIB_DIR/bun" ]; then
|
|
488
|
+
export OPENCODE_BUN_PATH="$NATIVE_LIB_DIR/bun"
|
|
489
|
+
fi
|
|
490
|
+
elif [ -n "$PREFIX" ] && [ -d "$PREFIX" ]; then
|
|
491
|
+
# warn only on real Termux — glibc Linux does not need the native lib
|
|
492
|
+
echo "blamcode: warning: native library directory not found, may crash on Android 11+" >&2
|
|
493
|
+
fi
|
|
494
|
+
|
|
495
|
+
# Locate the real binary. Prefer the package layout first so upgrades do not
|
|
496
|
+
# accidentally execute a stale flat-layout binary left in $PREFIX/bin.
|
|
497
|
+
BIN=""
|
|
498
|
+
for candidate in \
|
|
499
|
+
"$dir/../libexec/opencode/opencode.bin" \
|
|
500
|
+
"$HOME/.local/libexec/blamcode/opencode.bin" \
|
|
501
|
+
"${PREFIX:-/data/data/com.termux/files/usr}/libexec/opencode/opencode.bin" \
|
|
502
|
+
"$dir/opencode.bin" \
|
|
503
|
+
"$HOME/.opencode/bin/opencode"
|
|
504
|
+
do
|
|
505
|
+
if [ -x "$candidate" ]; then
|
|
506
|
+
BIN="$candidate"
|
|
507
|
+
break
|
|
508
|
+
fi
|
|
509
|
+
done
|
|
510
|
+
[ -n "$BIN" ] || { echo "blamcode: error: could not find opencode.bin" >&2; exit 127; }
|
|
511
|
+
|
|
512
|
+
rc=0
|
|
513
|
+
if [ "$SESSION_MODE" = 1 ]; then
|
|
514
|
+
"$BIN" --continue "$@" || rc=$?
|
|
515
|
+
else
|
|
516
|
+
"$BIN" "$@" || rc=$?
|
|
517
|
+
fi
|
|
518
|
+
exit $rc
|