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.
Files changed (45) hide show
  1. package/client.js +1 -0
  2. package/client.ts +2 -0
  3. package/lib/logger.js +27 -0
  4. package/mcp-server.js +5 -4
  5. package/mcp-server.ts +4 -3
  6. package/package.json +12 -10
  7. package/dashboard/dist/assets/index-BnPt1Fii.js +0 -1
  8. package/dashboard/dist/assets/index-CfH00tOL.css +0 -1
  9. package/dashboard/dist/index.html +0 -3
  10. package/lib/blackbox-rf.js +0 -1099
  11. package/lib/blackbox.js +0 -137
  12. package/lib/compression.js +0 -119
  13. package/lib/db.js +0 -106
  14. package/lib/db.ts +0 -113
  15. package/lib/delegation.js +0 -137
  16. package/lib/meta-controller.js +0 -418
  17. package/lib/meta-controller.mjs +0 -499
  18. package/lib/patterns.js +0 -150
  19. package/lib/resolution-tracker.js +0 -486
  20. package/lib/stress.js +0 -84
  21. package/lib/tdd.js +0 -218
  22. package/lib/tier-routing.js +0 -48
  23. package/middleware/auth.js +0 -75
  24. package/middleware/auth.ts +0 -87
  25. package/middleware/usage-logging.js +0 -29
  26. package/middleware/usage-logging.ts +0 -41
  27. package/nginx-vibetheog-api.conf +0 -64
  28. package/routes/admin.js +0 -93
  29. package/routes/admin.ts +0 -107
  30. package/routes/blackbox.js +0 -463
  31. package/routes/compression.js +0 -12
  32. package/routes/delegation.js +0 -30
  33. package/routes/patterns.js +0 -53
  34. package/routes/pricing.js +0 -62
  35. package/routes/stress.js +0 -30
  36. package/routes/tdd.js +0 -68
  37. package/routes/tier-routing.js +0 -31
  38. package/scripts/dashboard-server.mjs +0 -246
  39. package/scripts/deploy-zero-downtime.sh +0 -77
  40. package/scripts/deploy.sh +0 -68
  41. package/scripts/release.mjs +0 -30
  42. package/scripts/seed-master-token.js +0 -29
  43. package/scripts/start-all.mjs +0 -34
  44. package/server.js +0 -88
  45. 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
- }
@@ -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"
@@ -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`)