owlservable 0.2.1 → 0.2.2
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/auto.cjs +1 -2
- package/index.cjs +400 -0
- package/package.json +6 -2
package/auto.cjs
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import('./index.js').then(function(m) { m.init() }).catch(function() {})
|
|
1
|
+
require('./index.cjs').init()
|
package/index.cjs
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const { EventEmitter } = require('node:events')
|
|
3
|
+
const http = require('node:http')
|
|
4
|
+
const https = require('node:https')
|
|
5
|
+
const fs = require('node:fs')
|
|
6
|
+
const path = require('node:path')
|
|
7
|
+
|
|
8
|
+
const __dir = __dirname
|
|
9
|
+
|
|
10
|
+
const MAX_REQ_BODY = 32 * 1024
|
|
11
|
+
const MAX_RES_BODY = 64 * 1024
|
|
12
|
+
const MAX_PARSE = 512 * 1024
|
|
13
|
+
|
|
14
|
+
const emitter = new EventEmitter()
|
|
15
|
+
emitter.setMaxListeners(100)
|
|
16
|
+
|
|
17
|
+
const requests = []
|
|
18
|
+
let nextId = 1
|
|
19
|
+
|
|
20
|
+
function addRequest(entry) {
|
|
21
|
+
try {
|
|
22
|
+
const r = { id: nextId++, ...entry, timestamp: Date.now() }
|
|
23
|
+
requests.push(r)
|
|
24
|
+
if (requests.length > 500) requests.shift()
|
|
25
|
+
emitter.emit('request', r)
|
|
26
|
+
if (saveState.enabled) persistLine(r)
|
|
27
|
+
return r
|
|
28
|
+
} catch (_) {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function updateRequest(id, patch) {
|
|
32
|
+
try {
|
|
33
|
+
const r = requests.find(r => r.id === id)
|
|
34
|
+
if (r) {
|
|
35
|
+
Object.assign(r, patch)
|
|
36
|
+
emitter.emit('update', { id, patch })
|
|
37
|
+
if (saveState.enabled && (patch.tokens || patch.resBody)) persistLine(r)
|
|
38
|
+
}
|
|
39
|
+
} catch (_) {}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getRequests() { try { return requests.slice() } catch (_) { return [] } }
|
|
43
|
+
|
|
44
|
+
function clearRequests() {
|
|
45
|
+
requests.splice(0)
|
|
46
|
+
if (saveState.enabled) try { fs.writeFileSync(saveState.filePath, '') } catch (_) {}
|
|
47
|
+
emitter.emit('reload', { requests: [], save: getSaveInfo() })
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function extractAiMeta(body) {
|
|
51
|
+
try {
|
|
52
|
+
const j = JSON.parse(body)
|
|
53
|
+
if (j.usage?.total_tokens != null)
|
|
54
|
+
return { model: j.model || null, tokens: { input: j.usage.prompt_tokens || 0, output: j.usage.completion_tokens || 0, total: j.usage.total_tokens } }
|
|
55
|
+
if (j.usage?.input_tokens != null) {
|
|
56
|
+
const i = j.usage.input_tokens || 0, o = j.usage.output_tokens || 0
|
|
57
|
+
return { model: j.model || null, tokens: { input: i, output: o, total: i + o } }
|
|
58
|
+
}
|
|
59
|
+
if (j.usageMetadata?.totalTokenCount != null) {
|
|
60
|
+
const m = j.usageMetadata
|
|
61
|
+
return { model: j.modelVersion || null, tokens: { input: m.promptTokenCount || 0, output: m.candidatesTokenCount || 0, total: m.totalTokenCount } }
|
|
62
|
+
}
|
|
63
|
+
if (j.meta?.billed_units) {
|
|
64
|
+
const b = j.meta.billed_units, i = b.input_tokens || 0, o = b.output_tokens || 0
|
|
65
|
+
if (i || o) return { model: null, tokens: { input: i, output: o, total: i + o } }
|
|
66
|
+
}
|
|
67
|
+
} catch (_) {}
|
|
68
|
+
return null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function extractAiMetaFromSSE(raw) {
|
|
72
|
+
try {
|
|
73
|
+
let input = 0, output = 0, model = null
|
|
74
|
+
for (const line of raw.split('\n')) {
|
|
75
|
+
if (!line.startsWith('data: ') || line === 'data: [DONE]') continue
|
|
76
|
+
try {
|
|
77
|
+
const j = JSON.parse(line.slice(6))
|
|
78
|
+
if (j.usage?.total_tokens != null)
|
|
79
|
+
return { model: j.model || model, tokens: { input: j.usage.prompt_tokens || 0, output: j.usage.completion_tokens || 0, total: j.usage.total_tokens } }
|
|
80
|
+
if (j.type === 'message_start' && j.message) {
|
|
81
|
+
if (j.message.usage?.input_tokens) input = j.message.usage.input_tokens
|
|
82
|
+
if (j.message.model) model = j.message.model
|
|
83
|
+
}
|
|
84
|
+
if (j.type === 'message_delta' && j.usage?.output_tokens)
|
|
85
|
+
output = j.usage.output_tokens
|
|
86
|
+
} catch (_) {}
|
|
87
|
+
}
|
|
88
|
+
if (input || output) return { model, tokens: { input, output, total: input + output } }
|
|
89
|
+
} catch (_) {}
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function capStr(s, max) {
|
|
94
|
+
if (!s || typeof s !== 'string') return null
|
|
95
|
+
return s.length <= max ? s : s.slice(0, max) + '…'
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function bodyFromInit(b) {
|
|
99
|
+
try {
|
|
100
|
+
if (typeof b === 'string') return capStr(b, MAX_REQ_BODY)
|
|
101
|
+
if (b instanceof URLSearchParams) return capStr(b.toString(), MAX_REQ_BODY)
|
|
102
|
+
if (b instanceof ArrayBuffer) return capStr(Buffer.from(b).toString('utf8'), MAX_REQ_BODY)
|
|
103
|
+
if (ArrayBuffer.isView(b)) return capStr(Buffer.from(b.buffer, b.byteOffset, b.byteLength).toString('utf8'), MAX_REQ_BODY)
|
|
104
|
+
} catch (_) {}
|
|
105
|
+
return null
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
let patched = false
|
|
109
|
+
|
|
110
|
+
function handleJsonResponse(record, getText) {
|
|
111
|
+
getText()
|
|
112
|
+
.then(body => {
|
|
113
|
+
const meta = extractAiMeta(body)
|
|
114
|
+
const update = { metaPending: false, resBody: capStr(body, MAX_RES_BODY) }
|
|
115
|
+
if (meta) Object.assign(update, meta)
|
|
116
|
+
updateRequest(record.id, update)
|
|
117
|
+
})
|
|
118
|
+
.catch(() => updateRequest(record.id, { metaPending: false }))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function handleSseResponse(record, getText) {
|
|
122
|
+
getText()
|
|
123
|
+
.then(body => {
|
|
124
|
+
const meta = extractAiMetaFromSSE(body)
|
|
125
|
+
const update = { metaPending: false }
|
|
126
|
+
if (meta) Object.assign(update, meta)
|
|
127
|
+
updateRequest(record.id, update)
|
|
128
|
+
})
|
|
129
|
+
.catch(() => updateRequest(record.id, { metaPending: false }))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function patchFetch() {
|
|
133
|
+
const orig = globalThis.fetch
|
|
134
|
+
globalThis.fetch = async function interceptedFetch(input, init) {
|
|
135
|
+
let url = 'unknown', method = 'GET', reqBody = null
|
|
136
|
+
try {
|
|
137
|
+
url = input instanceof Request ? input.url : String(input)
|
|
138
|
+
method = (init?.method || (input instanceof Request ? input.method : null) || 'GET').toUpperCase()
|
|
139
|
+
const rawBody = init?.body ?? null
|
|
140
|
+
if (typeof rawBody === 'string' && rawBody.includes('"stream"')) {
|
|
141
|
+
try {
|
|
142
|
+
const parsed = JSON.parse(rawBody)
|
|
143
|
+
if (parsed.stream === true && !parsed.stream_options?.include_usage) {
|
|
144
|
+
parsed.stream_options = { ...parsed.stream_options, include_usage: true }
|
|
145
|
+
init = { ...init, body: JSON.stringify(parsed) }
|
|
146
|
+
}
|
|
147
|
+
} catch (_) {}
|
|
148
|
+
}
|
|
149
|
+
reqBody = bodyFromInit(init?.body ?? null)
|
|
150
|
+
} catch (_) {}
|
|
151
|
+
|
|
152
|
+
const start = Date.now()
|
|
153
|
+
try {
|
|
154
|
+
const res = await orig.call(this, input, init)
|
|
155
|
+
const ct = res.headers.get('content-type') || ''
|
|
156
|
+
const isJson = ct.includes('application/json')
|
|
157
|
+
const isSse = ct.includes('text/event-stream')
|
|
158
|
+
const r = addRequest({ url, method, status: res.status, latency: Date.now() - start, reqBody, metaPending: isJson || isSse })
|
|
159
|
+
if (r && isJson) handleJsonResponse(r, () => res.clone().text())
|
|
160
|
+
if (r && isSse) handleSseResponse(r, () => res.clone().text())
|
|
161
|
+
return res
|
|
162
|
+
} catch (err) {
|
|
163
|
+
try { addRequest({ url, method, status: 0, latency: Date.now() - start, reqBody, error: err.message }) } catch (_) {}
|
|
164
|
+
throw err
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function patchHttpModule(mod, protocol) {
|
|
170
|
+
const orig = mod.request
|
|
171
|
+
mod.request = function interceptedRequest(...args) {
|
|
172
|
+
const start = Date.now()
|
|
173
|
+
let url = 'unknown', method = 'GET'
|
|
174
|
+
try {
|
|
175
|
+
const first = args[0]
|
|
176
|
+
if (typeof first === 'string') { url = first; method = args[1]?.method || 'GET' }
|
|
177
|
+
else if (first instanceof URL) { url = first.toString(); method = args[1]?.method || 'GET' }
|
|
178
|
+
else if (first && typeof first === 'object') {
|
|
179
|
+
url = `${protocol}://${first.hostname || first.host || 'localhost'}${first.port ? ':' + first.port : ''}${first.path || '/'}`
|
|
180
|
+
method = first.method || 'GET'
|
|
181
|
+
}
|
|
182
|
+
} catch (_) {}
|
|
183
|
+
|
|
184
|
+
const req = orig.apply(mod, args)
|
|
185
|
+
const reqChunks = []; let reqSize = 0
|
|
186
|
+
const origWrite = req.write
|
|
187
|
+
req.write = function(chunk, encoding, cb) {
|
|
188
|
+
try {
|
|
189
|
+
if (reqSize < MAX_REQ_BODY) {
|
|
190
|
+
const s = Buffer.isBuffer(chunk) ? chunk.toString('utf8') : typeof chunk === 'string' ? chunk : ''
|
|
191
|
+
if (s) { reqChunks.push(s); reqSize += s.length }
|
|
192
|
+
}
|
|
193
|
+
} catch (_) {}
|
|
194
|
+
return origWrite.call(this, chunk, encoding, cb)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
req.once('response', res => {
|
|
199
|
+
const reqBody = reqSize > 0 ? reqChunks.join('').slice(0, MAX_REQ_BODY) : null
|
|
200
|
+
const ct = res.headers['content-type'] || ''
|
|
201
|
+
const isJson = ct.includes('application/json')
|
|
202
|
+
const isSse = ct.includes('text/event-stream')
|
|
203
|
+
const r = addRequest({ url, method: method.toUpperCase(), status: res.statusCode, latency: Date.now() - start, reqBody, metaPending: isJson || isSse })
|
|
204
|
+
if (r && (isJson || isSse)) {
|
|
205
|
+
try {
|
|
206
|
+
const chunks = []; let size = 0
|
|
207
|
+
res.on('data', chunk => { try { if (size < MAX_PARSE) { chunks.push(chunk); size += chunk.length } } catch (_) {} })
|
|
208
|
+
const getText = () => new Promise((resolve, reject) => {
|
|
209
|
+
res.once('end', () => { try { resolve(Buffer.concat(chunks).toString('utf8')) } catch (e) { reject(e) } })
|
|
210
|
+
res.once('error', reject)
|
|
211
|
+
})
|
|
212
|
+
if (isJson) handleJsonResponse(r, getText)
|
|
213
|
+
if (isSse) handleSseResponse(r, getText)
|
|
214
|
+
} catch (_) { updateRequest(r.id, { metaPending: false }) }
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
req.once('error', err => {
|
|
218
|
+
try { addRequest({ url, method: method.toUpperCase(), status: 0, latency: Date.now() - start, error: err.message }) } catch (_) {}
|
|
219
|
+
})
|
|
220
|
+
} catch (_) {}
|
|
221
|
+
|
|
222
|
+
return req
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function patch() {
|
|
227
|
+
if (patched) return; patched = true
|
|
228
|
+
try { if (typeof globalThis.fetch === 'function') patchFetch() } catch (_) {}
|
|
229
|
+
try { patchHttpModule(http, 'http') } catch (_) {}
|
|
230
|
+
try { patchHttpModule(https, 'https') } catch (_) {}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const saveState = { enabled: false, filePath: null, retention: null }
|
|
234
|
+
|
|
235
|
+
function getSaveInfo() {
|
|
236
|
+
if (!saveState.enabled) return { enabled: false }
|
|
237
|
+
return { enabled: true, filePath: saveState.filePath, relPath: path.relative(process.cwd(), saveState.filePath).replace(/\\/g, '/'), retention: saveState.retention }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function persistLine(r) {
|
|
241
|
+
try { fs.appendFileSync(saveState.filePath, JSON.stringify(r) + '\n') } catch (_) {}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function parseLog(raw, cutoff) {
|
|
245
|
+
const byId = new Map()
|
|
246
|
+
for (const line of raw.split('\n')) {
|
|
247
|
+
try { const r = JSON.parse(line); if (r.id && r.timestamp > cutoff) byId.set(r.id, r) } catch (_) {}
|
|
248
|
+
}
|
|
249
|
+
return [...byId.values()].sort((a, b) => a.id - b.id)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function pruneFile() {
|
|
253
|
+
if (!saveState.filePath) return
|
|
254
|
+
try {
|
|
255
|
+
const raw = fs.readFileSync(saveState.filePath, 'utf8').trim()
|
|
256
|
+
if (!raw) return
|
|
257
|
+
const entries = parseLog(raw, Date.now() - saveState.retention)
|
|
258
|
+
const out = entries.map(JSON.stringify).join('\n')
|
|
259
|
+
fs.writeFileSync(saveState.filePath, out + (out ? '\n' : ''))
|
|
260
|
+
} catch (_) {}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function enableSave(retentionMs) {
|
|
264
|
+
const filePath = path.join(process.cwd(), '.owlservable', 'log.ndjson')
|
|
265
|
+
const dir = path.dirname(filePath)
|
|
266
|
+
try { fs.mkdirSync(dir, { recursive: true }) } catch (_) {}
|
|
267
|
+
try { const gi = path.join(dir, '.gitignore'); if (!fs.existsSync(gi)) fs.writeFileSync(gi, '*\n') } catch (_) {}
|
|
268
|
+
try { fs.writeFileSync(path.join(dir, 'config.json'), JSON.stringify({ save: true, retention: retentionMs })) } catch (_) {}
|
|
269
|
+
saveState.enabled = true
|
|
270
|
+
saveState.filePath = filePath
|
|
271
|
+
saveState.retention = retentionMs
|
|
272
|
+
try {
|
|
273
|
+
const raw = fs.readFileSync(filePath, 'utf8').trim()
|
|
274
|
+
if (raw) {
|
|
275
|
+
const loaded = parseLog(raw, Date.now() - retentionMs)
|
|
276
|
+
for (const r of loaded) {
|
|
277
|
+
if (!requests.find(x => x.id === r.id)) requests.push(r)
|
|
278
|
+
if (r.id >= nextId) nextId = r.id + 1
|
|
279
|
+
}
|
|
280
|
+
if (requests.length > 500) requests.splice(0, requests.length - 500)
|
|
281
|
+
}
|
|
282
|
+
} catch (_) {}
|
|
283
|
+
pruneFile()
|
|
284
|
+
emitter.emit('reload', { requests: getRequests(), save: getSaveInfo() })
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function disableSave() {
|
|
288
|
+
saveState.enabled = false
|
|
289
|
+
try { fs.unlinkSync(path.join(process.cwd(), '.owlservable', 'config.json')) } catch (_) {}
|
|
290
|
+
emitter.emit('saveConfig', getSaveInfo())
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
setInterval(() => { if (saveState.enabled) pruneFile() }, 3_600_000).unref()
|
|
294
|
+
|
|
295
|
+
let server = null
|
|
296
|
+
let DASHBOARD_HTML = null
|
|
297
|
+
|
|
298
|
+
function getDashboard() {
|
|
299
|
+
if (!DASHBOARD_HTML) DASHBOARD_HTML = fs.readFileSync(path.join(__dir, 'dashboard.html'), 'utf8')
|
|
300
|
+
return DASHBOARD_HTML
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function readBody(req) {
|
|
304
|
+
return new Promise((resolve, reject) => {
|
|
305
|
+
let body = ''
|
|
306
|
+
req.on('data', c => { body += c })
|
|
307
|
+
req.on('end', () => resolve(body))
|
|
308
|
+
req.on('error', reject)
|
|
309
|
+
})
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function startDashboard(port) {
|
|
313
|
+
if (server) return
|
|
314
|
+
|
|
315
|
+
server = http.createServer((req, res) => {
|
|
316
|
+
try {
|
|
317
|
+
const urlPath = req.url?.split('?')[0] || '/'
|
|
318
|
+
|
|
319
|
+
if (urlPath === '/events') {
|
|
320
|
+
res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' })
|
|
321
|
+
const send = d => { try { res.write('data: ' + JSON.stringify(d) + '\n\n') } catch (_) {} }
|
|
322
|
+
send({ type: 'init', requests: getRequests(), save: getSaveInfo() })
|
|
323
|
+
const onReq = r => send({ type: 'request', record: r })
|
|
324
|
+
const onUpdate = ({ id, patch }) => send({ type: 'update', id, patch })
|
|
325
|
+
const onSave = cfg => send({ type: 'saveConfig', config: cfg })
|
|
326
|
+
const onReload = data => send({ type: 'reload', ...data })
|
|
327
|
+
emitter.on('request', onReq)
|
|
328
|
+
emitter.on('update', onUpdate)
|
|
329
|
+
emitter.on('saveConfig', onSave)
|
|
330
|
+
emitter.on('reload', onReload)
|
|
331
|
+
req.on('close', () => {
|
|
332
|
+
emitter.off('request', onReq)
|
|
333
|
+
emitter.off('update', onUpdate)
|
|
334
|
+
emitter.off('saveConfig', onSave)
|
|
335
|
+
emitter.off('reload', onReload)
|
|
336
|
+
})
|
|
337
|
+
return
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (urlPath === '/api/save' && req.method === 'POST') {
|
|
341
|
+
readBody(req).then(body => {
|
|
342
|
+
const { action, retention } = JSON.parse(body)
|
|
343
|
+
if (action === 'enable' && retention > 0) enableSave(retention)
|
|
344
|
+
else if (action === 'disable') disableSave()
|
|
345
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
346
|
+
res.end(JSON.stringify({ ok: true, save: getSaveInfo() }))
|
|
347
|
+
}).catch(e => { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: e.message })) })
|
|
348
|
+
return
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (urlPath === '/api/clear' && req.method === 'POST') {
|
|
352
|
+
clearRequests()
|
|
353
|
+
res.writeHead(200, { 'Content-Type': 'application/json' })
|
|
354
|
+
res.end(JSON.stringify({ ok: true }))
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (urlPath === '/owl.png') {
|
|
359
|
+
try {
|
|
360
|
+
const data = fs.readFileSync(path.join(__dir, 'owl.png'))
|
|
361
|
+
res.writeHead(200, { 'Content-Type': 'image/png', 'Cache-Control': 'public, max-age=86400' })
|
|
362
|
+
res.end(data)
|
|
363
|
+
} catch (_) { res.writeHead(404); res.end() }
|
|
364
|
+
return
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
|
|
368
|
+
res.end(getDashboard())
|
|
369
|
+
} catch (_) { try { if (!res.headersSent) { res.writeHead(500); res.end() } } catch (_) {} }
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
server.on('error', e => { if (e.code === 'EADDRINUSE') console.warn('[owlservable] Port ' + port + ' in use — dashboard not started') })
|
|
373
|
+
server.listen(port, '127.0.0.1', () => console.log('[owlservable] Dashboard → http://localhost:' + port))
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
let initialized = false
|
|
377
|
+
|
|
378
|
+
function init({ port = 4321, dashboard = true, logging = false, save = false, retention = 86_400_000 } = {}) {
|
|
379
|
+
if (initialized) return; initialized = true
|
|
380
|
+
if (process.env.NODE_ENV === 'production') {
|
|
381
|
+
console.warn('[owlservable] Refusing to start in NODE_ENV=production — remove owlservable from your production bundle')
|
|
382
|
+
return
|
|
383
|
+
}
|
|
384
|
+
try { patch() } catch (_) {}
|
|
385
|
+
if (!save) {
|
|
386
|
+
try {
|
|
387
|
+
const cfg = JSON.parse(fs.readFileSync(path.join(process.cwd(), '.owlservable', 'config.json'), 'utf8'))
|
|
388
|
+
if (cfg.save) { save = true; retention = cfg.retention || retention }
|
|
389
|
+
} catch (_) {}
|
|
390
|
+
}
|
|
391
|
+
if (save) try { enableSave(retention) } catch (_) {}
|
|
392
|
+
if (dashboard) try { startDashboard(port) } catch (_) {}
|
|
393
|
+
if (logging) {
|
|
394
|
+
emitter.on('request', r => {
|
|
395
|
+
try { console.log('[owlservable]', r.method, r.url, '→', r.status || r.error || '?', '(' + (r.latency || 0) + 'ms)') } catch (_) {}
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
module.exports = { init }
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "owlservable",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Minimalist Observability Platform. Zero config, zero dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"types": "index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./index.js",
|
|
11
|
+
"require": "./index.cjs"
|
|
12
|
+
},
|
|
10
13
|
"./auto": {
|
|
11
14
|
"import": "./auto.js",
|
|
12
15
|
"require": "./auto.cjs"
|
|
@@ -14,6 +17,7 @@
|
|
|
14
17
|
},
|
|
15
18
|
"files": [
|
|
16
19
|
"index.js",
|
|
20
|
+
"index.cjs",
|
|
17
21
|
"index.d.ts",
|
|
18
22
|
"auto.js",
|
|
19
23
|
"auto.cjs",
|