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
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
#!/data/data/com.termux/files/usr/bin/bash
|
|
2
|
+
# blamcode-browser — Web page reader + AI extractor + real-time search for BLAMCODE
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# blamcode-browser <url> [question] — read a page, AI answers about it
|
|
6
|
+
# blamcode-browser <file.html> [question] — read a local HTML file
|
|
7
|
+
# blamcode-browser search "query" [count] — real-time web search (DuckDuckGo)
|
|
8
|
+
#
|
|
9
|
+
# AI model: nemotron-3-ultra-free (default — fastest ~3s, handles 37KB+ text)
|
|
10
|
+
# Override: BLAMCODE_BROWSER_MODEL=deepseek-v4-flash-free
|
|
11
|
+
# Provider: OpenCode Zen (key from OPENCODE_API_KEY, built-in fallback)
|
|
12
|
+
#
|
|
13
|
+
# Termux notes baked in:
|
|
14
|
+
# - python stdout is broken in some Termux builds → os.write(1, ...)
|
|
15
|
+
# - no /tmp folder → temp dirs come from TMPDIR/TEMP/TMP
|
|
16
|
+
# - payloads are built with json.dump (printf breaks JSON on quotes)
|
|
17
|
+
|
|
18
|
+
set -e
|
|
19
|
+
|
|
20
|
+
if [ $# -lt 1 ]; then
|
|
21
|
+
echo "Usage:"
|
|
22
|
+
echo " blamcode-browser <url> [question] — read a page, AI answers"
|
|
23
|
+
echo " blamcode-browser search \"query\" [count] — real-time web search"
|
|
24
|
+
echo " blamcode-browser <file.html> [question] — read a local HTML file"
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
29
|
+
PY=python3
|
|
30
|
+
elif command -v python >/dev/null 2>&1; then
|
|
31
|
+
PY=python
|
|
32
|
+
else
|
|
33
|
+
echo "❌ Python is required for blamcode-browser" >&2
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
ZEN_KEYS="${OPENCODE_API_KEY:-sk-PKOWRt2391BL0MP3W90yaG8qx4vofQJQgigJreBBYjrArj0lwuU1HkWUqOHgDGHP}"
|
|
38
|
+
MODEL="${BLAMCODE_BROWSER_MODEL:-nemotron-3-ultra-free}"
|
|
39
|
+
|
|
40
|
+
exec "$PY" - "$1" "$2" "$3" "$MODEL" "$ZEN_KEYS" <<'PYBR'
|
|
41
|
+
import sys, os, json, re, time
|
|
42
|
+
import urllib.request, urllib.error, urllib.parse
|
|
43
|
+
import html as html_mod
|
|
44
|
+
from html.parser import HTMLParser
|
|
45
|
+
|
|
46
|
+
argv = sys.argv[1:]
|
|
47
|
+
target = argv[0] if len(argv) > 0 else ""
|
|
48
|
+
question = argv[1] if len(argv) > 1 else ""
|
|
49
|
+
count = argv[2] if len(argv) > 2 else ""
|
|
50
|
+
model = argv[3] if len(argv) > 3 else "nemotron-3-ultra-free"
|
|
51
|
+
raw_key = argv[4] if len(argv) > 4 else ""
|
|
52
|
+
|
|
53
|
+
keys = [k.strip() for k in re.split(r"[,;\s\n]+", raw_key) if k.strip()]
|
|
54
|
+
if not keys:
|
|
55
|
+
keys = [os.environ.get("OPENCODE_API_KEY", "")]
|
|
56
|
+
|
|
57
|
+
def out(s):
|
|
58
|
+
try:
|
|
59
|
+
os.write(1, (str(s) + "\n").encode("utf-8", "replace"))
|
|
60
|
+
except Exception:
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
def fetch(url, timeout=40):
|
|
64
|
+
req = urllib.request.Request(url, headers={
|
|
65
|
+
"User-Agent": ("Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 "
|
|
66
|
+
"(KHTML, like Gecko) Chrome/120.0 Mobile Safari/537.36"),
|
|
67
|
+
"Accept-Language": "en-US,en;q=0.9",
|
|
68
|
+
})
|
|
69
|
+
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
70
|
+
return r.read().decode("utf-8", "replace")
|
|
71
|
+
|
|
72
|
+
def strip_tags(s):
|
|
73
|
+
s = re.sub(r"<[^>]+>", "", s or "")
|
|
74
|
+
s = html_mod.unescape(s)
|
|
75
|
+
return re.sub(r"\s+", " ", s).strip()
|
|
76
|
+
|
|
77
|
+
class TextExtractor(HTMLParser):
|
|
78
|
+
def __init__(self):
|
|
79
|
+
super().__init__(convert_charrefs=True)
|
|
80
|
+
self.parts = []
|
|
81
|
+
self.skip = 0
|
|
82
|
+
self.blocks = {"p", "div", "li", "tr", "h1", "h2", "h3", "h4", "h5",
|
|
83
|
+
"section", "article", "br", "pre", "blockquote", "td", "th"}
|
|
84
|
+
self.hidden = {"script", "style", "noscript", "template", "svg",
|
|
85
|
+
"head", "iframe", "form", "nav", "footer"}
|
|
86
|
+
def handle_starttag(self, tag, attrs):
|
|
87
|
+
if tag in self.hidden:
|
|
88
|
+
self.skip += 1
|
|
89
|
+
if tag in self.blocks:
|
|
90
|
+
self.parts.append("\n")
|
|
91
|
+
def handle_endtag(self, tag):
|
|
92
|
+
if tag in self.hidden and self.skip:
|
|
93
|
+
self.skip -= 1
|
|
94
|
+
if tag in self.blocks:
|
|
95
|
+
self.parts.append("\n")
|
|
96
|
+
def handle_data(self, data):
|
|
97
|
+
if not self.skip:
|
|
98
|
+
self.parts.append(data)
|
|
99
|
+
|
|
100
|
+
def html_to_text(raw):
|
|
101
|
+
p = TextExtractor()
|
|
102
|
+
try:
|
|
103
|
+
p.feed(raw)
|
|
104
|
+
except Exception:
|
|
105
|
+
pass
|
|
106
|
+
text = "".join(p.parts)
|
|
107
|
+
text = re.sub(r"[ \t]+", " ", text)
|
|
108
|
+
text = re.sub(r"\n\s*\n+", "\n", text)
|
|
109
|
+
return text.strip()
|
|
110
|
+
|
|
111
|
+
def decode_uddg(href):
|
|
112
|
+
href = (href or "").strip()
|
|
113
|
+
if href.startswith("//"):
|
|
114
|
+
href = "https:" + href
|
|
115
|
+
if "uddg=" in href:
|
|
116
|
+
q = urllib.parse.parse_qs(urllib.parse.urlparse(href).query)
|
|
117
|
+
if q.get("uddg"):
|
|
118
|
+
return q["uddg"][0]
|
|
119
|
+
return href
|
|
120
|
+
|
|
121
|
+
def parse_ddg(page):
|
|
122
|
+
"""Parse html.duckduckgo.com results — tolerant of markup changes."""
|
|
123
|
+
results = []
|
|
124
|
+
blocks = re.split(r'<div[^>]*class="[^"]*result[^"]*"[^>]*>', page)[1:]
|
|
125
|
+
for b in blocks:
|
|
126
|
+
m = re.search(r'<a[^>]*href="([^"]+)"[^>]*>(.*?)</a>', b, re.S)
|
|
127
|
+
if not m:
|
|
128
|
+
continue
|
|
129
|
+
url = decode_uddg(m.group(1))
|
|
130
|
+
title = strip_tags(m.group(2))
|
|
131
|
+
if not title or not url.startswith("http"):
|
|
132
|
+
continue
|
|
133
|
+
sm = re.search(r'class="[^"]*snippet[^"]*"[^>]*>(.*?)</(?:a|div|span)>', b, re.S)
|
|
134
|
+
snippet = strip_tags(sm.group(1)) if sm else ""
|
|
135
|
+
results.append({"title": title, "url": url, "snippet": snippet})
|
|
136
|
+
if len(results) >= 10:
|
|
137
|
+
break
|
|
138
|
+
if not results:
|
|
139
|
+
for m in re.finditer(r'<a[^>]*href="([^"]+)"[^>]*>(.*?)</a>', page, re.S):
|
|
140
|
+
url = decode_uddg(m.group(1))
|
|
141
|
+
title = strip_tags(m.group(2))
|
|
142
|
+
if not title or not url.startswith("http"):
|
|
143
|
+
continue
|
|
144
|
+
if any(d in url for d in ("duckduckgo.com", "duck.co", "w3.org")):
|
|
145
|
+
continue
|
|
146
|
+
results.append({"title": title, "url": url, "snippet": ""})
|
|
147
|
+
if len(results) >= 10:
|
|
148
|
+
break
|
|
149
|
+
return results
|
|
150
|
+
|
|
151
|
+
def parse_ddg_lite(page):
|
|
152
|
+
results = []
|
|
153
|
+
for m in re.finditer(r'<a[^>]*href="([^"]+)"[^>]*>(.*?)</a>', page, re.S):
|
|
154
|
+
url = m.group(1)
|
|
155
|
+
title = strip_tags(m.group(2))
|
|
156
|
+
if not title or not url.startswith("http"):
|
|
157
|
+
continue
|
|
158
|
+
if "duckduckgo.com" in url or "duck.co" in url:
|
|
159
|
+
continue
|
|
160
|
+
results.append({"title": title, "url": url, "snippet": ""})
|
|
161
|
+
if len(results) >= 10:
|
|
162
|
+
break
|
|
163
|
+
return results
|
|
164
|
+
|
|
165
|
+
def ddg_search(query, n=8):
|
|
166
|
+
q = urllib.parse.quote_plus(query)
|
|
167
|
+
try:
|
|
168
|
+
page = fetch("https://html.duckduckgo.com/html/?q=" + q)
|
|
169
|
+
results = parse_ddg(page)
|
|
170
|
+
except Exception:
|
|
171
|
+
results = []
|
|
172
|
+
if not results:
|
|
173
|
+
try:
|
|
174
|
+
page = fetch("https://lite.duckduckgo.com/lite/?q=" + q)
|
|
175
|
+
results = parse_ddg_lite(page)
|
|
176
|
+
except Exception:
|
|
177
|
+
results = []
|
|
178
|
+
return results[:max(1, n)]
|
|
179
|
+
|
|
180
|
+
def zen_chat(system, user, key, max_tokens=2500):
|
|
181
|
+
url = "https://opencode.ai/zen/v1/chat/completions"
|
|
182
|
+
payload = {
|
|
183
|
+
"model": model,
|
|
184
|
+
"messages": [
|
|
185
|
+
{"role": "system", "content": system},
|
|
186
|
+
{"role": "user", "content": user},
|
|
187
|
+
],
|
|
188
|
+
"max_tokens": max_tokens,
|
|
189
|
+
}
|
|
190
|
+
req = urllib.request.Request(
|
|
191
|
+
url,
|
|
192
|
+
data=json.dumps(payload).encode("utf-8"),
|
|
193
|
+
headers={
|
|
194
|
+
"Content-Type": "application/json",
|
|
195
|
+
"Authorization": "Bearer " + key,
|
|
196
|
+
# Zen keys the free quota to the opencode client id — BLAMCODE is a
|
|
197
|
+
# rebranded opencode, so this is our real identity (other UAs
|
|
198
|
+
# land in a permanently-exhausted anonymous bucket).
|
|
199
|
+
"User-Agent": "opencode/1.0.0",
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
with urllib.request.urlopen(req, timeout=180) as resp:
|
|
203
|
+
d = json.loads(resp.read().decode("utf-8"))
|
|
204
|
+
return d["choices"][0]["message"]["content"]
|
|
205
|
+
|
|
206
|
+
def zen_chat_retry(system, user):
|
|
207
|
+
"""Rate limits (429) → wait 12s and retry up to 4 times across all keys."""
|
|
208
|
+
errors = []
|
|
209
|
+
for attempt in range(4):
|
|
210
|
+
for k in keys:
|
|
211
|
+
if not k:
|
|
212
|
+
continue
|
|
213
|
+
try:
|
|
214
|
+
return zen_chat(system, user, k)
|
|
215
|
+
except urllib.error.HTTPError as e:
|
|
216
|
+
errors.append(f"HTTP {e.code}")
|
|
217
|
+
if e.code == 429:
|
|
218
|
+
time.sleep(12)
|
|
219
|
+
else:
|
|
220
|
+
return f"[browser: provider error {e.code}]"
|
|
221
|
+
except Exception as e:
|
|
222
|
+
errors.append(str(e)[:120])
|
|
223
|
+
if attempt < 3:
|
|
224
|
+
time.sleep(5)
|
|
225
|
+
return "[browser: all attempts failed — " + "; ".join(errors[-3:]) + "]"
|
|
226
|
+
|
|
227
|
+
def summarize_search(query, results):
|
|
228
|
+
lines = [f"Real-time search results for: {query}\n"]
|
|
229
|
+
for i, r in enumerate(results, 1):
|
|
230
|
+
lines.append(f"{i}. {r['title']}")
|
|
231
|
+
lines.append(f" URL: {r['url']}")
|
|
232
|
+
if r["snippet"]:
|
|
233
|
+
lines.append(f" {r['snippet']}")
|
|
234
|
+
text = "\n".join(lines)
|
|
235
|
+
system = ("You are a real-time search assistant. Answer the user's question "
|
|
236
|
+
"using ONLY the search results below. Give a clear, detailed answer "
|
|
237
|
+
"with numbered sources. If results are missing or irrelevant, say so "
|
|
238
|
+
"honestly instead of guessing.")
|
|
239
|
+
return zen_chat_retry(system, text + "\n\nQuestion: " + query)
|
|
240
|
+
|
|
241
|
+
def answer_page(page_title, text, question):
|
|
242
|
+
system = ("You are a precise web-page reader. Answer the user's question "
|
|
243
|
+
"using ONLY the page text below. Include exact names, numbers, "
|
|
244
|
+
"facts and steps; quote the page when relevant. If the page does "
|
|
245
|
+
"not contain the answer, say so clearly.")
|
|
246
|
+
user = f"Page: {page_title}\n\n--- page text ---\n{text}\n--- end ---\n\nQuestion: {question}"
|
|
247
|
+
return zen_chat_retry(system, user)
|
|
248
|
+
|
|
249
|
+
def local_or_url_path(p):
|
|
250
|
+
if os.path.isfile(p):
|
|
251
|
+
return os.path.abspath(p)
|
|
252
|
+
if p.startswith(("http://", "https://", "file://")):
|
|
253
|
+
return p
|
|
254
|
+
return "https://" + p
|
|
255
|
+
|
|
256
|
+
# ---------------- main ----------------
|
|
257
|
+
if target == "search":
|
|
258
|
+
query = question or ""
|
|
259
|
+
if not query:
|
|
260
|
+
out("Usage: blamcode-browser search \"query\" [count]")
|
|
261
|
+
sys.exit(1)
|
|
262
|
+
n = 8
|
|
263
|
+
if count and count.isdigit():
|
|
264
|
+
n = int(count)
|
|
265
|
+
out(f"🔎 Searching: {query}\n")
|
|
266
|
+
results = ddg_search(query, n)
|
|
267
|
+
if not results:
|
|
268
|
+
out("⚠ No results found. Try a different query.")
|
|
269
|
+
sys.exit(0)
|
|
270
|
+
out(f"Found {len(results)} result(s):\n")
|
|
271
|
+
for i, r in enumerate(results, 1):
|
|
272
|
+
out(f"{i}. {r['title']}")
|
|
273
|
+
out(f" {r['url']}")
|
|
274
|
+
if r["snippet"]:
|
|
275
|
+
out(f" {r['snippet']}")
|
|
276
|
+
out("\n--- AI summary ---")
|
|
277
|
+
answer = summarize_search(query, results)
|
|
278
|
+
out(answer)
|
|
279
|
+
sys.exit(0)
|
|
280
|
+
|
|
281
|
+
# page mode
|
|
282
|
+
p = local_or_url_path(target)
|
|
283
|
+
page_title = p
|
|
284
|
+
if os.path.isfile(p):
|
|
285
|
+
try:
|
|
286
|
+
with open(p, "r", encoding="utf-8", errors="replace") as fh:
|
|
287
|
+
raw = fh.read()
|
|
288
|
+
page_title = os.path.basename(p)
|
|
289
|
+
except Exception as e:
|
|
290
|
+
out(f"❌ Could not read file {p}: {e}")
|
|
291
|
+
sys.exit(1)
|
|
292
|
+
else:
|
|
293
|
+
out(f"🌐 Fetching: {p}\n")
|
|
294
|
+
try:
|
|
295
|
+
raw = fetch(p)
|
|
296
|
+
except Exception as e:
|
|
297
|
+
out(f"❌ Could not fetch {p}: {e}")
|
|
298
|
+
sys.exit(1)
|
|
299
|
+
m = re.search(r"<title[^>]*>(.*?)</title>", raw, re.S | re.I)
|
|
300
|
+
if m:
|
|
301
|
+
page_title = strip_tags(m.group(1))
|
|
302
|
+
|
|
303
|
+
text = html_to_text(raw)
|
|
304
|
+
if len(text) > 55000:
|
|
305
|
+
text = text[:55000] + "\n...[truncated]"
|
|
306
|
+
if not text:
|
|
307
|
+
out("⚠ No readable text found on the page.")
|
|
308
|
+
sys.exit(0)
|
|
309
|
+
|
|
310
|
+
question = question or "Summarize this page in detail — main topic, key facts, structure and anything notable."
|
|
311
|
+
out(f"📄 {page_title} ({len(text)} chars)\n")
|
|
312
|
+
out("--- AI answer ---")
|
|
313
|
+
answer = answer_page(page_title, text, question)
|
|
314
|
+
out(answer)
|
|
315
|
+
PYBR
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/data/data/com.termux/files/usr/bin/sh
|
|
2
|
+
# blamcode-menu — optional branded launcher for BLAMCODE on Termux.
|
|
3
|
+
#
|
|
4
|
+
# Safe way to start BLAMCODE: every option below exec's the real blamcode wrapper
|
|
5
|
+
# (which sets LD_PRELOAD=libtagfix.so + all env). If blamcode is not on PATH,
|
|
6
|
+
# paper over it with a direct opencode.bin launch using the same env chain.
|
|
7
|
+
#
|
|
8
|
+
# Note: /models, /theme and /auto are TUI-internal commands — they can't be
|
|
9
|
+
# run from a shell. The menu prints the exact keys to press inside the TUI.
|
|
10
|
+
|
|
11
|
+
set -e
|
|
12
|
+
|
|
13
|
+
PREFIX="${PREFIX:-/data/data/com.termux/files/usr}"
|
|
14
|
+
|
|
15
|
+
find_blamcode() {
|
|
16
|
+
for c in \
|
|
17
|
+
"$PREFIX/bin/blamcode" \
|
|
18
|
+
"$PREFIX/libexec/opencode/blamcode.bin" \
|
|
19
|
+
"$PREFIX/libexec/opencode/opencode.bin" \
|
|
20
|
+
"$HOME/.local/bin/blamcode" \
|
|
21
|
+
"$HOME/.local/libexec/blamcode/opencode.bin" \
|
|
22
|
+
"$HOME/.opencode/bin/opencode"
|
|
23
|
+
do
|
|
24
|
+
[ -x "$c" ] && { echo "$c"; return 0; }
|
|
25
|
+
done
|
|
26
|
+
return 1
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
launch() { # run the real wrapper with args ($1 = bin, rest = args)
|
|
30
|
+
bin="$1"; shift
|
|
31
|
+
if [ "$(basename "$bin")" = "blamcode" ]; then
|
|
32
|
+
exec "$bin" "$@"
|
|
33
|
+
else
|
|
34
|
+
# raw binary: replicate the wrapper env chain
|
|
35
|
+
export ANDROID_ROOT="${ANDROID_ROOT:-/system}"
|
|
36
|
+
export OPENCODE_DISABLE_TUI_AUDIO="${OPENCODE_DISABLE_TUI_AUDIO:-1}"
|
|
37
|
+
export TMPDIR="${BLAMCODE_TMPDIR:-${HOME:-/data/data/com.termux/files/home}/tmp}"
|
|
38
|
+
mkdir -p "$TMPDIR" 2>/dev/null || true
|
|
39
|
+
for libdir in "$PREFIX/lib" "$HOME/.local/lib/blamcode"; do
|
|
40
|
+
if [ -f "$libdir/libtagfix.so" ]; then
|
|
41
|
+
export LD_PRELOAD="$libdir/libtagfix.so${LD_PRELOAD:+:$LD_PRELOAD}"
|
|
42
|
+
export LD_LIBRARY_PATH="$libdir${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
|
43
|
+
[ -f "$libdir/libopentui.so" ] && export OPENTUI_LIB_PATH="$libdir/libopentui.so"
|
|
44
|
+
[ -f "$libdir/librust_pty_arm64.so" ] && export BUN_PTY_LIB="$libdir/librust_pty_arm64.so"
|
|
45
|
+
[ -x "$libdir/bun" ] && export OPENCODE_BUN_PATH="$libdir/bun"
|
|
46
|
+
export OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER="${OPENCODE_EXPERIMENTAL_DISABLE_FILEWATCHER:-true}"
|
|
47
|
+
break
|
|
48
|
+
fi
|
|
49
|
+
done
|
|
50
|
+
exec "$bin" "$@"
|
|
51
|
+
fi
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
BIN="$(find_blamcode || true)"
|
|
55
|
+
|
|
56
|
+
menu() {
|
|
57
|
+
printf '\033[1;96m=== BLAMCODE launcher ===\033[0m\n'
|
|
58
|
+
printf ' 1) Chat (new session)\n'
|
|
59
|
+
printf ' 2) Open project blamcode <name>\n'
|
|
60
|
+
printf ' 3) List projects blamcode ls\n'
|
|
61
|
+
printf ' 4) Keys help\n'
|
|
62
|
+
printf ' 5) Version\n'
|
|
63
|
+
printf ' 6) Update BLAMCODE\n'
|
|
64
|
+
printf ' 7) Headless server (serve)\n'
|
|
65
|
+
printf ' 8) Plugins\n'
|
|
66
|
+
printf ' 9) Trust a folder\n'
|
|
67
|
+
printf ' 0) Exit\n'
|
|
68
|
+
printf 'Choose [0-9]: '
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
while :; do
|
|
72
|
+
menu
|
|
73
|
+
read -r choice
|
|
74
|
+
case "$choice" in
|
|
75
|
+
1)
|
|
76
|
+
[ -n "$BIN" ] || { echo "blamcode not found — run install.sh first" >&2; exit 1; }
|
|
77
|
+
launch "$BIN"
|
|
78
|
+
;;
|
|
79
|
+
2)
|
|
80
|
+
printf 'Project name: '
|
|
81
|
+
read -r pname
|
|
82
|
+
[ -n "$BIN" ] || { echo "blamcode not found" >&2; exit 1; }
|
|
83
|
+
launch "$BIN" "$pname"
|
|
84
|
+
;;
|
|
85
|
+
3)
|
|
86
|
+
[ -n "$BIN" ] || { echo "blamcode not found" >&2; exit 1; }
|
|
87
|
+
SDIR_ROOT=""
|
|
88
|
+
if [ -d /storage/emulated/0 ]; then SDIR_ROOT=/storage/emulated/0/blamcode
|
|
89
|
+
elif [ -d /sdcard ]; then SDIR_ROOT=/sdcard/blamcode
|
|
90
|
+
else SDIR_ROOT="$HOME/blamcode"
|
|
91
|
+
fi
|
|
92
|
+
echo ""
|
|
93
|
+
echo "📁 BLAMCODE Projects ($SDIR_ROOT):"
|
|
94
|
+
if [ -d "$SDIR_ROOT" ]; then
|
|
95
|
+
count=0
|
|
96
|
+
for d in "$SDIR_ROOT"/*; do
|
|
97
|
+
if [ -d "$d" ]; then
|
|
98
|
+
b="$(basename "$d")"
|
|
99
|
+
[ "$b" != ".opencode" ] && echo " 🔹 $b" && count=$((count+1))
|
|
100
|
+
fi
|
|
101
|
+
done
|
|
102
|
+
[ $count -eq 0 ] && echo " (no project folders yet)"
|
|
103
|
+
else
|
|
104
|
+
echo " (no blamcode folder yet)"
|
|
105
|
+
fi
|
|
106
|
+
echo ""
|
|
107
|
+
echo "Open or create: blamcode <project-name>"
|
|
108
|
+
echo ""
|
|
109
|
+
read -r _ || true
|
|
110
|
+
;;
|
|
111
|
+
4)
|
|
112
|
+
printf '\nInside the BLAMCODE TUI:\n Ctrl+K -> command palette (/models, /theme...)\n Esc -> back\n /explore /fix /review /ask /blamcode /open -> in-chat commands\nPress any key to return to menu...\n'
|
|
113
|
+
read -r _
|
|
114
|
+
;;
|
|
115
|
+
5)
|
|
116
|
+
if [ -n "$BIN" ]; then launch "$BIN" --version; else echo "blamcode not installed"; fi
|
|
117
|
+
read -r _ || true
|
|
118
|
+
;;
|
|
119
|
+
6)
|
|
120
|
+
echo "Run: sh $PREFIX/../share/blamcode/install.sh (or re-download the installer)"
|
|
121
|
+
read -r _ || true
|
|
122
|
+
;;
|
|
123
|
+
7)
|
|
124
|
+
[ -n "$BIN" ] || { echo "blamcode not found" >&2; exit 1; }
|
|
125
|
+
launch "$BIN" serve 2>/dev/null || launch "$BIN" serve --port 4020
|
|
126
|
+
;;
|
|
127
|
+
8)
|
|
128
|
+
[ -n "$BIN" ] || { echo "blamcode not found" >&2; exit 1; }
|
|
129
|
+
launch "$BIN" plugin
|
|
130
|
+
;;
|
|
131
|
+
9)
|
|
132
|
+
printf 'Folder to trust: '
|
|
133
|
+
read -r folder
|
|
134
|
+
[ -n "$BIN" ] || { echo "blamcode not found" >&2; exit 1; }
|
|
135
|
+
launch "$BIN" trust include "$folder"
|
|
136
|
+
;;
|
|
137
|
+
0) exit 0 ;;
|
|
138
|
+
*) ;;
|
|
139
|
+
esac
|
|
140
|
+
done
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/data/data/com.termux/files/usr/bin/bash
|
|
2
|
+
# blamcode-uninstall — cleanly removes BLAMCODE from the device
|
|
3
|
+
#
|
|
4
|
+
# Usage:
|
|
5
|
+
# blamcode uninstall (asks for confirmation)
|
|
6
|
+
# blamcode uninstall -y (skip confirmation)
|
|
7
|
+
#
|
|
8
|
+
# Removes:
|
|
9
|
+
# - the blamcode / oc-settings / blamcode-menu launchers
|
|
10
|
+
# - the BLAMCODE layer (config, skills, commands) at ~/.config/opencode
|
|
11
|
+
# - the branded core binary at libexec + native libs
|
|
12
|
+
# - PATH + OPENCODE_API_KEY lines from ~/.bashrc
|
|
13
|
+
#
|
|
14
|
+
# Keeps (never touched):
|
|
15
|
+
# - your projects / session folders (/storage/emulated/0/blamcode)
|
|
16
|
+
# - the opencode core install at ~/.opencode (separate tool)
|
|
17
|
+
set -e
|
|
18
|
+
|
|
19
|
+
PREFIX="${PREFIX:-/data/data/com.termux/files/usr}"
|
|
20
|
+
HOME_DIR="${HOME:-/data/data/com.termux/files/home}"
|
|
21
|
+
CONFIG_DIR="$HOME/.config/opencode"
|
|
22
|
+
BIN_DIR=""
|
|
23
|
+
LIBEXEC_DIR=""
|
|
24
|
+
LIB_DIR=""
|
|
25
|
+
|
|
26
|
+
# resolve install layout (Termux vs ~/.local)
|
|
27
|
+
if [ -n "$PREFIX" ] && [ -d "$PREFIX" ] && [ -n "$(command -v pkg 2>/dev/null || true)" ]; then
|
|
28
|
+
BIN_DIR="$PREFIX/bin"
|
|
29
|
+
LIBEXEC_DIR="$PREFIX/libexec/opencode"
|
|
30
|
+
LIB_DIR="$PREFIX/lib"
|
|
31
|
+
else
|
|
32
|
+
BIN_DIR="$HOME/.local/bin"
|
|
33
|
+
LIBEXEC_DIR="$HOME/.local/libexec/blamcode"
|
|
34
|
+
LIB_DIR="$HOME/.local/lib/blamcode"
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
if [ "${1:-}" != "-y" ] && [ "${1:-}" != "--yes" ]; then
|
|
38
|
+
echo "This will remove BLAMCODE (AI coding CLI) from this device."
|
|
39
|
+
echo " launchers: $BIN_DIR/{blamcode,blamcode-menu,oc-settings}"
|
|
40
|
+
echo " config: $CONFIG_DIR (opencode.json, agent, commands, skills)"
|
|
41
|
+
echo " core: $LIBEXEC_DIR + $LIB_DIR"
|
|
42
|
+
echo
|
|
43
|
+
echo "Your projects and session folders are NOT touched."
|
|
44
|
+
echo -n "Continue? [y/N]: "
|
|
45
|
+
read -r CONFIRM || CONFIRM=""
|
|
46
|
+
case "$CONFIRM" in
|
|
47
|
+
y|Y|yes|YES) ;;
|
|
48
|
+
*) echo "Aborted."; exit 1;;
|
|
49
|
+
esac
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
rm -f "$BIN_DIR/blamcode" "$BIN_DIR/blamcode-menu" "$BIN_DIR/oc-settings"
|
|
53
|
+
rm -f "$LIBEXEC_DIR/opencode.bin" "$LIBEXEC_DIR/blamcode-core-version"
|
|
54
|
+
rm -rf "$LIBEXEC_DIR" "$LIB_DIR"
|
|
55
|
+
rm -rf "$CONFIG_DIR"
|
|
56
|
+
echo " ✓ launchers removed"
|
|
57
|
+
echo " ✓ config layer removed ($CONFIG_DIR)"
|
|
58
|
+
echo " ✓ core removed"
|
|
59
|
+
|
|
60
|
+
# clean ~/.bashrc additions (only the BLAMCODE lines)
|
|
61
|
+
RC="$HOME_DIR/.bashrc"
|
|
62
|
+
[ -f "$RC" ] || RC="$HOME_DIR/.profile"
|
|
63
|
+
if [ -f "$RC" ]; then
|
|
64
|
+
grep -v "BLAMCODE AI\|BLAMCODE PATH\|OPENCODE_API_KEY=" "$RC" > "$RC.blamcode-clean" 2>/dev/null || true
|
|
65
|
+
mv "$RC.blamcode-clean" "$RC"
|
|
66
|
+
echo " ✓ PATH + API key lines removed from $(basename "$RC")"
|
|
67
|
+
fi
|
|
68
|
+
|
|
69
|
+
echo
|
|
70
|
+
echo "BLAMCODE uninstalled. Your projects are still at /storage/emulated/0/blamcode."
|
|
71
|
+
echo "Reinstall any time with: curl -fsSL https://raw.githubusercontent.com/zyvo9/blamcode/main/install.sh | bash"
|