unoverse 0.1.18 → 0.1.19
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.
- package/bin/unoverse.mjs +10 -7
- package/lib/create.mjs +8 -3
- package/operator/lib/check.sh +97 -0
- package/operator/lib/common.sh +58 -0
- package/operator/lib/dashboard.sh +85 -0
- package/operator/lib/db-setup.sh +145 -0
- package/operator/lib/db-verify.sh +243 -0
- package/operator/lib/deploy.sh +176 -0
- package/operator/lib/dev.sh +90 -0
- package/operator/lib/doctor.sh +180 -0
- package/operator/lib/find-root.sh +28 -0
- package/operator/lib/ground.sh +252 -0
- package/operator/lib/help.sh +36 -0
- package/operator/lib/init.sh +247 -0
- package/operator/lib/logs.sh +23 -0
- package/operator/lib/publish-assets.mjs +116 -0
- package/operator/lib/start.sh +213 -0
- package/operator/lib/stop.sh +11 -0
- package/operator/lib/update.sh +330 -0
- package/operator/operator.sh +117 -0
- package/package.json +5 -1
package/bin/unoverse.mjs
CHANGED
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync } from "node:fs";
|
|
11
11
|
import { spawnSync } from "node:child_process";
|
|
12
|
-
import { join, resolve } from "node:path";
|
|
12
|
+
import { join, resolve, dirname } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
13
14
|
import { create } from "../lib/create.mjs";
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -20,15 +21,17 @@ import { create } from "../lib/create.mjs";
|
|
|
20
21
|
* root. Found means the operator commands exist; not found means they simply are not
|
|
21
22
|
* offered, instead of being advertised and then refusing.
|
|
22
23
|
*/
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
|
|
24
|
+
// THE OPERATOR SHIPS WITH THIS PACKAGE, not with the universe. A universe is a folder
|
|
25
|
+
// holding a docker-compose.yml, and nothing else: it carries no tooling to keep in step.
|
|
26
|
+
// The scripts act on that folder (each resolves $ROOT by walking up for the compose
|
|
27
|
+
// file), so the code is global and the data is local.
|
|
28
|
+
const VENDORED = join(dirname(fileURLToPath(import.meta.url)), "../operator/operator.sh");
|
|
29
|
+
const IN_REPO = join(dirname(fileURLToPath(import.meta.url)), "../../../scripts/operator.sh");
|
|
30
|
+
const OPERATOR = existsSync(VENDORED) ? VENDORED : IN_REPO;
|
|
26
31
|
|
|
27
32
|
function findUniverse() {
|
|
28
33
|
for (let dir = process.cwd(); ; ) {
|
|
29
|
-
|
|
30
|
-
if (existsSync(join(dir, rel))) return { root: dir, script: join(dir, rel) };
|
|
31
|
-
}
|
|
34
|
+
if (existsSync(join(dir, "docker-compose.yml"))) return { root: dir, script: OPERATOR };
|
|
32
35
|
const parent = resolve(dir, "..");
|
|
33
36
|
if (parent === dir) return null;
|
|
34
37
|
dir = parent;
|
package/lib/create.mjs
CHANGED
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* a real auth round-trip against the registry, not a format check.
|
|
9
9
|
*/
|
|
10
10
|
import { createInterface } from "node:readline/promises";
|
|
11
|
+
import { dirname, resolve } from "node:path";
|
|
12
|
+
import { fileURLToPath } from "node:url";
|
|
11
13
|
import { existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
12
14
|
import { spawnSync } from "node:child_process";
|
|
13
15
|
|
|
@@ -242,9 +244,12 @@ export async function create(nameArg) {
|
|
|
242
244
|
// already in hand and already validated, so hand it over and configure now.
|
|
243
245
|
rl.close();
|
|
244
246
|
console.log("");
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
247
|
+
// This package's own operator, run against the folder just scaffolded.
|
|
248
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
249
|
+
const vendored = resolve(here, "../operator/operator.sh");
|
|
250
|
+
const operatorScript = existsSync(vendored)
|
|
251
|
+
? vendored
|
|
252
|
+
: resolve(here, "../../../scripts/operator.sh");
|
|
248
253
|
const r = spawnSync("bash", [operatorScript, "init"], {
|
|
249
254
|
stdio: "inherit",
|
|
250
255
|
cwd: name,
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# unoverse check
|
|
3
|
+
|
|
4
|
+
cmd_check() {
|
|
5
|
+
echo ""
|
|
6
|
+
echo -e " ${BOLD}Unoverse Platform Health Check${NC}"
|
|
7
|
+
echo ""
|
|
8
|
+
local pass=0 total=0
|
|
9
|
+
|
|
10
|
+
# 1. Services
|
|
11
|
+
for svc in unoverse canvas umap memory; do
|
|
12
|
+
total=$((total + 1))
|
|
13
|
+
local status
|
|
14
|
+
status=$(docker compose -f "$ROOT/docker-compose.yml" ps --format '{{.Status}}' "$svc" 2>/dev/null | head -1)
|
|
15
|
+
if echo "$status" | grep -qi "up"; then
|
|
16
|
+
ok "$svc"
|
|
17
|
+
pass=$((pass + 1))
|
|
18
|
+
elif echo "$status" | grep -qi "created"; then
|
|
19
|
+
fail "$svc ${DIM}(stuck in Created, the container never started)${NC}"
|
|
20
|
+
elif [ -z "$status" ]; then
|
|
21
|
+
fail "$svc ${DIM}(no container found)${NC}"
|
|
22
|
+
else
|
|
23
|
+
fail "$svc ${DIM}($status)${NC}"
|
|
24
|
+
fi
|
|
25
|
+
done
|
|
26
|
+
echo ""
|
|
27
|
+
|
|
28
|
+
# 2. Health endpoints
|
|
29
|
+
for endpoint in 4105:unoverse 4101:engine 5001:umap 4104:memory; do
|
|
30
|
+
local port="${endpoint%%:*}" name="${endpoint##*:}"
|
|
31
|
+
total=$((total + 1))
|
|
32
|
+
local code
|
|
33
|
+
code=$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:$port/health" 2>/dev/null)
|
|
34
|
+
if [ "$code" = "200" ]; then
|
|
35
|
+
ok "$name health ${DIM}:$port${NC}"
|
|
36
|
+
pass=$((pass + 1))
|
|
37
|
+
else
|
|
38
|
+
fail "$name health ${DIM}:$port → $code${NC}"
|
|
39
|
+
fi
|
|
40
|
+
done
|
|
41
|
+
echo ""
|
|
42
|
+
|
|
43
|
+
# 3. Packages built
|
|
44
|
+
local built=0 pkg_total=0
|
|
45
|
+
for pkg in "$ROOT"/packages/*/; do
|
|
46
|
+
[ -f "$pkg/package.json" ] || continue
|
|
47
|
+
local name
|
|
48
|
+
name=$(basename "$pkg")
|
|
49
|
+
case "$name" in marketplace|gravity-client|plugin-base|skills|prompt-blocks) continue;; esac
|
|
50
|
+
pkg_total=$((pkg_total + 1))
|
|
51
|
+
if [ -f "$pkg/dist/index.js" ]; then
|
|
52
|
+
built=$((built + 1))
|
|
53
|
+
else
|
|
54
|
+
fail "$name ${DIM}(missing dist/index.js)${NC}"
|
|
55
|
+
fi
|
|
56
|
+
done
|
|
57
|
+
total=$((total + 1))
|
|
58
|
+
if [ "$built" -eq "$pkg_total" ]; then
|
|
59
|
+
ok "$built/$pkg_total packages built"
|
|
60
|
+
pass=$((pass + 1))
|
|
61
|
+
else
|
|
62
|
+
fail "$built/$pkg_total packages built"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# 4. Unoverse node catalog
|
|
66
|
+
total=$((total + 1))
|
|
67
|
+
local plugin_count
|
|
68
|
+
# Catalog lives on unoverse; :4106 is Docker-internal and :4105 /plugins is JWT-gated,
|
|
69
|
+
# so count nodes from inside the container (node:20-slim has no curl → use node fetch).
|
|
70
|
+
plugin_count=$(docker compose -f "$ROOT/docker-compose.yml" exec -T unoverse node -e "fetch('http://127.0.0.1:4106/nodes').then(r=>r.json()).then(d=>console.log((d.nodes||[]).length)).catch(()=>console.log(0))" 2>/dev/null | tr -d ' \r')
|
|
71
|
+
if [ "$plugin_count" -gt "0" ]; then
|
|
72
|
+
ok "$plugin_count nodes loaded"
|
|
73
|
+
pass=$((pass + 1))
|
|
74
|
+
else
|
|
75
|
+
fail "0 nodes loaded ${DIM}(check unoverse logs)${NC}"
|
|
76
|
+
fi
|
|
77
|
+
|
|
78
|
+
# 5. Canvas
|
|
79
|
+
total=$((total + 1))
|
|
80
|
+
local canvas_code
|
|
81
|
+
canvas_code=$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:3001" 2>/dev/null)
|
|
82
|
+
if [ "$canvas_code" = "200" ]; then
|
|
83
|
+
ok "Canvas ${DIM}http://localhost:3001${NC}"
|
|
84
|
+
pass=$((pass + 1))
|
|
85
|
+
else
|
|
86
|
+
fail "Canvas ${DIM}http://localhost:3001 → $canvas_code${NC}"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
# Summary
|
|
90
|
+
echo ""
|
|
91
|
+
if [ "$pass" -eq "$total" ]; then
|
|
92
|
+
echo -e " ${GREEN}${BOLD}All $total checks passed${NC}"
|
|
93
|
+
else
|
|
94
|
+
echo -e " ${YELLOW}${BOLD}$pass/$total checks passed${NC}"
|
|
95
|
+
fi
|
|
96
|
+
echo ""
|
|
97
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared colors, helpers, and constants
|
|
3
|
+
|
|
4
|
+
GRAVITY_VERSION="1.0.0"
|
|
5
|
+
DOCR_REGISTRY="registry.digitalocean.com"
|
|
6
|
+
|
|
7
|
+
# Colors
|
|
8
|
+
RED='\033[0;31m'
|
|
9
|
+
GREEN='\033[0;32m'
|
|
10
|
+
YELLOW='\033[1;33m'
|
|
11
|
+
CYAN='\033[0;36m'
|
|
12
|
+
DIM='\033[2m'
|
|
13
|
+
BOLD='\033[1m'
|
|
14
|
+
NC='\033[0m'
|
|
15
|
+
BLUE='\033[0;34m'
|
|
16
|
+
MAGENTA='\033[0;35m'
|
|
17
|
+
WHITE='\033[1;37m'
|
|
18
|
+
UNDERLINE='\033[4m'
|
|
19
|
+
|
|
20
|
+
ok() { echo -e " ${GREEN}✓${NC} $1"; }
|
|
21
|
+
warn() { echo -e " ${YELLOW}⚠${NC} $1"; }
|
|
22
|
+
fail() { echo -e " ${RED}✗${NC} $1"; }
|
|
23
|
+
info() { echo -e " ${DIM}$1${NC}"; }
|
|
24
|
+
link() { echo -e " ${CYAN}${UNDERLINE}$1${NC}"; }
|
|
25
|
+
banner() {
|
|
26
|
+
echo ""
|
|
27
|
+
echo -e " ${BOLD}${CYAN}⬡ $1${NC}"
|
|
28
|
+
echo -e " ${DIM}─────────────────────────────────${NC}"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# The branded access box. ONE definition so start.sh, update.sh and dashboard.sh
|
|
32
|
+
# never drift apart again.
|
|
33
|
+
print_access_urls() {
|
|
34
|
+
echo -e " ${WHITE}${BOLD}unoverse${NC}${DIM}, the experience layer for AI${NC}"
|
|
35
|
+
echo -e " ${DIM}Use the Unoverse MCP to build agents. Somewhere, Skynet is taking notes. 🤖${NC}"
|
|
36
|
+
echo ""
|
|
37
|
+
echo -e " ${CYAN}Canvas${NC} ${DIM}(build agents)${NC} ${UNDERLINE}http://localhost:3001${NC}"
|
|
38
|
+
echo -e " ${CYAN}Studio${NC} ${DIM}(build assets)${NC} ${UNDERLINE}http://localhost:3002${NC}"
|
|
39
|
+
echo -e " ${CYAN}API${NC} ${DIM}(REST + MCP)${NC} ${UNDERLINE}http://localhost:4105${NC}"
|
|
40
|
+
echo ""
|
|
41
|
+
echo -e " ${DIM}▶ Next:${NC} open this repo in ${BOLD}Claude Code${NC} and ask it to build an agent"
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
# Elapsed time helper
|
|
45
|
+
timer_start() { GRAVITY_START=$(date +%s); }
|
|
46
|
+
timer_elapsed() {
|
|
47
|
+
local end=$(date +%s)
|
|
48
|
+
local elapsed=$((end - GRAVITY_START))
|
|
49
|
+
if [ $elapsed -ge 60 ]; then
|
|
50
|
+
echo "$((elapsed / 60))m $((elapsed % 60))s"
|
|
51
|
+
else
|
|
52
|
+
echo "${elapsed}s"
|
|
53
|
+
fi
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
# The old "studio mode" system (mode file, is_platform_mode, require_platform_mode)
|
|
57
|
+
# was removed 2026-07-28: Studio is a separate app now, and this CLI has one job:
|
|
58
|
+
# operate a universe. See _legacy/scripts-lib/ for the retired authoring tools.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# unoverse dashboard (default command when no args)
|
|
3
|
+
|
|
4
|
+
cmd_dashboard() {
|
|
5
|
+
echo ""
|
|
6
|
+
echo -e " ${BOLD}${CYAN}⬡ Unoverse Platform${NC} ${DIM}v${GRAVITY_VERSION}${NC}"
|
|
7
|
+
echo -e " ${DIM}─────────────────────────────────${NC}"
|
|
8
|
+
|
|
9
|
+
# Check if .env exists
|
|
10
|
+
if [ ! -f "$ROOT/.env" ]; then
|
|
11
|
+
echo ""
|
|
12
|
+
echo -e " ${YELLOW}${BOLD}First time?${NC} Run ${GREEN}${BOLD}unoverse init${NC} to get started."
|
|
13
|
+
echo ""
|
|
14
|
+
return
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Quick status — use -a to see Created containers too
|
|
18
|
+
local ps
|
|
19
|
+
ps=$(docker compose -f "$ROOT/docker-compose.yml" ps -a --format "{{.Name}}\t{{.Status}}" 2>/dev/null) || true
|
|
20
|
+
|
|
21
|
+
if [ -n "$ps" ]; then
|
|
22
|
+
local total running created
|
|
23
|
+
total=$(echo "$ps" | wc -l | tr -d ' ')
|
|
24
|
+
running=$(echo "$ps" | grep -ci "up" || echo "0")
|
|
25
|
+
created=$(echo "$ps" | grep -ci "created" || echo "0")
|
|
26
|
+
|
|
27
|
+
echo ""
|
|
28
|
+
if [ "$running" -eq "$total" ] && [ "$total" -gt 0 ]; then
|
|
29
|
+
echo -e " ${GREEN}●${NC} ${BOLD}Platform running${NC} ${DIM}($running services)${NC}"
|
|
30
|
+
echo ""
|
|
31
|
+
print_access_urls
|
|
32
|
+
elif [ "$created" -gt 0 ] && [ "$running" -eq 0 ]; then
|
|
33
|
+
echo -e " ${RED}●${NC} ${BOLD}$total containers stuck in Created state${NC}"
|
|
34
|
+
echo ""
|
|
35
|
+
echo -e " Containers were created but never started."
|
|
36
|
+
echo -e " Run ${GREEN}${BOLD}unoverse check${NC} to diagnose"
|
|
37
|
+
elif [ "$running" -gt 0 ]; then
|
|
38
|
+
echo -e " ${YELLOW}●${NC} ${BOLD}$running/$total services up${NC}"
|
|
39
|
+
echo ""
|
|
40
|
+
print_access_urls
|
|
41
|
+
else
|
|
42
|
+
echo -e " ${RED}●${NC} ${BOLD}Platform not running${NC} ${DIM}($total containers stopped)${NC}"
|
|
43
|
+
echo ""
|
|
44
|
+
echo -e " Run ${GREEN}${BOLD}unoverse start${NC} to launch"
|
|
45
|
+
fi
|
|
46
|
+
else
|
|
47
|
+
echo ""
|
|
48
|
+
echo -e " ${DIM}●${NC} ${BOLD}Platform stopped${NC}"
|
|
49
|
+
echo ""
|
|
50
|
+
echo -e " Run ${GREEN}${BOLD}unoverse start${NC} to launch"
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
echo ""
|
|
54
|
+
echo -e " ${DIM}Run ${NC}${BOLD}unoverse help${NC}${DIM} for all commands${NC}"
|
|
55
|
+
echo ""
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
# ── unoverse open — browser shortcut (merged from open.sh 2026-07-28) ──
|
|
59
|
+
|
|
60
|
+
cmd_open() {
|
|
61
|
+
local target="${1:-canvas}"
|
|
62
|
+
local url
|
|
63
|
+
|
|
64
|
+
case "$target" in
|
|
65
|
+
canvas) url="http://localhost:3001" ;;
|
|
66
|
+
api) url="http://localhost:4105" ;;
|
|
67
|
+
logs|dozzle) url="http://localhost:8080" ;;
|
|
68
|
+
*)
|
|
69
|
+
fail "Unknown target: $target"
|
|
70
|
+
info "Options: canvas, api, logs"
|
|
71
|
+
return
|
|
72
|
+
;;
|
|
73
|
+
esac
|
|
74
|
+
|
|
75
|
+
ok "Opening $target → ${UNDERLINE}$url${NC}"
|
|
76
|
+
|
|
77
|
+
# Cross-platform open
|
|
78
|
+
if command -v open &>/dev/null; then
|
|
79
|
+
open "$url"
|
|
80
|
+
elif command -v xdg-open &>/dev/null; then
|
|
81
|
+
xdg-open "$url"
|
|
82
|
+
else
|
|
83
|
+
info "Open in your browser: $url"
|
|
84
|
+
fi
|
|
85
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# unoverse check — runs node-pg-migrate to apply all pending migrations
|
|
3
|
+
|
|
4
|
+
cmd_db_setup() {
|
|
5
|
+
banner "Database Setup"
|
|
6
|
+
timer_start
|
|
7
|
+
|
|
8
|
+
# Load DATABASE_URL from .env
|
|
9
|
+
local db_url
|
|
10
|
+
# MIGRATIONS CONNECT DIRECT (INFRASTRUCTURE.md, Postgres law): when the env
|
|
11
|
+
# carries a pooled DATABASE_URL (PgBouncer transaction mode), node-pg-migrate
|
|
12
|
+
# must bypass it — DDL and advisory locks don't ride transaction pooling.
|
|
13
|
+
# DATABASE_URL_DIRECT is rendered by the Terraform alongside the pooled URL.
|
|
14
|
+
db_url=$(grep "^DATABASE_URL_DIRECT=" "$ROOT/.env" 2>/dev/null | cut -d'=' -f2-)
|
|
15
|
+
[ -z "$db_url" ] && db_url=$(grep "^DATABASE_URL=" "$ROOT/.env" 2>/dev/null | cut -d'=' -f2-)
|
|
16
|
+
if [ -z "$db_url" ]; then
|
|
17
|
+
fail "DATABASE_URL not found in .env"
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
ok "DATABASE_URL configured"
|
|
21
|
+
|
|
22
|
+
# Auto-add sslmode=prefer if not specified (works for both SSL and non-SSL Postgres)
|
|
23
|
+
if [[ "$db_url" != *"sslmode="* ]]; then
|
|
24
|
+
if [[ "$db_url" == *"?"* ]]; then
|
|
25
|
+
db_url="${db_url}&sslmode=prefer"
|
|
26
|
+
else
|
|
27
|
+
db_url="${db_url}?sslmode=prefer"
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
# Detect environment: monorepo (local dev) vs starter (Docker)
|
|
32
|
+
if [ -d "$ROOT/apps/unoverse/engine" ]; then
|
|
33
|
+
# ── Local dev — run node-pg-migrate directly ──
|
|
34
|
+
echo ""
|
|
35
|
+
echo " Running migrations (local dev)..."
|
|
36
|
+
|
|
37
|
+
NODE_TLS_REJECT_UNAUTHORIZED=0 DATABASE_URL="$db_url" \
|
|
38
|
+
npx node-pg-migrate up \
|
|
39
|
+
--migrations-dir "$ROOT/apps/unoverse/engine/migrations" \
|
|
40
|
+
--migration-file-language sql \
|
|
41
|
+
--no-lock \
|
|
42
|
+
2>&1 | sed 's/^/ /' || {
|
|
43
|
+
fail "Migrations failed"
|
|
44
|
+
exit 1
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Seed security corpus (idempotent)
|
|
48
|
+
_seed_security_corpus "$db_url" "$ROOT/apps/memory/src/security/corpus-seed.json"
|
|
49
|
+
|
|
50
|
+
else
|
|
51
|
+
# ── Starter/production — run via the unoverse container ──
|
|
52
|
+
echo ""
|
|
53
|
+
|
|
54
|
+
if ! docker compose -f "$ROOT/docker-compose.yml" ps --status running unoverse 2>/dev/null | grep -q unoverse; then
|
|
55
|
+
echo ""
|
|
56
|
+
echo -e " ${YELLOW}The unoverse service must be running to apply migrations.${NC}"
|
|
57
|
+
echo -e " ${DIM}(Migration files are bundled inside the unoverse Docker image)${NC}"
|
|
58
|
+
echo ""
|
|
59
|
+
echo -e " Start services first, then re-run:"
|
|
60
|
+
echo -e " ${GREEN}unoverse start${NC}"
|
|
61
|
+
echo -e " ${GREEN}unoverse check${NC}"
|
|
62
|
+
echo ""
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
echo " Running migrations (via Docker)..."
|
|
67
|
+
|
|
68
|
+
docker compose -f "$ROOT/docker-compose.yml" exec -T \
|
|
69
|
+
-e NODE_TLS_REJECT_UNAUTHORIZED=0 \
|
|
70
|
+
-e DATABASE_URL="$db_url" \
|
|
71
|
+
unoverse \
|
|
72
|
+
npx node-pg-migrate up \
|
|
73
|
+
--migrations-dir /app/apps/unoverse/engine/migrations \
|
|
74
|
+
--migration-file-language sql \
|
|
75
|
+
--no-lock \
|
|
76
|
+
2>&1 | sed 's/^/ /' || {
|
|
77
|
+
fail "Migrations failed"
|
|
78
|
+
exit 1
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Seed security corpus via Docker
|
|
82
|
+
docker compose -f "$ROOT/docker-compose.yml" exec -T \
|
|
83
|
+
-e NODE_TLS_REJECT_UNAUTHORIZED=0 unoverse node --no-warnings -e "
|
|
84
|
+
const fs = require('fs');
|
|
85
|
+
const { Pool } = require('pg');
|
|
86
|
+
(async () => {
|
|
87
|
+
try {
|
|
88
|
+
const seedData = JSON.parse(fs.readFileSync('/app/apps/memory/src/security/corpus-seed.json', 'utf-8'));
|
|
89
|
+
const ssl = process.env.DATABASE_URL.includes('sslmode=') ? { rejectUnauthorized: false } : false;
|
|
90
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL, ssl });
|
|
91
|
+
for (const a of seedData) {
|
|
92
|
+
await pool.query(
|
|
93
|
+
'INSERT INTO security_attack_corpus (id,category,label,attack_prompt,expected_result,severity,source,is_active) VALUES (\$1,\$2,\$3,\$4,\$5,\$6,\$7,true) ON CONFLICT (id) DO NOTHING',
|
|
94
|
+
[a.id, a.category, a.label, a.attack_prompt, a.expected_result, a.severity, a.source]
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
console.log(' ✓ Seeded ' + seedData.length + ' security corpus attacks');
|
|
98
|
+
await pool.end();
|
|
99
|
+
} catch (e) { console.warn(' ⚠ Security corpus seed skipped: ' + e.message); }
|
|
100
|
+
process.exit(0);
|
|
101
|
+
})();
|
|
102
|
+
" 2>&1 || true
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
echo ""
|
|
106
|
+
ok "Database setup complete ${DIM}($(timer_elapsed))${NC}"
|
|
107
|
+
echo ""
|
|
108
|
+
|
|
109
|
+
# Auto-verify schema after setup
|
|
110
|
+
echo " Verifying schema..."
|
|
111
|
+
echo ""
|
|
112
|
+
cmd_db_verify
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# Seed security attack corpus (local dev helper)
|
|
116
|
+
_seed_security_corpus() {
|
|
117
|
+
local db_url="$1"
|
|
118
|
+
local seed_file="$2"
|
|
119
|
+
|
|
120
|
+
if [ ! -f "$seed_file" ]; then
|
|
121
|
+
echo " ⚠ Security corpus seed file not found: skipping"
|
|
122
|
+
return 0
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
NODE_TLS_REJECT_UNAUTHORIZED=0 DATABASE_URL="$db_url" node --no-warnings -e "
|
|
126
|
+
const fs = require('fs');
|
|
127
|
+
const { Pool } = require('pg');
|
|
128
|
+
(async () => {
|
|
129
|
+
try {
|
|
130
|
+
const seedData = JSON.parse(fs.readFileSync('$seed_file', 'utf-8'));
|
|
131
|
+
const ssl = process.env.DATABASE_URL.includes('sslmode=') ? { rejectUnauthorized: false } : false;
|
|
132
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL, ssl });
|
|
133
|
+
for (const a of seedData) {
|
|
134
|
+
await pool.query(
|
|
135
|
+
'INSERT INTO security_attack_corpus (id,category,label,attack_prompt,expected_result,severity,source,is_active) VALUES (\$1,\$2,\$3,\$4,\$5,\$6,\$7,true) ON CONFLICT (id) DO NOTHING',
|
|
136
|
+
[a.id, a.category, a.label, a.attack_prompt, a.expected_result, a.severity, a.source]
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
console.log(' ✓ Seeded ' + seedData.length + ' security corpus attacks');
|
|
140
|
+
await pool.end();
|
|
141
|
+
} catch (e) { console.warn(' ⚠ Security corpus seed skipped: ' + e.message); }
|
|
142
|
+
process.exit(0);
|
|
143
|
+
})();
|
|
144
|
+
" 2>&1 || true
|
|
145
|
+
}
|