wolverine-ai 1.0.0 → 1.1.0

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/PLATFORM.md CHANGED
@@ -103,6 +103,14 @@ Content-Type: application/json
103
103
  "chat": { "tokens": 25000, "cost": 0.05, "calls": 60 },
104
104
  "classify": { "tokens": 3000, "cost": 0.001, "calls": 15 },
105
105
  "develop": { "tokens": 5000, "cost": 0.03, "calls": 5 }
106
+ },
107
+ "byModel": {
108
+ "gpt-5.4-mini": { "tokens": 30000, "cost": 0.06, "calls": 40 },
109
+ "gpt-4o-mini": { "tokens": 15000, "cost": 0.02, "calls": 45 }
110
+ },
111
+ "byTool": {
112
+ "call_endpoint": { "tokens": 5000, "cost": 0.01, "calls": 20 },
113
+ "search_brain": { "tokens": 2000, "cost": 0.005, "calls": 10 }
106
114
  }
107
115
  },
108
116
 
package/README.md CHANGED
@@ -11,12 +11,21 @@ Built on patterns from [claw-code](https://github.com/instructkr/claw-code) —
11
11
  ## Quick Start
12
12
 
13
13
  ```bash
14
+ # Install from npm
15
+ npm i wolverine-ai
16
+
17
+ # Or clone from GitHub
14
18
  git clone https://github.com/bobbyswhip/Wolverine.git
15
19
  cd Wolverine
16
20
  npm install
21
+
22
+ # Configure
17
23
  cp .env.example .env.local
18
24
  # Edit .env.local — add your OPENAI_API_KEY and generate an ADMIN_KEY
25
+
26
+ # Run
19
27
  npm start
28
+ # or: npx wolverine server/index.js
20
29
  ```
21
30
 
22
31
  Dashboard opens at `http://localhost:PORT+1`. Server runs on `PORT`.
@@ -359,7 +368,8 @@ Startup:
359
368
  - Auto-registers on first run, retries every 60s until platform responds
360
369
  - Saves key to `.wolverine/platform-key` (survives restarts)
361
370
  - Sends one ~2KB JSON POST every 60 seconds (5s timeout, non-blocking)
362
- - Payload matches [PLATFORM.md](PLATFORM.md) spec: `instanceId`, `server`, `process`, `routes`, `repairs`, `usage`, `brain`, `backups`
371
+ - Payload matches [PLATFORM.md](PLATFORM.md) spec: `instanceId`, `server`, `process`, `routes`, `repairs`, `usage` (tokens/cost/calls + `byCategory` + `byModel` + `byTool`), `brain`, `backups`
372
+ - Platform analytics aggregates across all servers: total tokens/cost, breakdown by category (heal/chat/develop/security/classify/research/brain), by model, by tool
363
373
  - Secrets redacted before sending
364
374
  - Offline-resilient: queues up to 1440 heartbeats locally, drains on reconnect
365
375
 
@@ -19,13 +19,21 @@ server/
19
19
 
20
20
  ## Rules
21
21
 
22
+ ### Ports
23
+ - **Development**: use port 3000 (standard, no admin required, firewall-friendly)
24
+ - **Production**: use port 443 (HTTPS) or 80 (HTTP) behind a reverse proxy (nginx/caddy)
25
+ - **Never** use random high ports in production — they bypass firewalls and confuse load balancers
26
+ - **Always** use HTTPS in production — terminate TLS at the reverse proxy, not in Node
27
+ - Dashboard runs on port+1 automatically (3001 in dev, not exposed in prod)
28
+
22
29
  ### Security
23
30
  - Never expose secrets in responses — use env vars, never hardcode
24
- - Validate ALL input — use express.json() with size limits
25
- - Use helmet() for HTTP security headers in production
31
+ - Validate ALL input — Fastify has built-in JSON schema validation
32
+ - Use HTTPS in production reverse proxy (nginx/caddy) handles TLS
26
33
  - Rate limit public endpoints
27
- - Sanitize user input before database queries
34
+ - Sanitize user input before database queries — use the SQL skill
28
35
  - Never return stack traces in production error responses
36
+ - Use the sqlGuard() middleware on all routes that accept user input
29
37
 
30
38
  ### Scalability
31
39
  - Keep routes thin — business logic goes in services/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wolverine-ai",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Self-healing Node.js server framework powered by AI. Catches crashes, diagnoses errors, generates fixes, verifies, and restarts — automatically.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -18,7 +18,7 @@
18
18
  },
