Initial commit: RA-H Open Source Edition

Local-first knowledge management system with BYO API keys.

Features:
- 3-panel UI (Nodes | Focus | Helpers)
- SQLite + sqlite-vec for vector search
- Agent system (Easy/Hard mode orchestrators)
- Content extraction (YouTube, PDF, web)
- Integrate workflow for connection discovery
- Dimension system with auto-assignment

Tech stack:
- Next.js 15 + TypeScript + Tailwind CSS
- Anthropic (Claude) + OpenAI (GPT) via Vercel AI SDK

Setup:
  npm install && npm rebuild better-sqlite3
  scripts/dev/bootstrap-local.sh
  npm run dev

MIT License
This commit is contained in:
“BeeRad”
2025-12-15 16:14:28 +11:00
commit 733d1c3407
226 changed files with 46231 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
TMP_FILE="$(mktemp)"
printf "[audit] scanning for accidental API keys (sk-)...\n"
if rg --hidden --no-messages --files-with-matches "sk-" \
--glob '!.git/**' --glob '!node_modules/**' --glob '!dist/**' --glob '!apps/mac/src-tauri/target/**' \
> "$TMP_FILE"; then
printf "${RED}Found potential secrets:${NC}\n"
cat "$TMP_FILE"
rm -f "$TMP_FILE"
exit 1
else
status=$?
if [ "$status" -ne 1 ]; then
rm -f "$TMP_FILE"
exit "$status"
fi
printf "${GREEN}No sk- tokens found.${NC}\n"
fi
rm -f "$TMP_FILE"
printf "[audit] checking tracked files > 50MB...\n"
LARGE_FILES=$(git ls-tree -r HEAD --long | awk '$4 > 52428800 {printf "%s\t%s\n", $4, $5}')
if [ -n "$LARGE_FILES" ]; then
printf "${RED}Large tracked files detected (>50MB):${NC}\n%s\n" "$LARGE_FILES"
exit 1
else
printf "${GREEN}No tracked blobs exceed 50MB.${NC}\n"
fi
printf "[audit] done.\n"
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
ENV_TEMPLATE="${REPO_DIR}/.env.example.local"
TARGET_ENV="${REPO_DIR}/.env.local"
SQLITE_SEED_SCRIPT="${REPO_DIR}/scripts/database/sqlite-ensure-app-schema.sh"
log() {
echo "[bootstrap-local] $1"
}
if [ ! -f "$ENV_TEMPLATE" ]; then
echo "Error: ${ENV_TEMPLATE} not found. Make sure .env.example.local exists." >&2
exit 1
fi
if [ -f "$TARGET_ENV" ]; then
log ".env.local already exists; leaving it untouched."
else
log "Creating .env.local from template..."
cp "$ENV_TEMPLATE" "$TARGET_ENV"
fi
SQLITE_DB_PATH_LINE=$(grep -E '^SQLITE_DB_PATH=' "$TARGET_ENV" | tail -n 1 || true)
DEFAULT_DB_PATH="$HOME/Library/Application Support/RA-H/db/rah.sqlite"
if [ -z "$SQLITE_DB_PATH_LINE" ]; then
SQLITE_DB_PATH="$DEFAULT_DB_PATH"
else
SQLITE_DB_PATH="${SQLITE_DB_PATH_LINE#SQLITE_DB_PATH=}"
fi
# Expand variables like $HOME or ~
EXPANDED_DB_PATH=$(eval "echo \"$SQLITE_DB_PATH\"")
DB_DIR=$(dirname "$EXPANDED_DB_PATH")
log "Ensuring database directory exists: $DB_DIR"
mkdir -p "$DB_DIR"
if [ ! -f "$EXPANDED_DB_PATH" ]; then
log "Creating empty SQLite database at $EXPANDED_DB_PATH"
: > "$EXPANDED_DB_PATH"
else
log "SQLite database already exists at $EXPANDED_DB_PATH"
fi
if [ ! -x "$SQLITE_SEED_SCRIPT" ]; then
echo "Error: $SQLITE_SEED_SCRIPT not found or not executable" >&2
exit 1
fi
log "Seeding database schema via sqlite-ensure-app-schema.sh"
"$SQLITE_SEED_SCRIPT" "$EXPANDED_DB_PATH"
log "Bootstrap complete. Run 'npm run dev:local' to start the local-first app."
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
INFO() { printf "[clean-local] %s\n" "$1"; }
maybe_remove_dir() {
local target="$1"
if [ -d "$target" ]; then
rm -rf "$target"
INFO "Removed directory $target"
fi
}
maybe_remove_file() {
local target="$1"
if [ -f "$target" ]; then
rm -f "$target"
INFO "Removed file $target"
fi
}
INFO "Cleaning generated artifacts (safe-only)"
maybe_remove_dir "$REPO_ROOT/dist/local-app"
maybe_remove_dir "$REPO_ROOT/dist/runtime"
maybe_remove_dir "$REPO_ROOT/dist/.staging"
maybe_remove_dir "$REPO_ROOT/.next"
maybe_remove_dir "$REPO_ROOT/apps/mac/dist"
maybe_remove_file "$REPO_ROOT/dist/index.html"
maybe_remove_file "$REPO_ROOT/logs/helper-interactions.log"
maybe_remove_file "$REPO_ROOT/logs/next-dev.log"
maybe_remove_file "$REPO_ROOT/app/api/logs/requests.log"
INFO "Cleanup complete"
+96
View File
@@ -0,0 +1,96 @@
#!/usr/bin/env node
/*
Dev utility: Inspect memory claims ledger and write a Markdown report.
Usage: node scripts/dev/inspect-memory-ledger.js [--out docs/development/reports/memory-ledger.md] [--versions 5]
Optionally set RAH_DB_PATH to override the default DB path.
*/
const fs = require('fs');
const path = require('path');
const os = require('os');
const { execFileSync } = require('child_process');
const args = process.argv.slice(2);
const outIdx = args.indexOf('--out');
const outPath = outIdx >= 0 ? args[outIdx + 1] : 'docs/development/reports/memory-ledger.md';
const verIdx = args.indexOf('--versions');
const maxVersions = verIdx >= 0 ? parseInt(args[verIdx + 1], 10) : 5;
const defaultDbPath = path.join(os.homedir(), 'Library', 'Application Support', 'RA-H', 'db', 'rah.sqlite');
const dbPath = process.env.RAH_DB_PATH || defaultDbPath;
function q(dbPath, sql, params = []) {
const paramList = params.map(p => typeof p === 'number' ? String(p) : `'${String(p).replace(/'/g, "''")}'`).join(',');
const wrapped = params.length ? sql.replace(/\?/g, () => params.shift()) : sql;
try {
const out = execFileSync('sqlite3', ['-json', dbPath, wrapped], { encoding: 'utf8' });
return JSON.parse(out || '[]');
} catch (e) {
console.error('sqlite3 query failed:', e.message);
process.exit(1);
}
}
function loadCategoryRows(dbPath, category, limit) {
const sql = `SELECT id, version, is_current, content, metadata, created_at
FROM memory
WHERE type='big_memory' AND entity_id='${category}'
ORDER BY version DESC
LIMIT ${limit}`;
return q(dbPath, sql);
}
function parseMeta(m) {
try { return m ? JSON.parse(m) : {}; } catch { return {}; }
}
function renderTable(rowsLatest, rowsPrev) {
// rowsLatest: {claim, count, first_seen, last_seen}
// Map previous counts for delta
const prevMap = new Map(rowsPrev.map(r => [r.claim, r.count]));
const header = `| claim | count | Δ | first_seen | last_seen |\n|---|---:|---:|---|---|`;
const lines = rowsLatest.map(r => {
const prev = prevMap.get(r.claim) || 0;
const delta = r.count - prev;
return `| ${r.claim} | ${r.count} | ${delta >= 0 ? '+'+delta : delta} | ${r.first_seen} | ${r.last_seen} |`;
});
return [header, ...lines].join('\n');
}
function main() {
const categories = ['world_model','interests','goals','styles','connections'];
let md = `# Memory Ledger Report\n\n- DB: \`${dbPath}\`\n- Generated: ${new Date().toISOString()}\n- Versions shown per category: ${maxVersions}\n\n`;
for (const cat of categories) {
const rows = loadCategoryRows(dbPath, cat, maxVersions);
if (!rows || rows.length === 0) {
md += `## ${cat}\n(No rows)\n\n`;
continue;
}
const current = rows[0];
const prev = rows[1] || null;
const curMeta = parseMeta(current.metadata);
const prevMeta = parseMeta(prev && prev.metadata);
const curClaims = curMeta.claims || {};
const prevClaims = prevMeta.claims || {};
const toArr = (obj) => Object.entries(obj).map(([k, v]) => ({ claim: k, count: Number(v.count || 0), first_seen: v.first_seen || '', last_seen: v.last_seen || '' }));
const curArr = toArr(curClaims).sort((a,b) => b.count - a.count || (a.last_seen > b.last_seen ? -1 : 1)).slice(0, 50);
const prevArr = toArr(prevClaims).sort((a,b) => b.count - a.count).slice(0, 50);
md += `## ${cat}\n\n`;
md += `- current version: ${current.version} (created ${current.created_at})\n`;
if (prev) md += `- previous version: ${prev.version} (created ${prev.created_at})\n`;
md += `- current paragraph (first 200 chars):\n\n> ${String(current.content || '').slice(0,200)}\n\n`;
md += `### Top Claims (current vs previous Δ)\n\n`;
md += renderTable(curArr, prevArr) + '\n\n';
}
// Ensure folder exists
const outAbs = path.resolve(outPath);
fs.mkdirSync(path.dirname(outAbs), { recursive: true });
fs.writeFileSync(outAbs, md, 'utf8');
console.log(`Wrote report: ${outAbs}`);
}
main();
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
REPO_DIR="$(cd "${ROOT_DIR}/.." && pwd)"
PORT="${PORT:-3000}"
HOST="127.0.0.1"
NEXT_LOG="${REPO_DIR}/logs/next-dev.log"
mkdir -p "${REPO_DIR}/logs"
cleanup() {
if [[ -n "${NEXT_PID:-}" ]]; then
echo "Shutting down Next.js dev server (pid ${NEXT_PID})"
kill "${NEXT_PID}" 2>/dev/null || true
wait "${NEXT_PID}" 2>/dev/null || true
fi
}
trap cleanup EXIT INT TERM
echo "Starting Next.js dev server on http://${HOST}:${PORT}"
HOST="${HOST}" PORT="${PORT}" npm run dev > "${NEXT_LOG}" 2>&1 &
NEXT_PID=$!
sleep 1
echo -n "Waiting for Next.js to become ready"
for _ in $(seq 1 90); do
if nc -z "${HOST}" "${PORT}" 2>/dev/null; then
echo " ✓"
break
fi
echo -n "."
sleep 1
done
if ! nc -z "${HOST}" "${PORT}" 2>/dev/null; then
echo " ✗ Next.js did not start within 90s. Last log lines:"
tail -n 40 "${NEXT_LOG}" || true
exit 1
fi
echo "Next.js ready. Launching Tauri shell."
RAH_ATTACH_DEV_SERVER=true \
RAH_DEV_SERVER_PORT="${PORT}" \
PORT="${PORT}" \
HOST="${HOST}" \
npm run tauri:dev --workspace @ra-h/mac-shell