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:
Executable
+39
@@ -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"
|
||||
Executable
+56
@@ -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."
|
||||
Executable
+38
@@ -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"
|
||||
@@ -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();
|
||||
Executable
+47
@@ -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
|
||||
Reference in New Issue
Block a user