19
19
 
20
20
  "server": {
21
- "port": 6969,
21
+ "port": 3000,
22
22
  "maxRetries": 3,
23
23
  "maxMemoryMB": 512
24
24
  },
@@ -139,6 +139,58 @@ const SEED_DOCS = [
139
139
  text: "Telemetry architecture: 4 files, ~250 lines total. heartbeat.js sends one HTTP POST every 60s (5s timeout, non-blocking). register.js auto-registers and caches key in memory + disk. queue.js appends to JSONL file only on failure, trims lazily. telemetry.js collects from subsystems using optional chaining (no crashes if subsystem missing). All secrets redacted before sending. Response bodies drained immediately (res.resume). No blocking, no delays, no busy waits.",
140
140
  metadata: { topic: "telemetry-architecture" },
141
141
  },
142
+ {
143
+ text: "Server uses Fastify (migrated from Express). 5.6x faster routing: ~114k req/s vs Express ~20k req/s. Routes are async plugin functions: async function routes(fastify) { fastify.get('/', async () => ({...})); } module.exports = routes. Registered in index.js with fastify.register(require('./routes/X'), {prefix:'/X'}). JSON parsing is built-in, no middleware needed.",
144
+ metadata: { topic: "fastify" },
145
+ },
146
+ {
147
+ text: "npm package: wolverine-ai on npmjs.com. Install: npm i wolverine-ai. CLI: npx wolverine server/index.js. v1.0.0, 79 files, 125KB compressed. Includes src/, bin/, server/, examples/. GitHub: https://github.com/bobbyswhip/Wolverine",
148
+ metadata: { topic: "npm-package" },
149
+ },
150
+ {
151
+ text: "Dashboard has 9 panels: Overview (stats cards + recent events), Events (live SSE stream), Performance (endpoint metrics), Analytics (memory/CPU charts, route health, response times), Command (admin chat with 3-route classifier), Backups (server/ snapshots with status badges), Brain (vector store stats + function map), Repairs (error/resolution audit trail with tokens and cost), Tools (agent tool harness listing), Usage (token analytics by model/category/tool with USD costs).",
152
+ metadata: { topic: "dashboard-panels" },
153
+ },
154
+ {
155
+ text: "Command interface routing: AI classifier (CLASSIFIER_MODEL) returns SIMPLE/TOOLS/AGENT. SIMPLE = brain knowledge only (CHAT_MODEL, no tools). TOOLS = live data with function calling (TOOL_MODEL, call_endpoint/read_file/search_brain). AGENT SMALL = smart edit (CODING_MODEL, 1 AI call, structured JSON file operations). AGENT MEDIUM = single agent (REASONING_MODEL, 8 turns). AGENT LARGE = sub-agents (explore→plan→fix).",
156
+ metadata: { topic: "command-routing" },
157
+ },
158
+ {
159
+ text: "Smart edit: for SMALL tier tasks, one AI call returns JSON with file operations: [{action:'create',path:'server/routes/X.js',content:'...'},{action:'edit',path:'server/index.js',find:'...',replace:'...'}]. Creates backup before changes, restarts server after, tests endpoint, rescans brain with new routes. Skills auto-injected into prompt when relevant.",
160
+ metadata: { topic: "smart-edit" },
161
+ },
162
+ {
163
+ text: "Token tracking: every AI call tracked with input/output tokens + USD cost. Categories: heal, develop, chat, security, classify, research, brain. Tracked by model, by category, by tool. Persisted to .wolverine/usage.json (aggregates) and .wolverine/usage-history.jsonl (full timeline). Auto-saves on every call. Dashboard shows charts + cost breakdowns. Pricing from src/logger/pricing.js, customizable via .wolverine/pricing.json.",
164
+ metadata: { topic: "token-tracking" },
165
+ },
166
+ {
167
+ text: "Repair history: dedicated audit trail at .wolverine/repair-history.json. Each entry: error, file, line, resolution, success, mode (fast/agent/sub-agents), model, tokens, cost, iteration, duration, filesModified. Dashboard Repairs panel shows stats (total, success rate, total cost, avg tokens) + scrollable history with per-repair details.",
168
+ metadata: { topic: "repair-history" },
169
+ },
170
+ {
171
+ text: "Skill registry: auto-discovers skills from src/skills/ on startup. Each skill exports SKILL_NAME, SKILL_DESCRIPTION, SKILL_KEYWORDS, SKILL_USAGE. Registry matches skills to commands using token scoring (claw-code pattern). Matched skills get injected into agent prompts before AI calls. SQL skill auto-injects when building database features.",
172
+ metadata: { topic: "skill-registry" },
173
+ },
174
+ {
175
+ text: "Notifications: detects human-required errors (expired keys, billing, service down, certs, permissions, disk). Classifies errors as AI-fixable vs human-required using pattern matching. Generates AI summary (CHAT_MODEL). Fires before wasting tokens on repair. Console alert + dashboard event + optional webhook. Categories: auth, billing, service, cert, permission, disk.",
176
+ metadata: { topic: "notifications" },
177
+ },
178
+ {
179
+ text: "MCP integration: connect external tools via Model Context Protocol. Configure in .wolverine/mcp.json with per-server tool allowlists. Security: arg sanitization (secrets redacted before sending to MCP servers), result injection scanning, rate limiting per server, audit logging. Tools appear as mcp__server__tool in the agent. Supports stdio and HTTP transports.",
180
+ metadata: { topic: "mcp" },
181
+ },
182
+ {
183
+ text: "Demos: 7 demo servers in examples/demos/. Demo runner (examples/run-demo.js) copies demo into server/, runs wolverine, restores on exit. npm run demo:list shows all demos. Each demo is a proper Fastify server with routes/ that mirrors the real server/ structure. Tests: basic typo, multi-file, syntax error, secret leak, expired key, JSON config, null crash.",
184
+ metadata: { topic: "demos" },
185
+ },
186
+ {
187
+ text: "10 configurable models: REASONING_MODEL (multi-file agent), CODING_MODEL (code repair, Responses API for codex), CHAT_MODEL (simple text), TOOL_MODEL (function calling), CLASSIFIER_MODEL (routing), AUDIT_MODEL (injection detection), COMPACTING_MODEL (brain text compression), RESEARCH_MODEL (deep research), TEXT_EMBEDDING_MODEL (vectors). All in server/config/settings.json. Reasoning models auto-get 4x token limits for chain-of-thought.",
188
+ metadata: { topic: "model-slots" },
189
+ },
190
+ {
191
+ text: "Port best practices: development uses port 3000 (standard, no admin, firewall-friendly). Production uses 443 (HTTPS) or 80 (HTTP) behind a reverse proxy (nginx/caddy). Never use random high ports in production — they bypass firewalls and confuse load balancers. Always use HTTPS in production — terminate TLS at the proxy, not in Node. Dashboard auto-runs on port+1. Wolverine warns on startup if the port is non-standard.",
192
+ metadata: { topic: "port-security" },
193
+ },
142
194
  ];
