vibeoscore 1.0.2 → 1.0.9
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/client.js +1 -0
- package/client.ts +2 -0
- package/lib/logger.js +27 -0
- package/mcp-server.js +5 -4
- package/mcp-server.ts +4 -3
- package/package.json +12 -10
- package/dashboard/dist/assets/index-BnPt1Fii.js +0 -1
- package/dashboard/dist/assets/index-CfH00tOL.css +0 -1
- package/dashboard/dist/index.html +0 -3
- package/lib/blackbox-rf.js +0 -1099
- package/lib/blackbox.js +0 -137
- package/lib/compression.js +0 -119
- package/lib/db.js +0 -106
- package/lib/db.ts +0 -113
- package/lib/delegation.js +0 -137
- package/lib/meta-controller.js +0 -418
- package/lib/meta-controller.mjs +0 -499
- package/lib/patterns.js +0 -150
- package/lib/resolution-tracker.js +0 -486
- package/lib/stress.js +0 -84
- package/lib/tdd.js +0 -218
- package/lib/tier-routing.js +0 -48
- package/middleware/auth.js +0 -75
- package/middleware/auth.ts +0 -87
- package/middleware/usage-logging.js +0 -29
- package/middleware/usage-logging.ts +0 -41
- package/nginx-vibetheog-api.conf +0 -64
- package/routes/admin.js +0 -93
- package/routes/admin.ts +0 -107
- package/routes/blackbox.js +0 -463
- package/routes/compression.js +0 -12
- package/routes/delegation.js +0 -30
- package/routes/patterns.js +0 -53
- package/routes/pricing.js +0 -62
- package/routes/stress.js +0 -30
- package/routes/tdd.js +0 -68
- package/routes/tier-routing.js +0 -31
- package/scripts/dashboard-server.mjs +0 -246
- package/scripts/deploy-zero-downtime.sh +0 -77
- package/scripts/deploy.sh +0 -68
- package/scripts/release.mjs +0 -30
- package/scripts/seed-master-token.js +0 -29
- package/scripts/start-all.mjs +0 -34
- package/server.js +0 -88
- package/vibeos-api.service +0 -19
package/routes/pricing.js
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { parseOpenRouterTurnCost, modelCostPerTurn, normalizeModelId, MODEL_USD_PER_TURN } from "../lib/delegation.js"
|
|
2
|
-
|
|
3
|
-
const pricingCache = new Map()
|
|
4
|
-
let lastFetch = 0
|
|
5
|
-
const CACHE_TTL = 10 * 60 * 1000
|
|
6
|
-
|
|
7
|
-
export async function pricingRoutes(fastify) {
|
|
8
|
-
fastify.post("/api/v1/pricing/fetch", async (request, reply) => {
|
|
9
|
-
const { openrouter_key, force } = request.body || {}
|
|
10
|
-
|
|
11
|
-
const now = Date.now()
|
|
12
|
-
if (!force && pricingCache.size > 0 && (now - lastFetch) < CACHE_TTL) {
|
|
13
|
-
return { cached: true, models: Object.fromEntries(pricingCache), count: pricingCache.size }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (!openrouter_key) {
|
|
17
|
-
return reply.code(400).send({ error: "openrouter_key is required for live fetch" })
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const res = await fetch("https://openrouter.ai/api/v1/models", {
|
|
22
|
-
headers: { Authorization: "Bearer " + openrouter_key },
|
|
23
|
-
signal: AbortSignal.timeout(10000),
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
if (!res.ok) {
|
|
27
|
-
return reply.code(res.status).send({ error: "openrouter api error", status: res.status })
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const body = await res.json()
|
|
31
|
-
const list = body?.data || []
|
|
32
|
-
const pricingMap = {}
|
|
33
|
-
|
|
34
|
-
for (const m of list) {
|
|
35
|
-
const dynTurnCost = parseOpenRouterTurnCost(m)
|
|
36
|
-
if (dynTurnCost != null && Number.isFinite(dynTurnCost)) {
|
|
37
|
-
const rawId = m.id || ""
|
|
38
|
-
pricingMap[normalizeModelId(rawId)] = dynTurnCost
|
|
39
|
-
pricingCache.set(normalizeModelId(rawId), dynTurnCost)
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
lastFetch = now
|
|
44
|
-
return { cached: false, models: pricingMap, count: Object.keys(pricingMap).length }
|
|
45
|
-
} catch (err) {
|
|
46
|
-
return reply.code(502).send({ error: "failed to fetch pricing", message: err.message })
|
|
47
|
-
}
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
fastify.post("/api/v1/pricing/lookup", async (request, reply) => {
|
|
51
|
-
const { model } = request.body || {}
|
|
52
|
-
if (!model) {
|
|
53
|
-
return reply.code(400).send({ error: "model is required" })
|
|
54
|
-
}
|
|
55
|
-
const cost = modelCostPerTurn(model, Object.fromEntries(pricingCache))
|
|
56
|
-
return { model: normalizeModelId(model), cost_per_turn: cost ?? null, in_cache: pricingCache.has(normalizeModelId(model)) }
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
fastify.get("/api/v1/pricing/static", async (request, reply) => {
|
|
60
|
-
return { models: MODEL_USD_PER_TURN, count: Object.keys(MODEL_USD_PER_TURN).length }
|
|
61
|
-
})
|
|
62
|
-
}
|
package/routes/stress.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { scoreStress, getStressLevel, buildStressFooter } from "../lib/stress.js"
|
|
2
|
-
|
|
3
|
-
export async function stressRoutes(fastify) {
|
|
4
|
-
fastify.post("/api/v1/stress/score", async (request, reply) => {
|
|
5
|
-
const { text } = request.body || {}
|
|
6
|
-
if (!text) {
|
|
7
|
-
return reply.code(400).send({ error: "text is required" })
|
|
8
|
-
}
|
|
9
|
-
const score = scoreStress(text)
|
|
10
|
-
const level = getStressLevel(score)
|
|
11
|
-
const footer = buildStressFooter(score)
|
|
12
|
-
return { score, level: level.level, gauge: level.gauge, directive: level.directive, footer }
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
fastify.post("/api/v1/stress/level", async (request, reply) => {
|
|
16
|
-
const { score } = request.body || {}
|
|
17
|
-
if (score === undefined || score === null) {
|
|
18
|
-
return reply.code(400).send({ error: "score is required" })
|
|
19
|
-
}
|
|
20
|
-
const numScore = Number(score)
|
|
21
|
-
if (!Number.isFinite(numScore)) {
|
|
22
|
-
return reply.code(400).send({ error: "score must be a finite number" })
|
|
23
|
-
}
|
|
24
|
-
if (numScore < 0) {
|
|
25
|
-
return reply.code(400).send({ error: "score must be non-negative" })
|
|
26
|
-
}
|
|
27
|
-
const level = getStressLevel(numScore)
|
|
28
|
-
return level
|
|
29
|
-
})
|
|
30
|
-
}
|
package/routes/tdd.js
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { extractExports, inferFunctionParams, inferTypeFromName, buildTestSkeleton } from "../lib/tdd.js"
|
|
2
|
-
|
|
3
|
-
export async function tddRoutes(fastify) {
|
|
4
|
-
fastify.post("/api/v1/tdd/exports", async (request, reply) => {
|
|
5
|
-
try {
|
|
6
|
-
const { source_content, ext } = request.body || {}
|
|
7
|
-
if (!source_content || typeof source_content !== "string") {
|
|
8
|
-
return reply.code(400).send({ error: "source_content is required and must be a string", code: "INVALID_INPUT" })
|
|
9
|
-
}
|
|
10
|
-
if (!ext || typeof ext !== "string") {
|
|
11
|
-
return reply.code(400).send({ error: "ext is required and must be a string", code: "INVALID_INPUT" })
|
|
12
|
-
}
|
|
13
|
-
const exports = extractExports(source_content, ext)
|
|
14
|
-
return { exports }
|
|
15
|
-
} catch (err) {
|
|
16
|
-
request.log.error(err, "tdd/exports error")
|
|
17
|
-
return reply.code(500).send({ error: "Internal server error", code: "INTERNAL_ERROR" })
|
|
18
|
-
}
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
fastify.post("/api/v1/tdd/params", async (request, reply) => {
|
|
22
|
-
try {
|
|
23
|
-
const { source_content, func_name } = request.body || {}
|
|
24
|
-
if (!source_content || typeof source_content !== "string") {
|
|
25
|
-
return reply.code(400).send({ error: "source_content is required and must be a string", code: "INVALID_INPUT" })
|
|
26
|
-
}
|
|
27
|
-
if (!func_name || typeof func_name !== "string") {
|
|
28
|
-
return reply.code(400).send({ error: "func_name is required and must be a string", code: "INVALID_INPUT" })
|
|
29
|
-
}
|
|
30
|
-
const params = inferFunctionParams(source_content, func_name)
|
|
31
|
-
return { params }
|
|
32
|
-
} catch (err) {
|
|
33
|
-
request.log.error(err, "tdd/params error")
|
|
34
|
-
return reply.code(500).send({ error: "Internal server error", code: "INTERNAL_ERROR" })
|
|
35
|
-
}
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
fastify.post("/api/v1/tdd/infer-type", async (request, reply) => {
|
|
39
|
-
try {
|
|
40
|
-
const { param_name, default_value } = request.body || {}
|
|
41
|
-
const type = inferTypeFromName(param_name, default_value)
|
|
42
|
-
return { type }
|
|
43
|
-
} catch (err) {
|
|
44
|
-
request.log.error(err, "tdd/infer-type error")
|
|
45
|
-
return reply.code(500).send({ error: "Internal server error", code: "INTERNAL_ERROR" })
|
|
46
|
-
}
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
fastify.post("/api/v1/tdd/skeleton", async (request, reply) => {
|
|
50
|
-
try {
|
|
51
|
-
const { language, file_name, exports, options } = request.body || {}
|
|
52
|
-
if (!exports || !Array.isArray(exports)) {
|
|
53
|
-
return reply.code(400).send({ error: "exports is required and must be an array", code: "INVALID_INPUT" })
|
|
54
|
-
}
|
|
55
|
-
if (exports.length === 0) {
|
|
56
|
-
return reply.code(400).send({ error: "exports array must not be empty", code: "INVALID_INPUT" })
|
|
57
|
-
}
|
|
58
|
-
if (!file_name || typeof file_name !== "string") {
|
|
59
|
-
return reply.code(400).send({ error: "file_name is required and must be a string", code: "INVALID_INPUT" })
|
|
60
|
-
}
|
|
61
|
-
const result = buildTestSkeleton(language || "javascript", file_name, exports, options || {})
|
|
62
|
-
return { skeleton: result }
|
|
63
|
-
} catch (err) {
|
|
64
|
-
request.log.error(err, "tdd/skeleton error")
|
|
65
|
-
return reply.code(500).send({ error: "Internal server error", code: "INTERNAL_ERROR" })
|
|
66
|
-
}
|
|
67
|
-
})
|
|
68
|
-
}
|
package/routes/tier-routing.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { classify, routeModel, isExploratoryPrompt } from "../lib/tier-routing.js"
|
|
2
|
-
|
|
3
|
-
export async function tierRoutes(fastify) {
|
|
4
|
-
fastify.post("/api/v1/route/model", async (request, reply) => {
|
|
5
|
-
const { prompt, current_tier, trinity_cheap, trinity_medium, learned_exploratory, stress_score } = request.body || {}
|
|
6
|
-
if (!prompt) {
|
|
7
|
-
return reply.code(400).send({ error: "prompt is required" })
|
|
8
|
-
}
|
|
9
|
-
const learnedArr = Array.isArray(learned_exploratory) ? learned_exploratory : []
|
|
10
|
-
const result = routeModel(prompt, current_tier, trinity_cheap, trinity_medium, learnedArr, stress_score || 0)
|
|
11
|
-
return result
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
fastify.post("/api/v1/tier/classify", async (request, reply) => {
|
|
15
|
-
const { model, custom_regex } = request.body || {}
|
|
16
|
-
if (!model) {
|
|
17
|
-
return reply.code(400).send({ error: "model is required" })
|
|
18
|
-
}
|
|
19
|
-
const tier = classify(model, custom_regex)
|
|
20
|
-
return { model, tier }
|
|
21
|
-
})
|
|
22
|
-
|
|
23
|
-
fastify.post("/api/v1/tier/exploratory", async (request, reply) => {
|
|
24
|
-
const { prompt, learned_exploratory } = request.body || {}
|
|
25
|
-
if (!prompt) {
|
|
26
|
-
return reply.code(400).send({ error: "prompt is required" })
|
|
27
|
-
}
|
|
28
|
-
const result = isExploratoryPrompt(prompt, learned_exploratory || [])
|
|
29
|
-
return result
|
|
30
|
-
})
|
|
31
|
-
}
|
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import http from "node:http"
|
|
3
|
-
import { createReadStream, existsSync, statSync, readFileSync, readdirSync, writeFileSync, mkdirSync, renameSync } from "node:fs"
|
|
4
|
-
import { extname, join, dirname } from "node:path"
|
|
5
|
-
import { fileURLToPath } from "node:url"
|
|
6
|
-
import { homedir } from "node:os"
|
|
7
|
-
import crypto from "node:crypto"
|
|
8
|
-
|
|
9
|
-
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
10
|
-
const ROOT = dirname(__dirname)
|
|
11
|
-
const MIME_MAP = { ".html": "text/html; charset=utf-8", ".js": "application/javascript; charset=utf-8", ".css": "text/css; charset=utf-8", ".json": "application/json; charset=utf-8", ".png": "image/png", ".svg": "image/svg+xml", ".ico": "image/x-icon", ".map": "application/json" }
|
|
12
|
-
const DASHBOARD_DIR = join(ROOT, "src", "dashboard", "dist")
|
|
13
|
-
const STATE_DIR = join(homedir(), ".claude")
|
|
14
|
-
const TIERS_FILE = join(STATE_DIR, "model-tiers.json")
|
|
15
|
-
const STATE_FILE = join(STATE_DIR, "delegation-state.json")
|
|
16
|
-
const REPORTS_DIR = join(STATE_DIR, "reports")
|
|
17
|
-
const BACKEND_HEALTH_URL = process.env.VIBEOS_BACKEND_HEALTH_URL || "http://127.0.0.1:3000/health"
|
|
18
|
-
const BACKEND_HEALTH_TTL_MS = 5_000
|
|
19
|
-
|
|
20
|
-
let backendHealth = { ok: null, checkedAt: 0 }
|
|
21
|
-
|
|
22
|
-
function rj(f) { try { if (!existsSync(f)) return null; return JSON.parse(readFileSync(f, "utf-8")) } catch { return null } }
|
|
23
|
-
function wj(f, d) { try { mkdirSync(dirname(f), { recursive: true }); const t = f + ".tmp." + Date.now(); writeFileSync(t, JSON.stringify(d, null, 2) + "\n", "utf-8"); renameSync(t, f); return true } catch (e) { console.error("[vibeOS] write failed:", e.message); return false } }
|
|
24
|
-
|
|
25
|
-
function checkAuth(req, res) {
|
|
26
|
-
return true
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function probeBackendHealth(force = false) {
|
|
30
|
-
const now = Date.now()
|
|
31
|
-
if (!force && backendHealth.ok !== null && (now - backendHealth.checkedAt) < BACKEND_HEALTH_TTL_MS) {
|
|
32
|
-
return backendHealth.ok
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
const ctl = new AbortController()
|
|
36
|
-
const timer = setTimeout(() => ctl.abort(), 1500)
|
|
37
|
-
const res = await fetch(BACKEND_HEALTH_URL, { signal: ctl.signal })
|
|
38
|
-
clearTimeout(timer)
|
|
39
|
-
backendHealth = { ok: res.ok, checkedAt: now }
|
|
40
|
-
return res.ok
|
|
41
|
-
} catch {
|
|
42
|
-
backendHealth = { ok: false, checkedAt: now }
|
|
43
|
-
return false
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function status() {
|
|
48
|
-
const t = rj(TIERS_FILE)
|
|
49
|
-
const s = rj(STATE_FILE)
|
|
50
|
-
const sel = t?.selection || {}
|
|
51
|
-
const activeSlot = sel.active_slot || "unknown"
|
|
52
|
-
const lockedSlot = sel.locked_slot || null
|
|
53
|
-
return {
|
|
54
|
-
enabled: sel.enabled !== false,
|
|
55
|
-
active_slot: activeSlot,
|
|
56
|
-
enforce: sel.delegation_enforce !== false,
|
|
57
|
-
flow_enforcer: sel.flow_enabled === true,
|
|
58
|
-
flow_extract_todos: sel.flow_enforce === true,
|
|
59
|
-
tdd_enforcer: sel.tdd_enforce === true,
|
|
60
|
-
tdd_strict: sel.tdd_strict !== false,
|
|
61
|
-
thinking: sel.thinking_level || "auto",
|
|
62
|
-
current_model: t?.trinity?.[activeSlot]?.oc || "unknown",
|
|
63
|
-
credit_percent: 0,
|
|
64
|
-
version: "0.13.6",
|
|
65
|
-
sessions_raw: s?.sessions || {},
|
|
66
|
-
backend_connected: backendHealth.ok === true,
|
|
67
|
-
backend_health_url: BACKEND_HEALTH_URL,
|
|
68
|
-
model_locked: sel.model_locked === true,
|
|
69
|
-
locked_slot: lockedSlot,
|
|
70
|
-
locked_model: sel.model_locked && lockedSlot ? (t?.trinity?.[lockedSlot]?.oc || "unknown") : null,
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function savings() {
|
|
75
|
-
const s = rj(STATE_FILE)
|
|
76
|
-
const ses = Object.values(s?.sessions || {})
|
|
77
|
-
const lD = ses.reduce((a, e) => a + (e.warns || []).reduce((b, w) => b + (Number(w?.est_savings_usd) || 0), 0), 0)
|
|
78
|
-
const lC = ses.reduce((a, e) => a + (Number(e?.cache_savings_usd) || 0), 0)
|
|
79
|
-
const c = ses[ses.length - 1] || {}
|
|
80
|
-
const cW = c.warns || []
|
|
81
|
-
const cD = cW.reduce((a, w) => a + (Number(w?.est_savings_usd) || 0), 0)
|
|
82
|
-
const cC = Number(c?.cache_savings_usd) || 0
|
|
83
|
-
const bd = {}
|
|
84
|
-
cW.forEach(w => { const t = w.tool || "other"; bd[t] = (bd[t] || 0) + (Number(w?.est_savings_usd) || 0) })
|
|
85
|
-
return { lifetime: { delegation_usd: lD, cache_usd: lC, missed_context7_usd: 0, total_warns: ses.reduce((a, e) => a + (e.warns?.length || 0), 0) }, current_session: { delegation_usd: cD, cache_usd: cC, warns_count: cW.length, tool_breakdown: bd }, cache_hits_this_session: 0, trend: lD + lC > 0 ? "up" : "flat", savings_rate_per_hour: 0 }
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function sessions() {
|
|
89
|
-
const m = rj(STATE_FILE)?.sessions || {}
|
|
90
|
-
const l = Object.entries(m).map(([id, e]) => ({ id, started: e?.started || null, cost_usd: Number(e?.cost_usd ?? 0) || 0, delegation_savings_usd: (e.warns || []).reduce((a, w) => a + (Number(w?.est_savings_usd) || 0), 0), cache_savings_usd: Number(e?.cache_savings_usd ?? 0) || 0, warns_count: (e.warns || []).length }))
|
|
91
|
-
return { sessions: l, total_sessions: l.length }
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function reports() {
|
|
95
|
-
try {
|
|
96
|
-
if (!existsSync(REPORTS_DIR)) return []
|
|
97
|
-
return readdirSync(REPORTS_DIR).filter(f => f.endsWith(".json")).map(f => {
|
|
98
|
-
const d = rj(join(REPORTS_DIR, f))
|
|
99
|
-
return { id: f.replace(/\.json$/, ""), summary: d?.summary || "", type: d?.type || "", created: d?.created || "", tags: d?.tags || [] }
|
|
100
|
-
})
|
|
101
|
-
} catch {
|
|
102
|
-
return []
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function report(id) { try { return rj(join(REPORTS_DIR, `${id}.json`)) } catch { return null } }
|
|
107
|
-
|
|
108
|
-
function sf(res, fp, c) { const ext = extname(fp).toLowerCase(); const m = MIME_MAP[ext] || "application/octet-stream"; const st = statSync(fp); res.writeHead(200, { "Content-Type": m, "Content-Length": st.size, "Cache-Control": c ? "max-age=3600" : "no-cache", "Access-Control-Allow-Origin": process.env.DASHBOARD_ORIGIN || "http://localhost:" + (process.env.PORT || 3333) }); createReadStream(fp).pipe(res) }
|
|
109
|
-
function dash(res, p) { if (p.includes("..")) { res.writeHead(404); res.end(); return } if (p === "/") { sf(res, join(DASHBOARD_DIR, "index.html"), false); return } const fp = join(DASHBOARD_DIR, "." + p); if (existsSync(fp) && statSync(fp).isFile()) { sf(res, fp, /\.(js|css|png|svg|ico)$/.test(p)) } else { res.writeHead(404); res.end() } }
|
|
110
|
-
|
|
111
|
-
async function parseBody(req) { return new Promise((resolve) => { let raw = ""; req.on("data", c => { raw += String(c || ""); if (raw.length > 65536) { resolve(null) } }); req.on("end", () => { try { resolve(raw ? JSON.parse(raw) : {}) } catch { resolve(null) } }); req.on("error", () => resolve(null)) }) }
|
|
112
|
-
|
|
113
|
-
function handleTrinity(body) {
|
|
114
|
-
if (!body || !body.action) return { ok: false, error: "missing action" }
|
|
115
|
-
const action = String(body.action)
|
|
116
|
-
const slot = body.slot ? String(body.slot) : null
|
|
117
|
-
const tiers = rj(TIERS_FILE)
|
|
118
|
-
if (!tiers) return { ok: false, error: "model-tiers.json not found" }
|
|
119
|
-
tiers.selection ??= {}
|
|
120
|
-
|
|
121
|
-
if (action === "set") {
|
|
122
|
-
if (slot && ["brain", "medium", "cheap"].includes(slot)) {
|
|
123
|
-
if (tiers.selection.model_locked && tiers.selection.locked_slot && tiers.selection.locked_slot !== slot) {
|
|
124
|
-
return { ok: false, error: `model is locked to ${tiers.selection.locked_slot}` }
|
|
125
|
-
}
|
|
126
|
-
tiers.selection.active_slot = slot
|
|
127
|
-
wj(TIERS_FILE, tiers)
|
|
128
|
-
return { ok: true, result: `Slot set to ${slot}` }
|
|
129
|
-
}
|
|
130
|
-
return { ok: false, error: "invalid slot" }
|
|
131
|
-
}
|
|
132
|
-
if (action === "lock") {
|
|
133
|
-
if (slot === "on") {
|
|
134
|
-
tiers.selection.model_locked = true
|
|
135
|
-
tiers.selection.locked_slot = tiers.selection.active_slot || "medium"
|
|
136
|
-
wj(TIERS_FILE, tiers)
|
|
137
|
-
return { ok: true, result: `Model locked to ${tiers.selection.locked_slot}` }
|
|
138
|
-
}
|
|
139
|
-
if (slot === "off") {
|
|
140
|
-
tiers.selection.model_locked = false
|
|
141
|
-
delete tiers.selection.locked_slot
|
|
142
|
-
wj(TIERS_FILE, tiers)
|
|
143
|
-
return { ok: true, result: "Model lock disabled" }
|
|
144
|
-
}
|
|
145
|
-
return { ok: false, error: "invalid lock slot" }
|
|
146
|
-
}
|
|
147
|
-
if (action === "enforce") {
|
|
148
|
-
tiers.selection.delegation_enforce = slot === "on"
|
|
149
|
-
wj(TIERS_FILE, tiers)
|
|
150
|
-
return { ok: true, result: `Enforcement ${slot === "on" ? "ON" : "OFF"}` }
|
|
151
|
-
}
|
|
152
|
-
if (action === "flow") {
|
|
153
|
-
if (slot === "on" || slot === "off") {
|
|
154
|
-
tiers.selection.flow_enabled = slot === "on"
|
|
155
|
-
wj(TIERS_FILE, tiers)
|
|
156
|
-
return { ok: true, result: `Flow ${slot.toUpperCase()}` }
|
|
157
|
-
}
|
|
158
|
-
if (slot === "enforce") {
|
|
159
|
-
const cur = tiers.selection.flow_enforce === true
|
|
160
|
-
tiers.selection.flow_enforce = !cur
|
|
161
|
-
wj(TIERS_FILE, tiers)
|
|
162
|
-
return { ok: true, result: `Flow enforce ${cur ? "OFF" : "ON"}` }
|
|
163
|
-
}
|
|
164
|
-
return { ok: false, error: "invalid flow slot" }
|
|
165
|
-
}
|
|
166
|
-
if (action === "tdd") {
|
|
167
|
-
if (slot === "on" || slot === "off") {
|
|
168
|
-
tiers.selection.tdd_enforce = slot === "on"
|
|
169
|
-
wj(TIERS_FILE, tiers)
|
|
170
|
-
return { ok: true, result: `TDD ${slot.toUpperCase()}` }
|
|
171
|
-
}
|
|
172
|
-
if (slot === "strict") {
|
|
173
|
-
const cur = tiers.selection.tdd_strict === true
|
|
174
|
-
tiers.selection.tdd_strict = !cur
|
|
175
|
-
wj(TIERS_FILE, tiers)
|
|
176
|
-
return { ok: true, result: `TDD strict ${cur ? "OFF" : "ON"}` }
|
|
177
|
-
}
|
|
178
|
-
return { ok: false, error: "invalid tdd slot" }
|
|
179
|
-
}
|
|
180
|
-
if (action === "thinking") {
|
|
181
|
-
if (["full", "brief", "off"].includes(String(slot))) {
|
|
182
|
-
tiers.selection.thinking_level = String(slot)
|
|
183
|
-
wj(TIERS_FILE, tiers)
|
|
184
|
-
return { ok: true, result: `Thinking set to ${slot}` }
|
|
185
|
-
}
|
|
186
|
-
return { ok: false, error: "invalid thinking level" }
|
|
187
|
-
}
|
|
188
|
-
if (action === "disable") {
|
|
189
|
-
tiers.selection.enabled = false
|
|
190
|
-
wj(TIERS_FILE, tiers)
|
|
191
|
-
return { ok: true, result: "vibeOS disabled" }
|
|
192
|
-
}
|
|
193
|
-
if (action === "status" || action === "diagnose" || action === "project" || action === "help") {
|
|
194
|
-
return { ok: true, result: `${action} ok` }
|
|
195
|
-
}
|
|
196
|
-
return { ok: false, error: "unknown action" }
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const SSE = new Set()
|
|
200
|
-
let si = null
|
|
201
|
-
|
|
202
|
-
async function bcast() {
|
|
203
|
-
await probeBackendHealth()
|
|
204
|
-
const d = JSON.stringify({ status: status(), savings: savings() })
|
|
205
|
-
for (const r of SSE) { try { r.write(`data: ${d}\n\n`) } catch { SSE.delete(r) } }
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async function hSSE(req, res) {
|
|
209
|
-
await probeBackendHealth()
|
|
210
|
-
res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive", "Access-Control-Allow-Origin": process.env.DASHBOARD_ORIGIN || "http://localhost:" + (process.env.PORT || 3333) })
|
|
211
|
-
res.write(`data: ${JSON.stringify({ status: status(), savings: savings() })}\n\n`)
|
|
212
|
-
SSE.add(res)
|
|
213
|
-
if (!si) si = setInterval(() => { void bcast() }, 1500)
|
|
214
|
-
req.on("close", () => { SSE.delete(res); if (SSE.size === 0 && si) { clearInterval(si); si = null } })
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
async function main() {
|
|
218
|
-
const PORT = Number(process.env.PORT || 3333)
|
|
219
|
-
if (!existsSync(join(DASHBOARD_DIR, "index.html"))) { console.error("[vibeOS] Dashboard not built. Run: npm run build:dashboard"); process.exit(1) }
|
|
220
|
-
const s = http.createServer(async (req, res) => {
|
|
221
|
-
res.setHeader("Access-Control-Allow-Origin", process.env.DASHBOARD_ORIGIN || "http://localhost:" + (process.env.PORT || 3333))
|
|
222
|
-
if (req.method === "OPTIONS") { res.writeHead(204, { "Access-Control-Allow-Methods": "GET,POST,OPTIONS", "Access-Control-Allow-Headers": "Content-Type,Authorization", "Access-Control-Allow-Credentials": "true" }); res.end(); return }
|
|
223
|
-
const p = new URL(req.url, `http://127.0.0.1:${PORT}`).pathname
|
|
224
|
-
try {
|
|
225
|
-
if (p === "/status") { if (!checkAuth(req, res)) return; await probeBackendHealth(); res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(status())); return }
|
|
226
|
-
if (p === "/savings") { if (!checkAuth(req, res)) return; res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(savings())); return }
|
|
227
|
-
if (p === "/sessions") { if (!checkAuth(req, res)) return; res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(sessions())); return }
|
|
228
|
-
if (p === "/reports") { if (!checkAuth(req, res)) return; res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(reports())); return }
|
|
229
|
-
if (p.startsWith("/reports/")) { if (!checkAuth(req, res)) return; const id = decodeURIComponent(p.replace("/reports/", "")); const r = report(id); if (r) { res.writeHead(200, { "Content-Type": "application/json" }); res.end(JSON.stringify(r)) } else { res.writeHead(404); res.end() }; return }
|
|
230
|
-
if (p === "/events") { if (!checkAuth(req, res)) return; await hSSE(req, res); return }
|
|
231
|
-
if (p === "/trinity" && req.method === "POST") {
|
|
232
|
-
if (!checkAuth(req, res)) return
|
|
233
|
-
const body = await parseBody(req)
|
|
234
|
-
const result = handleTrinity(body)
|
|
235
|
-
res.writeHead(result.ok ? 200 : 400, { "Content-Type": "application/json" })
|
|
236
|
-
res.end(JSON.stringify(result))
|
|
237
|
-
void bcast()
|
|
238
|
-
return
|
|
239
|
-
}
|
|
240
|
-
dash(res, p)
|
|
241
|
-
} catch (e) { res.writeHead(500); res.end(e.message) }
|
|
242
|
-
})
|
|
243
|
-
s.listen(PORT, "127.0.0.1", () => { const a = s.address(); const ap = typeof a === "object" && a ? a.port : PORT; console.error(`[vibeOS] Dashboard server on http://127.0.0.1:${ap}\n[vibeOS] Open http://127.0.0.1:${ap}/ in your browser`) })
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
main()
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
VPS_HOST="${VPS_HOST:-45.132.242.217}"
|
|
5
|
-
VPS_USER="${VPS_USER:-root}"
|
|
6
|
-
BASE="/var/www/vibeos-api"
|
|
7
|
-
REL="$BASE/releases/$(date +%s)"
|
|
8
|
-
CUR="$BASE/current"
|
|
9
|
-
P1=3000
|
|
10
|
-
P2=3001
|
|
11
|
-
NGX="/etc/nginx/sites-enabled/vibeos-api.conf"
|
|
12
|
-
|
|
13
|
-
log() { echo " [$1] $2"; }
|
|
14
|
-
r() { ssh "$VPS_USER@$VPS_HOST" "$@"; }
|
|
15
|
-
pid() { r "lsof -ti :$1 2>/dev/null || true"; }
|
|
16
|
-
up() { r "curl -sf -o /dev/null http://127.0.0.1:$1/health" 2>/dev/null; return $?; }
|
|
17
|
-
|
|
18
|
-
echo "[vibeOS-api] Zero-downtime deploy"
|
|
19
|
-
|
|
20
|
-
# 1. Upload
|
|
21
|
-
log "deploy" "Creating release dir..."
|
|
22
|
-
r "mkdir -p $REL/data"
|
|
23
|
-
|
|
24
|
-
log "deploy" "Uploading files..."
|
|
25
|
-
D="$(dirname "$0")/.."
|
|
26
|
-
for f in server.js start.sh lib routes middleware package.json; do
|
|
27
|
-
[ -e "$D/$f" ] && scp -qr "$D/$f" "$VPS_USER@$VPS_HOST:$REL/"
|
|
28
|
-
done
|
|
29
|
-
|
|
30
|
-
log "deps" "npm install..."
|
|
31
|
-
r "cd $REL && npm install --omit=dev --silent 2>/dev/null"
|
|
32
|
-
|
|
33
|
-
# 2. .env
|
|
34
|
-
log "env" "Copying .env..."
|
|
35
|
-
r "cp $BASE/.env $REL/.env 2>/dev/null || cp $CUR/.env $REL/.env 2>/dev/null ||:"
|
|
36
|
-
|
|
37
|
-
# 3. Blue-green port detection
|
|
38
|
-
CUR_P=""; NEW_P=""
|
|
39
|
-
up $P1 && CUR_P=$P1 && NEW_P=$P2 || true
|
|
40
|
-
up $P2 && CUR_P=$P2 && NEW_P=$P1 || true
|
|
41
|
-
[ -z "$CUR_P" ] && NEW_P=$P1 && log "deploy" "Fresh start on $NEW_P" || log "deploy" "Instance on $CUR_P, deploying to $NEW_P"
|
|
42
|
-
|
|
43
|
-
# 4. Start new instance (detached via ssh -f)
|
|
44
|
-
log "start" "Starting on port $NEW_P..."
|
|
45
|
-
r "cd $REL && screen -dmS vibeos-$NEW_P env PORT=$NEW_P bash start.sh"
|
|
46
|
-
sleep 3
|
|
47
|
-
|
|
48
|
-
# 5. Health check
|
|
49
|
-
log "health" "Waiting for port $NEW_P..."
|
|
50
|
-
for i in $(seq 1 15); do
|
|
51
|
-
up $NEW_P && log "health" "OK after ${i}s" && break || true
|
|
52
|
-
[ $i -eq 15 ] && log "health" "FAIL" && r "screen -S vibeos-$NEW_P -X quit 2>/dev/null; true" && exit 1
|
|
53
|
-
sleep 1
|
|
54
|
-
done
|
|
55
|
-
|
|
56
|
-
# 6. Swap nginx
|
|
57
|
-
log "nginx" "Switching to port $NEW_P..."
|
|
58
|
-
r "sed -i 's|proxy_pass http://127.0.0.1:[0-9]*|proxy_pass http://127.0.0.1:$NEW_P|g' $NGX"
|
|
59
|
-
r "nginx -t && systemctl reload nginx"
|
|
60
|
-
|
|
61
|
-
# 7. Kill old
|
|
62
|
-
if [ -n "$CUR_P" ]; then
|
|
63
|
-
O=$(pid $CUR_P)
|
|
64
|
-
[ -n "$O" ] && log "stop" "Stopping old PID $O" && r "kill $O 2>/dev/null; sleep 2; kill -9 $O 2>/dev/null; true"
|
|
65
|
-
fi
|
|
66
|
-
|
|
67
|
-
# 8. Symlink + systemd
|
|
68
|
-
log "symlink" "$CUR -> $REL"
|
|
69
|
-
r "ln -sfn $REL $CUR && cp $REL/start.sh $BASE/start.sh"
|
|
70
|
-
r "sed -i 's|WorkingDirectory=.*|WorkingDirectory=$CUR|' /etc/systemd/system/vibeos-api.service 2>/dev/null; systemctl daemon-reload 2>/dev/null; true"
|
|
71
|
-
|
|
72
|
-
# 9. Cleanup
|
|
73
|
-
log "cleanup" "Keeping last 3 releases..."
|
|
74
|
-
r "ls -1d $BASE/releases/*/ 2>/dev/null | sort -r | tail -n +4 | xargs rm -rf 2>/dev/null; true"
|
|
75
|
-
|
|
76
|
-
echo
|
|
77
|
-
echo "[vibeOS-api] Done! Port $NEW_P | https://api.vibetheog.com/health"
|
package/scripts/deploy.sh
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
set -e
|
|
3
|
-
|
|
4
|
-
echo "[vibeOS-api] Deploying API server to VPS..."
|
|
5
|
-
|
|
6
|
-
VPS_HOST="${VPS_HOST:-45.132.242.217}"
|
|
7
|
-
VPS_USER="${VPS_USER:-root}"
|
|
8
|
-
DEPLOY_DIR="/var/www/vibeos-api"
|
|
9
|
-
|
|
10
|
-
echo "[1/7] Checking SSH connection to $VPS_HOST..."
|
|
11
|
-
ssh -o ConnectTimeout=10 "$VPS_USER@$VPS_HOST" "echo 'SSH OK'" || {
|
|
12
|
-
echo "ERROR: Cannot connect to VPS at $VPS_HOST"
|
|
13
|
-
exit 1
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
echo "[2/7] Installing Node.js on VPS..."
|
|
17
|
-
ssh "$VPS_USER@$VPS_HOST" bash << 'EOF'
|
|
18
|
-
if ! command -v node &>/dev/null; then
|
|
19
|
-
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
|
|
20
|
-
apt-get install -y nodejs
|
|
21
|
-
fi
|
|
22
|
-
echo "Node.js $(node --version) installed"
|
|
23
|
-
EOF
|
|
24
|
-
|
|
25
|
-
echo "[3/7] Creating deploy directory..."
|
|
26
|
-
ssh "$VPS_USER@$VPS_HOST" "mkdir -p $DEPLOY_DIR/data"
|
|
27
|
-
|
|
28
|
-
echo "[4/7] Uploading API server files..."
|
|
29
|
-
scp -r "$(dirname "$0")/../server.js" \
|
|
30
|
-
"$(dirname "$0")/../lib/" \
|
|
31
|
-
"$(dirname "$0")/../routes/" \
|
|
32
|
-
"$(dirname "$0")/../middleware/" \
|
|
33
|
-
"$(dirname "$0")/../package.json" \
|
|
34
|
-
"$VPS_USER@$VPS_HOST:$DEPLOY_DIR/"
|
|
35
|
-
|
|
36
|
-
echo "[5/7] Installing dependencies..."
|
|
37
|
-
ssh "$VPS_USER@$VPS_HOST" "cd $DEPLOY_DIR && npm install --production"
|
|
38
|
-
|
|
39
|
-
echo "[6/7] Setting up systemd service..."
|
|
40
|
-
scp "$(dirname "$0")/../vibeos-api.service" "$VPS_USER@$VPS_HOST:/tmp/vibeos-api.service"
|
|
41
|
-
ssh "$VPS_USER@$VPS_HOST" bash << EOF
|
|
42
|
-
cp /tmp/vibeos-api.service /etc/systemd/system/vibeos-api.service
|
|
43
|
-
systemctl daemon-reload
|
|
44
|
-
systemctl enable vibeos-api
|
|
45
|
-
systemctl restart vibeos-api
|
|
46
|
-
rm /tmp/vibeos-api.service
|
|
47
|
-
EOF
|
|
48
|
-
|
|
49
|
-
echo "[7/7] Setting up Nginx reverse proxy..."
|
|
50
|
-
scp "$(dirname "$0")/../nginx-vibetheog-api.conf" "$VPS_USER@$VPS_HOST:/tmp/vibeos-api-nginx.conf"
|
|
51
|
-
ssh "$VPS_USER@$VPS_HOST" bash << 'EOF'
|
|
52
|
-
cp /tmp/vibeos-api-nginx.conf /etc/nginx/sites-available/vibeos-api.conf
|
|
53
|
-
ln -sf /etc/nginx/sites-available/vibeos-api.conf /etc/nginx/sites-enabled/
|
|
54
|
-
nginx -t && systemctl reload nginx
|
|
55
|
-
rm /tmp/vibeos-api-nginx.conf
|
|
56
|
-
EOF
|
|
57
|
-
|
|
58
|
-
echo ""
|
|
59
|
-
echo "[vibeOS-api] Deployment complete!"
|
|
60
|
-
echo " API URL: https://api.vibetheog.com"
|
|
61
|
-
echo " Health: https://api.vibetheog.com/health"
|
|
62
|
-
echo ""
|
|
63
|
-
echo "Next steps:"
|
|
64
|
-
echo " 1. SSH into VPS and create .env file: cp $DEPLOY_DIR/.env.example $DEPLOY_DIR/.env"
|
|
65
|
-
echo " 2. Set VIBEOS_API_MASTER_KEY in .env"
|
|
66
|
-
echo " 3. Run: cd $DEPLOY_DIR && npm run seed"
|
|
67
|
-
echo " 4. Add DNS record: api.vibetheog.com -> $VPS_HOST"
|
|
68
|
-
echo " 5. Run: certbot --nginx -d api.vibetheog.com"
|
package/scripts/release.mjs
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from "node:child_process"
|
|
3
|
-
import { readFileSync, writeFileSync } from "node:fs"
|
|
4
|
-
import { resolve, dirname } from "node:path"
|
|
5
|
-
import { fileURLToPath } from "node:url"
|
|
6
|
-
|
|
7
|
-
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
8
|
-
const ROOT = resolve(__dirname, "..")
|
|
9
|
-
|
|
10
|
-
const args = process.argv.slice(2)
|
|
11
|
-
const isCI = args.includes("--ci")
|
|
12
|
-
const bumpType = args.find(a => ["patch","minor","major"].includes(a)) || "patch"
|
|
13
|
-
|
|
14
|
-
const pkgPath = resolve(ROOT, "package.json")
|
|
15
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"))
|
|
16
|
-
const [major, minor, patch] = pkg.version.split(".").map(Number)
|
|
17
|
-
|
|
18
|
-
let newVersion
|
|
19
|
-
if (bumpType === "major") newVersion = `${major + 1}.0.0`
|
|
20
|
-
else if (bumpType === "minor") newVersion = `${major}.${minor + 1}.0`
|
|
21
|
-
else newVersion = `${major}.${minor}.${patch + 1}`
|
|
22
|
-
|
|
23
|
-
pkg.version = newVersion
|
|
24
|
-
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n")
|
|
25
|
-
|
|
26
|
-
if (isCI) {
|
|
27
|
-
execSync(`git add package.json`, { cwd: ROOT, stdio: "inherit" })
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
console.log(`[vibeOScore] Version bumped to ${newVersion}`)
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { getDb, initDb } from "../lib/db.js"
|
|
2
|
-
import { randomBytes } from "node:crypto"
|
|
3
|
-
|
|
4
|
-
const MASTER_KEY = process.env.VIBEOS_API_MASTER_KEY || randomBytes(32).toString("hex")
|
|
5
|
-
const SEAT_NAME = process.env.SEAT_NAME || "vibeOS Default"
|
|
6
|
-
const SEAT_EMAIL = process.env.SEAT_EMAIL || ""
|
|
7
|
-
|
|
8
|
-
initDb()
|
|
9
|
-
const db = getDb()
|
|
10
|
-
|
|
11
|
-
const existing = db.prepare("SELECT COUNT(*) as count FROM seats").get()
|
|
12
|
-
if (existing.count > 0) {
|
|
13
|
-
console.log("[seed] Database already has seats, skipping")
|
|
14
|
-
process.exit(0)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const seatResult = db.prepare("INSERT INTO seats (name, email) VALUES (?, ?)").run(SEAT_NAME, SEAT_EMAIL || null)
|
|
18
|
-
console.log(`[seed] Created seat: ${SEAT_NAME} (id: ${seatResult.lastInsertRowid})`)
|
|
19
|
-
|
|
20
|
-
const token = "vos_" + randomBytes(32).toString("hex")
|
|
21
|
-
const tokenResult = db.prepare("INSERT INTO api_tokens (token, seat_id, label) VALUES (?, ?, ?)").run(token, seatResult.lastInsertRowid, "default")
|
|
22
|
-
|
|
23
|
-
console.log(`[seed] Created API token: ${token}`)
|
|
24
|
-
console.log(`[seed] Master key: ${MASTER_KEY}`)
|
|
25
|
-
console.log("")
|
|
26
|
-
console.log("IMPORTANT: Save these credentials!")
|
|
27
|
-
console.log(` VIBEOS_API_MASTER_KEY=${MASTER_KEY}`)
|
|
28
|
-
console.log(` VIBEOS_API_TOKEN=${token}`)
|
|
29
|
-
console.log(` API_URL=http://localhost:3000`)
|