new file mode 100644
@@ -0,0 +1,13 @@
+{
+ "mcpServers": {
+ "edge-agent": {
+ "command": "/home/drucker/edge/tools/.venv/bin/python3",
+ "args": [
+ "/home/drucker/edge/tools/mcp-agent-server.py"
+ ],
+ "env": {
+ "EDGE_DIR": "/home/drucker/edge"
+ }
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,24 @@
+---
+name: assertia-nomenclatura
+description: Nomes oficiais do projeto AssertIA/TCU e tabela de deformações conhecidas de transcrições Gemini
+type: project
+---
+
+## Deformações confirmadas (Gemini → real)
+
+| Gemini transcreve | Nome real | Fonte |
+|---|---|---|
+| Acerte, Acertia | AssertIA | contrato, repos |
+| CJUS | Sejus (Secretaria de Jurisprudência) | contrato |
+| CEPROC | Seproc | contrato |
+| EITEC | ETEC | contrato |
+
+## Siglas não confirmadas [?validar]
+
+| Sigla | Hipótese | Status |
+|---|---|---|
+| NIA | Núcleo de Inteligência Artificial (unidade TCU?) | ?validar — sem fonte escrita |
+| AUD Contratações | Unidade de auditoria focada em contratações (nome formal?) | ?validar — pode ser shorthand informal |
+| Rtex / rtcu | Desconhecido — repo? ferramenta? deformação Gemini? | ?validar — sem hipótese forte |
+
+**Regra:** escrito > falado. Só promover para "confirmado" com fonte escrita (contrato, repo, relatório mensal, organograma oficial).
@@ -3,3 +3,16 @@
Errors that must not recur. READ at start of autonomous sessions. WRITE when errors occur.
---
+
+## 2026-04-07: edge-signal é bash, não python
+
+`tools/edge-signal` tem shebang `#!/usr/bin/env bash`. Invocar com
+`python3 tools/edge-signal` causa SyntaxError. Sempre usar
+`bash tools/edge-signal` ou invocação direta (`./tools/edge-signal`).
+
+**Regra:** checar shebang antes de invocar ferramentas CLI.
+
+## 2026-04-07: edge-consult.py broken
+
+`tools/edge-consult.py` importa `_shared.openai_client` que não existe
+em `tools/_shared/`. Bug de genotype — não corrigir, reportar.
new file mode 100644
@@ -0,0 +1,8 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
+QyNTUxOQAAACBu2HPMvn/qsqQsDPgdU2X7/gG948q12tB1qwCVacySAwAAALDvD7WZ7w+1
+mQAAAAtzc2gtZWQyNTUxOQAAACBu2HPMvn/qsqQsDPgdU2X7/gG948q12tB1qwCVacySAw
+AAAEATLzHSf/00PUX16RgeGCdO0TV86w5vIienoeMGaBZYxG7Yc8y+f+qypCwM+B1TZfv+
+Ab3jyrXa0HWrAJVpzJIDAAAAKGRydWNrZXJAZWRnZS1vZi1jaGFvcyAobW9uaXRvcnMgcm
+9iZXJ0bykBAgMEBQ==
+-----END OPENSSH PRIVATE KEY-----
new file mode 100755
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+# edge-assertia-db — Read-only PostgreSQL access to AssertIA staging DB.
+# Contract: exit 0 + JSON stdout on success, exit 1 + JSON error on failure.
+set -uo pipefail
+EDGE_DIR="${EDGE_DIR:-$HOME/edge}"
+[[ -f "$EDGE_DIR/secrets/keys.env" ]] && { set -a; source "$EDGE_DIR/secrets/keys.env"; set +a; }
+die() { printf '{"ok":false,"error":"%s","source":"assertia-db"}\n' "$1"; exit 1; }
+for v in PGHOST PGPORT PGDATABASE PGUSER PGPASSWORD; do
+ [[ -z "${!v:-}" ]] && die "$v not set"
+done
+PG="psql -h ${PGHOST} -p ${PGPORT} -d ${PGDATABASE} -U ${PGUSER} -v ON_ERROR_STOP=1 --no-psqlrc -Atq"
+CMD="${1:-}"; shift 2>/dev/null || true
+case "$CMD" in
+ test) printf '{"ok":true,"source":"assertia-db"}\n'; exit 0 ;;
+ query)
+ SQL="${1:-}"; [[ -z "$SQL" ]] && die "usage: edge-assertia-db query <sql>"
+ UP=$(echo "$SQL" | tr '[:lower:]' '[:upper:]')
+ for KW in INSERT UPDATE DELETE DROP ALTER TRUNCATE CREATE GRANT REVOKE; do
+ [[ "$UP" == *"$KW"* ]] && die "read-only: $KW not allowed"
+ done
+ ROWS=$(PGPASSWORD="$PGPASSWORD" PGSSLMODE="${PGSSLMODE:-require}" $PG -F$'\t' -c "$SQL" 2>&1) || die "query failed: $(echo "$ROWS" | head -1)"
+ echo "$ROWS" | python3 -c "
+import sys,json
+lines=[l for l in sys.stdin if l.strip()]
+rows=[l.rstrip('\n').split('\t') for l in lines]
+json.dump({'ok':True,'source':'assertia-db','count':len(rows),'rows':rows},sys.stdout,ensure_ascii=False); print()"
+ exit 0 ;;
+ tables)
+ ROWS=$(PGPASSWORD="$PGPASSWORD" PGSSLMODE="${PGSSLMODE:-require}" $PG -F$'\t' -c \
+ "SELECT table_name, pg_size_pretty(pg_total_relation_size(quote_ident(table_name))) FROM information_schema.tables WHERE table_schema='public' ORDER BY table_name" 2>&1) \
+ || die "tables query failed"
+ echo "$ROWS" | python3 -c "
+import sys,json
+items=[{'table':r[0],'size':r[1]} for l in sys.stdin if l.strip() for r in [l.rstrip('\n').split('\t')]]
+json.dump({'ok':True,'source':'assertia-db','count':len(items),'results':items},sys.stdout,ensure_ascii=False); print()"
+ exit 0 ;;
+ stats)
+ TABLE="${1:-chat_session}"
+ ROWS=$(PGPASSWORD="$PGPASSWORD" PGSSLMODE="${PGSSLMODE:-require}" $PG -F$'\t' -c \
+ "SELECT count(*) AS n, min(created_at)::date AS earliest, max(created_at)::date AS latest FROM ${TABLE}" 2>&1) \
+ || die "stats query failed"
+ echo "$ROWS" | python3 -c "
+import sys,json; parts=sys.stdin.readline().strip().split('\t')
+json.dump({'ok':True,'source':'assertia-db','table':'$TABLE','count':int(parts[0]),'earliest':parts[1],'latest':parts[2]},sys.stdout,ensure_ascii=False); print()"
+ exit 0 ;;
+ *) die "usage: edge-assertia-db {test|query|tables|stats} [args]" ;;
+esac
new file mode 100755
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+# edge-exa — Semantic search via Exa API for horizon scanning.
+# Contract: exit 0 + JSON stdout on success, exit 1 + JSON error on failure.
+set -uo pipefail
+
+EDGE_DIR="${EDGE_DIR:-$HOME/edge}"
+KEYS_ENV="$EDGE_DIR/secrets/keys.env"
+
+# Source secrets
+if [[ -f "$KEYS_ENV" ]]; then
+ set -a; source "$KEYS_ENV"; set +a
+fi
+
+die() { printf '{"ok":false,"error":"%s","source":"exa"}\n' "$1" >&1; exit 1; }
+
+[[ -z "${EXA_API_KEY:-}" ]] && die "EXA_API_KEY not set"
+
+CMD="${1:-}"
+shift 2>/dev/null || true
+
+case "$CMD" in
+ test)
+ printf '{"ok":true,"source":"exa"}\n'
+ exit 0
+ ;;
+ search)
+ QUERY="${1:-}"
+ NUM="${2:-10}"
+ [[ -z "$QUERY" ]] && die "usage: edge-exa search <query> [num_results]"
+ BODY=$(printf '{"query":"%s","type":"auto","numResults":%d,"contents":{"text":{"maxCharacters":300}}}' \
+ "$(echo "$QUERY" | sed 's/"/\\"/g')" "$NUM")
+ RESP=$(curl -sf --max-time 20 \
+ -X POST "https://api.exa.ai/search" \
+ -H "x-api-key: $EXA_API_KEY" \
+ -H "Content-Type: application/json" \
+ -H "User-Agent: edge-exa/1.0" \
+ -d "$BODY" 2>/dev/null) || die "Exa API request failed"
+ # Extract results array, normalize to edge schema
+ echo "$RESP" | python3 -c "
+import sys, json
+data = json.load(sys.stdin)
+items = []
+for r in data.get('results', []):
+ text = (r.get('text') or '')[:200].replace('\n', ' ')
+ score = int(float(r.get('score', 0)) * 100) if r.get('score') else 5
+ items.append({'title': (r.get('title') or '')[:150], 'url': r.get('url', ''),
+ 'source': 'exa', 'detail': text, 'score': score})
+json.dump({'ok': True, 'source': 'exa', 'count': len(items), 'results': items}, sys.stdout, ensure_ascii=False)
+print()
+"
+ exit 0
+ ;;
+ *)
+ die "usage: edge-exa {test|search} [args]"
+ ;;
+esac
new file mode 100755
@@ -0,0 +1,65 @@
+#!/usr/bin/env bash
+# edge-gdrive — Google Drive do consórcio: contrato, relatórios, transcrições.
+# Contract: exit 0 + JSON stdout on success, exit 1 + JSON error on failure.
+set -uo pipefail
+EDGE_DIR="${EDGE_DIR:-$HOME/edge}"
+KEYS_ENV="$EDGE_DIR/secrets/keys.env"
+[[ -f "$KEYS_ENV" ]] && { set -a; source "$KEYS_ENV"; set +a; }
+
+die() { printf '{"ok":false,"error":"%s","source":"gdrive"}\n' "$1" >&1; exit 1; }
+
+[[ -z "${GDRIVE_REFRESH_TOKEN:-}" ]] && die "GDRIVE_REFRESH_TOKEN not set"
+
+# rclone-compatible OAuth2 client (public, embedded in rclone source)
+_GCID="202264815644.apps.googleusercontent.com"
+_GSEC="X4Z3ca8xfWDb1Voo-F9a7ZxJ"
+
+get_token() {
+ local tok
+ tok=$(curl -sf --max-time 10 -X POST "https://oauth2.googleapis.com/token" \
+ -d "client_id=$_GCID&client_secret=$_GSEC&refresh_token=$GDRIVE_REFRESH_TOKEN&grant_type=refresh_token" \
+ 2>/dev/null) || die "token refresh failed"
+ echo "$tok" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])" 2>/dev/null \
+ || die "token parse failed"
+}
+
+CMD="${1:-}"
+shift 2>/dev/null || true
+
+case "$CMD" in
+ test)
+ TOKEN=$(get_token)
+ curl -sf --max-time 10 \
+ -H "Authorization: Bearer $TOKEN" \
+ "https://www.googleapis.com/drive/v3/about?fields=user" >/dev/null 2>&1 \
+ || die "Drive API unreachable"
+ printf '{"ok":true,"source":"gdrive"}\n'
+ exit 0
+ ;;
+ search)
+ QUERY="${1:-}"
+ MAX="${2:-20}"
+ [[ -z "$QUERY" ]] && die "usage: edge-gdrive search <query> [max]"
+ TOKEN=$(get_token)
+ ENC_Q=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$QUERY")
+ RESP=$(curl -sf --max-time 20 \
+ -H "Authorization: Bearer $TOKEN" \
+ "https://www.googleapis.com/drive/v3/files?q=fullText+contains+'${ENC_Q}'+and+trashed=false&pageSize=${MAX}&fields=files(id,name,mimeType,modifiedTime,webViewLink,size)&orderBy=modifiedTime+desc" \
+ 2>/dev/null) || die "Drive search failed"
+ echo "$RESP" | python3 -c "
+import sys, json
+raw = json.load(sys.stdin)
+items = []
+for f in raw.get('files', []):
+ items.append({'id': f['id'], 'name': f.get('name',''), 'mime': f.get('mimeType',''),
+ 'modified': (f.get('modifiedTime') or '')[:10], 'url': f.get('webViewLink',''),
+ 'size': int(f.get('size', 0) or 0), 'source': 'gdrive'})
+json.dump({'ok': True, 'source': 'gdrive', 'count': len(items), 'results': items}, sys.stdout, ensure_ascii=False)
+print()
+"
+ exit 0
+ ;;
+ *)
+ die "usage: edge-gdrive {test|search} [args]"
+ ;;
+esac
new file mode 100755
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+# edge-github-consorcio — AssertIA codebase in Consorcio-Neuralmind-Terranova org.
+# Contract: exit 0 + JSON stdout on success, exit 1 + JSON error on failure.
+set -uo pipefail
+EDGE_DIR="${EDGE_DIR:-$HOME/edge}"
+[[ -f "$EDGE_DIR/secrets/keys.env" ]] && { set -a; source "$EDGE_DIR/secrets/keys.env"; set +a; }
+die() { printf '{"ok":false,"error":"%s","source":"github-consorcio"}\n' "$1"; exit 1; }
+[[ -z "${GH_TOKEN_CONSORCIO:-}" ]] && die "GH_TOKEN_CONSORCIO not set"
+ORG="Consorcio-Neuralmind-Terranova"; API="https://api.github.com"
+AUTH=(-sf --max-time 20 -H "Authorization: token $GH_TOKEN_CONSORCIO" -H "Accept: application/vnd.github+json" -H "User-Agent: edge-github-consorcio/1.0")
+CMD="${1:-}"; shift 2>/dev/null || true
+case "$CMD" in
+ test) printf '{"ok":true,"source":"github-consorcio"}\n'; exit 0 ;;
+ repos)
+ FILTER="${1:-}"
+ RESP=$(curl "${AUTH[@]}" "$API/orgs/$ORG/repos?per_page=100&sort=updated" 2>/dev/null) || die "API request failed"
+ echo "$RESP" | python3 -c "
+import sys,json; data=json.load(sys.stdin); f='$FILTER'.lower()
+items=[{'name':r['full_name'],'url':r['html_url'],'description':(r.get('description')or'')[:150],
+ 'updated':r.get('updated_at','')[:10],'language':r.get('language')or'','stars':r.get('stargazers_count',0),
+ 'private':r.get('private',False),'source':'github-consorcio'}
+ for r in data if not f or f in r['full_name'].lower() or f in (r.get('description')or'').lower()]
+json.dump({'ok':True,'source':'github-consorcio','count':len(items),'results':items},sys.stdout,ensure_ascii=False); print()"
+ exit 0 ;;
+ search)
+ QUERY="${1:-}"; [[ -z "$QUERY" ]] && die "usage: edge-github-consorcio search <query>"
+ ENC=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$QUERY")
+ RESP=$(curl "${AUTH[@]}" "$API/search/code?q=${ENC}+org:${ORG}&per_page=20" 2>/dev/null) || die "search API failed"
+ echo "$RESP" | python3 -c "
+import sys,json; data=json.load(sys.stdin)
+items=[{'path':r['path'],'repo':r['repository']['full_name'],'url':r['html_url'],
+ 'score':r.get('score',0),'source':'github-consorcio'} for r in data.get('items',[])]
+json.dump({'ok':True,'source':'github-consorcio','count':len(items),'results':items},sys.stdout,ensure_ascii=False); print()"
+ exit 0 ;;
+ activity)
+ REPO="${1:-}"; [[ -z "$REPO" ]] && die "usage: edge-github-consorcio activity <repo-name>"
+ RESP=$(curl "${AUTH[@]}" "$API/repos/$ORG/$REPO/commits?per_page=15" 2>/dev/null) || die "commits API failed"
+ echo "$RESP" | python3 -c "
+import sys,json; data=json.load(sys.stdin)
+items=[{'sha':c['sha'][:7],'message':(c['commit']['message'].split('\n')[0])[:120],
+ 'author':c['commit']['author']['name'],'date':c['commit']['author']['date'][:10],
+ 'url':c['html_url'],'source':'github-consorcio'} for c in data]
+json.dump({'ok':True,'source':'github-consorcio','count':len(items),'results':items},sys.stdout,ensure_ascii=False); print()"
+ exit 0 ;;
+ *) die "usage: edge-github-consorcio {test|repos|search|activity} [args]" ;;
+esac
new file mode 100755
@@ -0,0 +1,53 @@
+#!/usr/bin/env bash
+# edge-github-tcu-cex — TCU-CEX production org (~200+ repos). Doc_AssertIA (canonical),
+# competing products (alice-360, chattcu, travesia, gabi, simone, aihelper). Uses GH_TOKEN_TCU_CEX.
+# Contract: exit 0 + JSON stdout on success, exit 1 + JSON error on failure.
+set -uo pipefail
+EDGE_DIR="${EDGE_DIR:-$HOME/edge}"
+[[ -f "$EDGE_DIR/secrets/keys.env" ]] && { set -a; source "$EDGE_DIR/secrets/keys.env"; set +a; }
+die() { printf '{"ok":false,"error":"%s","source":"github-tcu-cex"}\n' "$1"; exit 1; }
+[[ -z "${GH_TOKEN_TCU_CEX:-}" ]] && die "GH_TOKEN_TCU_CEX not set"
+ORG="TCU-CEX"; API="https://api.github.com"
+AUTH=(-sf --max-time 20 -H "Authorization: token $GH_TOKEN_TCU_CEX" -H "Accept: application/vnd.github+json" -H "User-Agent: edge-github-tcu-cex/1.0")
+CMD="${1:-}"; shift 2>/dev/null || true
+case "$CMD" in
+ test) printf '{"ok":true,"source":"github-tcu-cex"}\n'; exit 0 ;;
+ repos)
+ FILTER="${1:-}"; PAGE=1; ALL="[]"
+ while :; do
+ RESP=$(curl "${AUTH[@]}" "$API/orgs/$ORG/repos?per_page=100&sort=updated&page=$PAGE" 2>/dev/null) || die "API request failed"
+ N=$(echo "$RESP" | python3 -c "import sys,json;print(len(json.load(sys.stdin)))" 2>/dev/null) || die "parse error"
+ ALL=$(printf '%s\n%s' "$ALL" "$RESP" | python3 -c "
+import sys,json; a=json.load(sys.stdin); b=json.load(sys.stdin); json.dump(a+b,sys.stdout)")
+ [[ "$N" -lt 100 ]] && break; ((PAGE++))
+ done
+ echo "$ALL" | python3 -c "
+import sys,json; data=json.load(sys.stdin); f='$FILTER'.lower()
+items=[{'name':r['full_name'],'url':r['html_url'],'description':(r.get('description')or'')[:150],
+ 'updated':r.get('updated_at','')[:10],'language':r.get('language')or'','stars':r.get('stargazers_count',0),
+ 'private':r.get('private',False),'source':'github-tcu-cex'}
+ for r in data if not f or f in r['full_name'].lower() or f in (r.get('description')or'').lower()]
+json.dump({'ok':True,'source':'github-tcu-cex','count':len(items),'results':items},sys.stdout,ensure_ascii=False); print()"
+ exit 0 ;;
+ search)
+ QUERY="${1:-}"; [[ -z "$QUERY" ]] && die "usage: edge-github-tcu-cex search <query>"
+ ENC=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$QUERY")
+ RESP=$(curl "${AUTH[@]}" "$API/search/code?q=${ENC}+org:${ORG}&per_page=20" 2>/dev/null) || die "search API failed"
+ echo "$RESP" | python3 -c "
+import sys,json; data=json.load(sys.stdin)
+items=[{'path':r['path'],'repo':r['repository']['full_name'],'url':r['html_url'],
+ 'score':r.get('score',0),'source':'github-tcu-cex'} for r in data.get('items',[])]
+json.dump({'ok':True,'source':'github-tcu-cex','count':len(items),'results':items},sys.stdout,ensure_ascii=False); print()"
+ exit 0 ;;
+ activity)
+ REPO="${1:-}"; [[ -z "$REPO" ]] && die "usage: edge-github-tcu-cex activity <repo-name>"
+ RESP=$(curl "${AUTH[@]}" "$API/repos/$ORG/$REPO/commits?per_page=15" 2>/dev/null) || die "commits API failed"
+ echo "$RESP" | python3 -c "
+import sys,json; data=json.load(sys.stdin)
+items=[{'sha':c['sha'][:7],'message':(c['commit']['message'].split('\n')[0])[:120],
+ 'author':c['commit']['author']['name'],'date':c['commit']['author']['date'][:10],
+ 'url':c['html_url'],'source':'github-tcu-cex'} for c in data]
+json.dump({'ok':True,'source':'github-tcu-cex','count':len(items),'results':items},sys.stdout,ensure_ascii=False); print()"
+ exit 0 ;;
+ *) die "usage: edge-github-tcu-cex {test|repos|search|activity} [args]" ;;
+esac
new file mode 100755
@@ -0,0 +1,52 @@
+#!/usr/bin/env bash
+# edge-roberto-blog — R&D signal from peer agent roberto via SSH.
+# Contract: exit 0 + JSON stdout on success, exit 1 + JSON error on failure.
+set -uo pipefail
+EDGE_DIR="${EDGE_DIR:-$HOME/edge}"
+[[ -f "$EDGE_DIR/secrets/keys.env" ]] && { set -a; source "$EDGE_DIR/secrets/keys.env"; set +a; }
+die() { printf '{"ok":false,"error":"%s","source":"roberto-blog"}\n' "$1" >&1; exit 1; }
+KEY="${DRUCKER_SSH_ROBERTO_KEY:-$EDGE_DIR/secrets/ssh_roberto}"
+[[ "$KEY" != /* ]] && KEY="$EDGE_DIR/secrets/$KEY"
+[[ -f "$KEY" ]] || die "SSH key not found: $KEY"
+SSH="ssh -i $KEY -o StrictHostKeyChecking=no -o ConnectTimeout=5 -o BatchMode=yes joao@216.238.118.21"
+CMD="${1:-}"; shift 2>/dev/null || true
+
+parse_entries() {
+ local max_body="${1:-500}"
+ python3 -c "
+import sys,json,re
+raw=sys.stdin.read(); items=[]; path=''
+for b in re.split(r'===F:(.*?)===\n', raw):
+ s=b.strip()
+ if not s: continue
+ if s.startswith('/'): path=s; continue
+ L=s.split('\n')
+ g=lambda k: next((l.split(':',1)[1].strip().strip('\"') for l in L if l.startswith(k+':')), '')
+ t=g('title') or path.rsplit('/',1)[-1]; d=g('date'); tg=g('tags')
+ bs=next((i for i,l in enumerate(L) if l.strip()=='---' and i>0), len(L))
+ body='\n'.join(L[bs+1:]).strip()
+ if $max_body > 0: body=body[:$max_body]
+ items.append({'title':t,'date':d,'tags':tg,'body':body,'file':path})
+json.dump({'ok':True,'source':'roberto-blog','count':len(items),'results':items},sys.stdout,ensure_ascii=False)
+print()
+"
+}
+
+case "$CMD" in
+ test)
+ $SSH "echo ok" >/dev/null 2>&1 || die "SSH connection failed"
+ printf '{"ok":true,"source":"roberto-blog"}\n'; exit 0 ;;
+ entries)
+ MAX="${1:-5}"
+ $SSH "ls -1t ~/edge/blog/entries/*.md 2>/dev/null|head -$MAX|xargs -I{} sh -c 'echo ===F:{}===;cat {}'" 2>/dev/null \
+ | parse_entries 500; exit 0 ;;
+ briefing)
+ RAW=$($SSH "cat ~/edge/briefing.md 2>/dev/null" 2>/dev/null) || die "briefing not found"
+ python3 -c "import sys,json;json.dump({'ok':True,'source':'roberto-blog','type':'briefing','content':sys.stdin.read()},sys.stdout,ensure_ascii=False);print()" <<< "$RAW"
+ exit 0 ;;
+ experiments)
+ MAX="${1:-5}"
+ $SSH "ls -1t ~/edge/blog/entries/*.md 2>/dev/null|xargs grep -l 'type:.*experiment\|tags:.*experiment' 2>/dev/null|head -$MAX|xargs -I{} sh -c 'echo ===F:{}===;head -30 {}'" 2>/dev/null \
+ | parse_entries 0; exit 0 ;;
+ *) die "usage: edge-roberto-blog {test|entries|briefing|experiments} [args]" ;;
+esac
new file mode 100755
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+# edge-slack — Consórcio Neuralmind-Terranova Slack. Read any channel, write ONLY to etec-estrategico.
+# Contract: exit 0 + JSON stdout on success, exit 1 + JSON error on failure.
+set -uo pipefail
+EDGE_DIR="${EDGE_DIR:-$HOME/edge}"
+[[ -f "$EDGE_DIR/secrets/keys.env" ]] && { set -a; source "$EDGE_DIR/secrets/keys.env"; set +a; }
+WCH="C0AQYCN8UJE" # etec-estrategico — the ONLY writable channel
+API="https://slack.com/api"
+die() { printf '{"ok":false,"error":"%s","source":"slack"}\n' "$1" >&1; exit 1; }
+slk() { curl -sf --max-time 20 -H "Authorization: Bearer $SLACK_BOT_TOKEN" "$@" 2>/dev/null; }
+chk() { python3 -c "import sys,json;r=json.load(sys.stdin)
+if not r.get('ok'):print(json.dumps({'ok':False,'error':r.get('error','unknown'),'source':'slack'}));sys.exit(1)
+$1";}
+[[ -z "${SLACK_BOT_TOKEN:-}" ]] && die "SLACK_BOT_TOKEN not set"
+CMD="${1:-}"; shift 2>/dev/null || true
+case "$CMD" in
+ test)
+ printf '{"ok":true,"source":"slack"}\n'; exit 0 ;;
+ channels)
+ RESP=$(slk "$API/conversations.list?types=public_channel,private_channel&exclude_archived=true&limit=200") \
+ || die "conversations.list failed"
+ echo "$RESP" | chk "
+cs=[{'id':c['id'],'name':c.get('name',''),'members':c.get('num_members',0)} for c in r.get('channels',[])]
+json.dump({'ok':True,'source':'slack','count':len(cs),'channels':cs},sys.stdout,ensure_ascii=False);print()"
+ exit 0 ;;
+ history)
+ CH="${1:-}"; LIMIT="${2:-30}"
+ [[ -z "$CH" ]] && die "usage: edge-slack history <channel_id> [limit]"
+ RESP=$(slk "$API/conversations.history?channel=${CH}&limit=${LIMIT}") \
+ || die "conversations.history failed"
+ echo "$RESP" | chk "
+ms=[{'ts':m['ts'],'user':m.get('user','bot'),'text':m.get('text','')[:500]} for m in r.get('messages',[])]
+json.dump({'ok':True,'source':'slack','count':len(ms),'messages':ms},sys.stdout,ensure_ascii=False);print()"
+ exit 0 ;;
+ post)
+ TEXT="${1:-}"
+ [[ -z "$TEXT" ]] && die "usage: edge-slack post <text>"
+ BODY=$(python3 -c "import json,sys;print(json.dumps({'channel':'$WCH','text':sys.argv[1]}))" "$TEXT")
+ RESP=$(slk -X POST -H "Content-Type: application/json" -d "$BODY" "$API/chat.postMessage") \
+ || die "chat.postMessage failed"
+ echo "$RESP" | chk "
+json.dump({'ok':True,'source':'slack','channel':'$WCH','ts':r.get('ts','')},sys.stdout);print()"
+ exit 0 ;;
+ *)
+ die "usage: edge-slack {test|channels|history|post} [args]" ;;
+esac
new file mode 100755
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+# edge-x-twitter — Real-time signal from AI labs, legal-AI researchers, audit accounts.
+# Contract: exit 0 + JSON stdout on success, exit 1 + JSON error on failure.
+set -uo pipefail
+EDGE_DIR="${EDGE_DIR:-$HOME/edge}"
+KEYS_ENV="$EDGE_DIR/secrets/keys.env"
+[[ -f "$KEYS_ENV" ]] && { set -a; source "$KEYS_ENV"; set +a; }
+
+die() { printf '{"ok":false,"error":"%s","source":"x-twitter"}\n' "$1" >&1; exit 1; }
+
+[[ -z "${X_BEARER_TOKEN:-}" ]] && die "X_BEARER_TOKEN not set"
+
+CMD="${1:-}"
+shift 2>/dev/null || true
+
+case "$CMD" in
+ test)
+ printf '{"ok":true,"source":"x-twitter"}\n'
+ exit 0
+ ;;
+ search)
+ QUERY="${1:-}"
+ MAX="${2:-10}"
+ [[ -z "$QUERY" ]] && die "usage: edge-x-twitter search <query> [max_results]"
+ (( MAX < 10 )) && MAX=10
+ (( MAX > 100 )) && MAX=100
+ ENC_QUERY=$(python3 -c "import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))" "$QUERY")
+ URL="https://api.twitter.com/2/tweets/search/recent?query=${ENC_QUERY}%20-is:retweet&max_results=${MAX}&tweet.fields=created_at,public_metrics,author_id&expansions=author_id&user.fields=username,name,public_metrics"
+ RESP=$(curl -sf --max-time 20 \
+ -H "Authorization: Bearer $X_BEARER_TOKEN" \
+ -H "User-Agent: edge-x-twitter/1.0" \
+ "$URL" 2>/dev/null) || die "X API request failed"
+ echo "$RESP" | python3 -c "
+import sys, json
+raw = json.load(sys.stdin)
+users = {u['id']: u for u in (raw.get('includes', {}).get('users', []))}
+items = []
+for t in raw.get('data', []):
+ m = t.get('public_metrics', {})
+ u = users.get(t.get('author_id', ''), {})
+ um = u.get('public_metrics', {})
+ eng = m.get('like_count',0) + m.get('retweet_count',0)*3 + m.get('reply_count',0)*2
+ items.append({'username': u.get('username','?'), 'text': t.get('text','')[:280],
+ 'url': 'https://x.com/{}/status/{}'.format(u.get('username','_'), t['id']),
+ 'likes': m.get('like_count',0), 'rts': m.get('retweet_count',0),
+ 'replies': m.get('reply_count',0), 'followers': um.get('followers_count',0),
+ 'created': t.get('created_at','')[:10], 'score': eng, 'source': 'x-twitter'})
+items.sort(key=lambda x: x['score'], reverse=True)
+json.dump({'ok': True, 'source': 'x-twitter', 'count': len(items), 'results': items}, sys.stdout, ensure_ascii=False)
+print()
+"
+ exit 0
+ ;;
+ *)
+ die "usage: edge-x-twitter {test|search} [args]"
+ ;;
+esac
old mode 100644
new mode 100755