143
195
 
144
196
  class Brain {
@@ -140,6 +140,20 @@ class WolverineRunner {
140
140
  maxRetries: this.maxRetries,
141
141
  });
142
142
 
143
+ // Port safety check
144
+ const port = parseInt(process.env.PORT, 10) || 3000;
145
+ const safeDevPorts = [3000, 3001, 8080, 8443];
146
+ const safeProdPorts = [80, 443, 8080, 8443];
147
+ const env = process.env.NODE_ENV || "development";
148
+
149
+ if (env === "production" && port !== 443 && port !== 80 && port !== 8443 && port !== 8080) {
150
+ console.log(chalk.yellow(` ⚠️ Port ${port} in production — recommend 443 (HTTPS) or 80 (HTTP) behind a reverse proxy`));
151
+ } else if (env !== "production" && !safeDevPorts.includes(port) && port < 1024) {
152
+ console.log(chalk.yellow(` ⚠️ Port ${port} requires root/admin — use 3000 for local development`));
153
+ } else if (port > 9999) {
154
+ console.log(chalk.yellow(` ⚠️ Port ${port} is non-standard — use 3000 (dev) or 443 (prod) for best compatibility`));
155
+ }
156
+
143
157
  // Initialize brain (scan project, seed docs, embed function map)
144
158
  try {
145
159
  await this.brain.init();
@@ -1029,6 +1029,7 @@ main{overflow-y:auto;padding:24px}
1029
1029
  <a data-panel="events">📋 Events</a>
1030
1030
  <a data-panel="perf">⚡ Performance</a>
1031
1031
  <a data-panel="analytics">📊 Analytics</a>
1032
+ <a data-panel="processes">⚙️ Processes</a>
1032
1033
  <div class="sep"></div>
1033
1034
  <div class="label">Agent</div>
1034
1035
  <a data-panel="command">💬 Command</a>
@@ -1059,6 +1060,32 @@ main{overflow-y:auto;padding:24px}
1059
1060
  </div>
1060
1061
  <div class="panel" id="p-events"><div class="card"><h3>Live Event Stream</h3><div class="ev-list" id="ev-all"></div></div></div>
1061
1062
  <div class="panel" id="p-perf"><div class="card"><h3>Endpoint Metrics</h3><div id="perf-list"><div class="empty">No traffic yet</div></div></div></div>
1063
+ <div class="panel" id="p-processes">
1064
+ <div class="stats" style="grid-template-columns:repeat(4,1fr)">
1065
+ <div class="stat-card heal"><div class="stat-val" id="proc-pid">-</div><div class="stat-lbl">Server PID</div></div>
1066
+ <div class="stat-card up"><div class="stat-val" id="proc-up">-</div><div class="stat-lbl">Uptime</div></div>
1067
+ <div class="stat-card err"><div class="stat-val" id="proc-mem">-</div><div class="stat-lbl">Memory (RSS)</div></div>
1068
+ <div class="stat-card brain"><div class="stat-val" id="proc-cpu">-</div><div class="stat-lbl">CPU %</div></div>
1069
+ </div>
1070
+ <div class="row2">
1071
+ <div class="card">
1072
+ <h3>System</h3>
1073
+ <div id="proc-sys"><div class="empty">Loading...</div></div>
1074
+ </div>
1075
+ <div class="card">
1076
+ <h3>Process Health</h3>
1077
+ <div id="proc-health"><div class="empty">Loading...</div></div>
1078
+ </div>
1079
+ </div>
1080
+ <div class="card">
1081
+ <h3>Memory Timeline</h3>
1082
+ <div id="proc-mem-chart" style="height:160px"></div>
1083
+ </div>
1084
+ <div class="card">
1085
+ <h3>Active Connections & Listeners</h3>
1086
+ <div id="proc-listen"><div class="empty">Loading...</div></div>
1087
+ </div>
1088
+ </div>
1062
1089
  <div class="panel" id="p-analytics">
1063
1090
  <div class="stats" style="grid-template-columns:repeat(4,1fr)">
1064
1091
  <div class="stat-card heal"><div class="stat-val" id="a-mem">-</div><div class="stat-lbl">Memory (RSS)</div></div>
@@ -1280,6 +1307,59 @@ async function refresh(){
1280
1307
  return '<div class="mrow" style="flex-wrap:wrap"><div style="flex:1"><span style="margin-right:6px">'+icon+'</span><span class="ep">'+esc(r.error).slice(0,60)+'</span></div><span class="vals">'+r.mode+' &middot; '+r.tokens.toLocaleString()+' tokens &middot; '+cost+' &middot; iter '+r.iteration+' &middot; '+(r.duration/1000).toFixed(1)+'s</span><div style="width:100%;font-size:.75rem;color:var(--text2);margin-top:4px">'+date+' — '+esc(r.resolution).slice(0,100)+'</div></div>';
1281
1308
  }).join('');
1282
1309
  }
1310
+ // Processes panel
1311
+ if(proc){
1312
+ $('proc-pid').textContent=proc.pid||'-';
1313
+ if(proc.current){
1314
+ $('proc-mem').textContent=proc.current.rss+'MB';
1315
+ $('proc-cpu').textContent=proc.current.cpu+'%';
1316
+ }
1317
+ // Uptime from server stats
1318
+ if(sr.session){
1319
+ const u=Math.round((sr.session.uptime||0)/1000);
1320
+ const h=Math.floor(u/3600),m=Math.floor((u%3600)/60),s=u%60;
1321
+ $('proc-up').textContent=(h>0?h+'h ':'')+(m>0?m+'m ':'')+s+'s';
1322
+ }
1323
+ // Leak detection
1324
+ if(proc.leakDetection){
1325
+ const ld=proc.leakDetection;
1326
+ $('proc-health').innerHTML='<div class="mrow"><span>Leak Detection</span><span class="vals">'+(ld.warning?'<span style="color:var(--yellow)">⚠️ Growing</span>':'<span style="color:var(--green)">✅ Stable</span>')+' ('+ld.consecutiveGrowth+'/'+ld.threshold+' samples)</span></div>'
1327
+ +'<div class="mrow"><span>Peak Memory</span><span class="vals"><b>'+(proc.peak?.memory||0)+'MB</b></span></div>'
1328
+ +'<div class="mrow"><span>Avg Memory</span><span class="vals"><b>'+(proc.average?.memory||0)+'MB</b></span></div>'
1329
+ +'<div class="mrow"><span>Avg CPU</span><span class="vals"><b>'+(proc.average?.cpu||0)+'%</b></span></div>'
1330
+ +'<div class="mrow"><span>Process Alive</span><span class="vals">'+(proc.alive?'<span style="color:var(--green)">✅ Yes</span>':'<span style="color:var(--red)">❌ No</span>')+'</span></div>';
1331
+ }
1332
+ // Memory timeline chart
1333
+ if(proc.samples&&proc.samples.length>1){
1334
+ const s=proc.samples,max=Math.max(...s.map(e=>e.rss))||1,w=$('proc-mem-chart').offsetWidth||500,h=150;
1335
+ const bw=Math.max(3,Math.floor(w/s.length)-1);
1336
+ let svg='<svg width="'+w+'" height="'+h+'">';
1337
+ s.forEach((e,i)=>{
1338
+ const bh=Math.max(2,Math.round((e.rss/max)*h*0.85));
1339
+ const c=e.rss>400?'var(--red)':e.rss>200?'var(--yellow)':'var(--blue)';
1340
+ svg+='<rect x="'+(i*(bw+1))+'" y="'+(h-bh)+'" width="'+bw+'" height="'+bh+'" fill="'+c+'" rx="1"><title>'+e.rss+'MB</title></rect>';
1341
+ });
1342
+ svg+='</svg>';
1343
+ $('proc-mem-chart').innerHTML=svg;
1344
+ }
1345
+ }
1346
+ // System info
1347
+ const sysInfo=await fetch(B+'/api/system').then(r=>r.json()).catch(()=>({}));
1348
+ if(sysInfo&&sysInfo.cpu){
1349
+ $('proc-sys').innerHTML=[
1350
+ '<div class="mrow"><span>Platform</span><span class="vals">'+esc(sysInfo.platform)+'/'+esc(sysInfo.arch)+'</span></div>',
1351
+ '<div class="mrow"><span>CPU</span><span class="vals">'+esc((sysInfo.cpu.model||'').slice(0,40))+' ('+sysInfo.cpu.cores+' cores)</span></div>',
1352
+ '<div class="mrow"><span>RAM</span><span class="vals">'+sysInfo.memory.totalGB+'GB total, '+sysInfo.memory.freeGB+'GB free ('+sysInfo.memory.usedPercent+'%)</span></div>',
1353
+ '<div class="mrow"><span>Disk</span><span class="vals">'+sysInfo.disk.totalGB+'GB total, '+sysInfo.disk.freeGB+'GB free ('+sysInfo.disk.usedPercent+'%)</span></div>',
1354
+ '<div class="mrow"><span>Node</span><span class="vals">'+esc(sysInfo.nodeVersion)+'</span></div>',
1355
+ '<div class="mrow"><span>Hostname</span><span class="vals">'+esc(sysInfo.hostname)+'</span></div>',
1356
+ '<div class="mrow"><span>Environment</span><span class="vals">'+esc(sysInfo.environment?.type||'unknown')+(sysInfo.environment?.cloud?' ('+sysInfo.environment.cloud+')':'')+'</span></div>',
1357
+ ].join('');
1358
+ // Listening ports
1359
+ $('proc-listen').innerHTML='<div class="mrow"><span>Server</span><span class="vals">:'+(sysInfo.hostname?'running':'?')+'</span></div>'
1360
+ +'<div class="mrow"><span>Dashboard</span><span class="vals">:'+P+'</span></div>'
1361
+ +'<div class="mrow"><span>Workers Recommended</span><span class="vals">'+sysInfo.recommended?.workers+'</span></div>';
1362
+ }
1283
1363
  // Analytics: process + routes
1284
1364
  if(proc&&proc.current){
1285
1365
  $('a-mem').textContent=proc.current.rss+'MB';
@@ -52,6 +52,8 @@ function collectHeartbeat(subsystems) {
52
52
  totalCost: usage?.session?.totalCostUsd || 0,
53
53
  totalCalls: usage?.session?.totalCalls || 0,
54
54
  byCategory: usage?.byCategory || {},
55
+ byModel: usage?.byModel || {},
56
+ byTool: usage?.byTool || {},
55
57
  },
56
58
 
57
59
  brain: { totalMemories: brain?.getStats()?.totalEntries || 0 },