mumpix 1.0.19 → 1.0.29
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/CHANGELOG.md +42 -14
- package/README.md +185 -8
- package/bin/mumpix.js +1 -405
- package/examples/agent-memory.js +1 -1
- package/examples/basic.js +1 -1
- package/examples/behavioral-primitives.js +50 -0
- package/examples/verified-mode.js +1 -1
- package/package.json +17 -13
- package/scripts/test-license-modes.cjs +87 -0
- package/src/brp/index.js +1 -0
- package/src/collapse/index.js +1 -0
- package/src/core/MumpixDB.js +210 -322
- package/src/core/audit.js +1 -173
- package/src/core/auth.js +1 -232
- package/src/core/inverted-index.js +144 -0
- package/src/core/license.js +1 -267
- package/src/core/ml-dsa.mjs +1 -25
- package/src/core/ml-kem.mjs +1 -32
- package/src/core/recall.js +1 -176
- package/src/core/store.js +335 -286
- package/src/core/wal-writer.js +83 -0
- package/src/index.js +20 -34
- package/src/integrations/developer-sdk.js +1 -165
- package/src/integrations/langchain-official.js +1 -0
- package/src/integrations/langchain.js +1 -131
- package/src/integrations/llamaindex-official.js +1 -0
- package/src/integrations/llamaindex.js +1 -86
- package/src/integrations/vector-sidecar.js +325 -0
- package/src/rlp/index.js +1 -0
- package/src/temporal/engine.js +1 -1894
- package/src/temporal/indexes.js +1 -178
- package/src/temporal/operators.js +1 -186
- package/scripts/postinstall-auth.js +0 -101
package/src/core/audit.js
CHANGED
|
@@ -1,173 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* MumpixAudit — immutable append-only audit log for Verified consistency mode
|
|
5
|
-
*
|
|
6
|
-
* Stored alongside the main file: agent.mumpix.audit
|
|
7
|
-
* Each line is a JSON entry — never modified, only appended.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
const fs = require('fs');
|
|
11
|
-
const path = require('path');
|
|
12
|
-
|
|
13
|
-
const AUDIT_SCHEMA_VERSION = 1;
|
|
14
|
-
|
|
15
|
-
class MumpixAudit {
|
|
16
|
-
constructor(dbPath) {
|
|
17
|
-
this.auditPath = path.resolve(dbPath) + '.audit';
|
|
18
|
-
this._entries = [];
|
|
19
|
-
this._fd = null;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
open() {
|
|
23
|
-
if (fs.existsSync(this.auditPath)) {
|
|
24
|
-
this._load();
|
|
25
|
-
} else {
|
|
26
|
-
// Write header
|
|
27
|
-
const header = {
|
|
28
|
-
_type: 'audit_header',
|
|
29
|
-
version: AUDIT_SCHEMA_VERSION,
|
|
30
|
-
created: Date.now(),
|
|
31
|
-
};
|
|
32
|
-
fs.writeFileSync(this.auditPath, JSON.stringify(header) + '\n', 'utf8');
|
|
33
|
-
}
|
|
34
|
-
this._fd = fs.openSync(this.auditPath, 'a');
|
|
35
|
-
return this;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
close() {
|
|
39
|
-
if (this._fd !== null) {
|
|
40
|
-
fs.fdatasyncSync(this._fd);
|
|
41
|
-
fs.closeSync(this._fd);
|
|
42
|
-
this._fd = null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Log a write operation.
|
|
48
|
-
*/
|
|
49
|
-
logWrite(record) {
|
|
50
|
-
this._append({
|
|
51
|
-
_type: 'write',
|
|
52
|
-
id: record.id,
|
|
53
|
-
hash: record.h,
|
|
54
|
-
len: record.content.length,
|
|
55
|
-
ts: Date.now(),
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Log a recall operation.
|
|
61
|
-
*/
|
|
62
|
-
logRecall(query, result, latencyMs) {
|
|
63
|
-
this._append({
|
|
64
|
-
_type: 'recall',
|
|
65
|
-
queryHash: this._hash(query),
|
|
66
|
-
queryLen: query.length,
|
|
67
|
-
resultId: result ? result.id : null,
|
|
68
|
-
resultHash: result ? result.h : null,
|
|
69
|
-
score: result ? result._score : null,
|
|
70
|
-
latencyMs: Math.round(latencyMs * 100) / 100,
|
|
71
|
-
ts: Date.now(),
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Log a clear operation.
|
|
77
|
-
*/
|
|
78
|
-
logClear(count) {
|
|
79
|
-
this._append({
|
|
80
|
-
_type: 'clear',
|
|
81
|
-
count,
|
|
82
|
-
ts: Date.now(),
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Log a consistency mode change.
|
|
88
|
-
*/
|
|
89
|
-
logModeChange(from, to) {
|
|
90
|
-
this._append({
|
|
91
|
-
_type: 'mode_change',
|
|
92
|
-
from,
|
|
93
|
-
to,
|
|
94
|
-
ts: Date.now(),
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Return all audit entries (excluding header).
|
|
100
|
-
*/
|
|
101
|
-
all() {
|
|
102
|
-
return this._entries.map(e => ({ ...e }));
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Return entries filtered by type.
|
|
107
|
-
*/
|
|
108
|
-
filter(type) {
|
|
109
|
-
return this._entries.filter(e => e._type === type).map(e => ({ ...e }));
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Return a compact summary.
|
|
114
|
-
*/
|
|
115
|
-
summary() {
|
|
116
|
-
const writes = this._entries.filter(e => e._type === 'write').length;
|
|
117
|
-
const recalls = this._entries.filter(e => e._type === 'recall').length;
|
|
118
|
-
const clears = this._entries.filter(e => e._type === 'clear').length;
|
|
119
|
-
const first = this._entries[0];
|
|
120
|
-
const last = this._entries[this._entries.length - 1];
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
totalEntries: this._entries.length,
|
|
124
|
-
writes,
|
|
125
|
-
recalls,
|
|
126
|
-
clears,
|
|
127
|
-
firstEventAt: first ? first.ts : null,
|
|
128
|
-
lastEventAt: last ? last.ts : null,
|
|
129
|
-
auditPath: this.auditPath,
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Export as NDJSON string (for compliance hand-off).
|
|
135
|
-
*/
|
|
136
|
-
export() {
|
|
137
|
-
return this._entries.map(e => JSON.stringify(e)).join('\n');
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// ── Private ────────────────────────────────────
|
|
141
|
-
|
|
142
|
-
_append(entry) {
|
|
143
|
-
const line = JSON.stringify(entry) + '\n';
|
|
144
|
-
fs.writeSync(this._fd, line, null, 'utf8');
|
|
145
|
-
fs.fdatasyncSync(this._fd);
|
|
146
|
-
this._entries.push(entry);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
_load() {
|
|
150
|
-
const raw = fs.readFileSync(this.auditPath, 'utf8');
|
|
151
|
-
const lines = raw.split('\n').filter(l => l.trim());
|
|
152
|
-
|
|
153
|
-
for (const line of lines) {
|
|
154
|
-
try {
|
|
155
|
-
const entry = JSON.parse(line);
|
|
156
|
-
if (entry._type && entry._type !== 'audit_header') {
|
|
157
|
-
this._entries.push(entry);
|
|
158
|
-
}
|
|
159
|
-
} catch (_) { /* skip corrupt lines */ }
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
_hash(s) {
|
|
164
|
-
let h = 0x811c9dc5;
|
|
165
|
-
for (let i = 0; i < s.length; i++) {
|
|
166
|
-
h ^= s.charCodeAt(i);
|
|
167
|
-
h = Math.imul(h, 0x01000193);
|
|
168
|
-
}
|
|
169
|
-
return '0x' + (h >>> 0).toString(16).padStart(8, '0');
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
module.exports = { MumpixAudit };
|
|
1
|
+
"use strict";const t=require("fs"),e=require("path");module.exports={MumpixAudit:class{constructor(t){this.auditPath=e.resolve(t)+".audit",this._entries=[],this._fd=null}open(){if(t.existsSync(this.auditPath))this._load();else{const e={_type:"audit_header",version:1,created:Date.now()};t.writeFileSync(this.auditPath,JSON.stringify(e)+"\n","utf8")}return this._fd=t.openSync(this.auditPath,"a"),this}close(){null!==this._fd&&(t.fdatasyncSync(this._fd),t.closeSync(this._fd),this._fd=null)}logWrite(t){this._append({_type:"write",id:t.id,hash:t.h,len:t.content.length,ts:Date.now()})}logRecall(t,e,s){this._append({_type:"recall",queryHash:this._hash(t),queryLen:t.length,resultId:e?e.id:null,resultHash:e?e.h:null,score:e?e._score:null,latencyMs:Math.round(100*s)/100,ts:Date.now()})}logClear(t){this._append({_type:"clear",count:t,ts:Date.now()})}logModeChange(t,e){this._append({_type:"mode_change",from:t,to:e,ts:Date.now()})}all(){return this._entries.map(t=>({...t}))}filter(t){return this._entries.filter(e=>e._type===t).map(t=>({...t}))}summary(){const t=this._entries.filter(t=>"write"===t._type).length,e=this._entries.filter(t=>"recall"===t._type).length,s=this._entries.filter(t=>"clear"===t._type).length,i=this._entries[0],n=this._entries[this._entries.length-1];return{totalEntries:this._entries.length,writes:t,recalls:e,clears:s,firstEventAt:i?i.ts:null,lastEventAt:n?n.ts:null,auditPath:this.auditPath}}export(){return this._entries.map(t=>JSON.stringify(t)).join("\n")}_append(e){const s=JSON.stringify(e)+"\n";t.writeSync(this._fd,s,null,"utf8"),t.fdatasyncSync(this._fd),this._entries.push(e)}_load(){const e=t.readFileSync(this.auditPath,"utf8").split("\n").filter(t=>t.trim());for(const t of e)try{const e=JSON.parse(t);e._type&&"audit_header"!==e._type&&this._entries.push(e)}catch(t){}}_hash(t){let e=2166136261;for(let s=0;s<t.length;s++)e^=t.charCodeAt(s),e=Math.imul(e,16777619);return"0x"+(e>>>0).toString(16).padStart(8,"0")}}};
|
package/src/core/auth.js
CHANGED
|
@@ -1,232 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const os = require('os');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const https = require('https');
|
|
7
|
-
|
|
8
|
-
function configDir() {
|
|
9
|
-
const custom = String(process.env.MUMPIX_CONFIG_DIR || '').trim();
|
|
10
|
-
if (custom) return custom;
|
|
11
|
-
if (process.platform === 'win32') {
|
|
12
|
-
return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'mumpix');
|
|
13
|
-
}
|
|
14
|
-
return path.join(os.homedir(), '.config', 'mumpix');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function authStatePath() {
|
|
18
|
-
return path.join(configDir(), 'auth.json');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function sleep(ms) {
|
|
22
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function parseJsonSafe(raw) {
|
|
26
|
-
try {
|
|
27
|
-
return JSON.parse(raw);
|
|
28
|
-
} catch {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function requestJson(method, targetUrl, body = null, headers = {}, timeoutMs = 10000) {
|
|
34
|
-
return new Promise((resolve, reject) => {
|
|
35
|
-
const url = new URL(targetUrl);
|
|
36
|
-
const payload = body == null ? null : JSON.stringify(body);
|
|
37
|
-
const req = https.request({
|
|
38
|
-
protocol: url.protocol,
|
|
39
|
-
hostname: url.hostname,
|
|
40
|
-
port: url.port || 443,
|
|
41
|
-
path: `${url.pathname}${url.search || ''}`,
|
|
42
|
-
method,
|
|
43
|
-
timeout: timeoutMs,
|
|
44
|
-
headers: {
|
|
45
|
-
'content-type': 'application/json',
|
|
46
|
-
...(payload ? { 'content-length': Buffer.byteLength(payload) } : {}),
|
|
47
|
-
...headers,
|
|
48
|
-
}
|
|
49
|
-
}, (res) => {
|
|
50
|
-
let data = '';
|
|
51
|
-
res.on('data', (chunk) => { data += String(chunk); });
|
|
52
|
-
res.on('end', () => {
|
|
53
|
-
const json = parseJsonSafe(data || '{}') || {};
|
|
54
|
-
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
55
|
-
resolve(json);
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
const msg = json.error || json.message || `HTTP ${res.statusCode}`;
|
|
59
|
-
const err = new Error(msg);
|
|
60
|
-
err.statusCode = Number(res.statusCode || 0);
|
|
61
|
-
err.response = json;
|
|
62
|
-
err.url = targetUrl;
|
|
63
|
-
reject(err);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
req.on('error', reject);
|
|
67
|
-
req.on('timeout', () => req.destroy(new Error('auth request timeout')));
|
|
68
|
-
if (payload) req.write(payload);
|
|
69
|
-
req.end();
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function authBaseCandidates(inputBase) {
|
|
74
|
-
const raw = String(inputBase || process.env.MUMPIX_AUTH_BASE_URL || 'https://mumpixdb.com').replace(/\/+$/, '');
|
|
75
|
-
const out = [raw];
|
|
76
|
-
if (!raw.endsWith('/benchmark')) out.push(`${raw}/benchmark`);
|
|
77
|
-
return Array.from(new Set(out));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function decodeLicenseClaims(licenseKey) {
|
|
81
|
-
if (!licenseKey || typeof licenseKey !== 'string') return null;
|
|
82
|
-
const [payloadB64] = licenseKey.split('.');
|
|
83
|
-
if (!payloadB64) return null;
|
|
84
|
-
try {
|
|
85
|
-
return JSON.parse(Buffer.from(payloadB64, 'base64').toString('utf8'));
|
|
86
|
-
} catch {
|
|
87
|
-
return null;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function loadAuthState() {
|
|
92
|
-
try {
|
|
93
|
-
const raw = fs.readFileSync(authStatePath(), 'utf8');
|
|
94
|
-
const parsed = parseJsonSafe(raw);
|
|
95
|
-
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
96
|
-
} catch {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function saveAuthState(state) {
|
|
102
|
-
const p = authStatePath();
|
|
103
|
-
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
104
|
-
const tmp = `${p}.tmp`;
|
|
105
|
-
fs.writeFileSync(tmp, JSON.stringify(state, null, 2), 'utf8');
|
|
106
|
-
fs.renameSync(tmp, p);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function clearAuthState() {
|
|
110
|
-
try {
|
|
111
|
-
fs.unlinkSync(authStatePath());
|
|
112
|
-
} catch (_) {}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function getStoredLicenseKey() {
|
|
116
|
-
const state = loadAuthState();
|
|
117
|
-
if (!state || !state.license || typeof state.license.key !== 'string') return null;
|
|
118
|
-
return state.license.key;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function upsertStoredLicense(licenseKey, account = {}) {
|
|
122
|
-
const claims = decodeLicenseClaims(licenseKey) || {};
|
|
123
|
-
const state = {
|
|
124
|
-
version: 1,
|
|
125
|
-
provider: 'mumpix',
|
|
126
|
-
updatedAt: Date.now(),
|
|
127
|
-
account: {
|
|
128
|
-
id: account.id || claims.id || null,
|
|
129
|
-
email: account.email || null,
|
|
130
|
-
tier: account.tier || claims.tier || null,
|
|
131
|
-
},
|
|
132
|
-
license: {
|
|
133
|
-
key: licenseKey,
|
|
134
|
-
iat: claims.iat || null,
|
|
135
|
-
exp: claims.exp || null,
|
|
136
|
-
tier: claims.tier || account.tier || null,
|
|
137
|
-
fid: claims.fid || null,
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
saveAuthState(state);
|
|
141
|
-
return state;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async function exchangeTokenForLicense(authToken, opts = {}) {
|
|
145
|
-
const endpoint = String(opts.exchangePath || process.env.MUMPIX_AUTH_TOKEN_EXCHANGE_PATH || '/api/mumpix/auth/token/exchange');
|
|
146
|
-
const timeoutMs = Number(opts.timeoutMs || process.env.MUMPIX_AUTH_TIMEOUT_MS || 10000);
|
|
147
|
-
let lastErr = null;
|
|
148
|
-
for (const base of authBaseCandidates(opts.baseUrl)) {
|
|
149
|
-
try {
|
|
150
|
-
const body = await requestJson('POST', `${base}${endpoint}`, {}, {
|
|
151
|
-
authorization: `Bearer ${authToken}`
|
|
152
|
-
}, timeoutMs);
|
|
153
|
-
const licenseKey = body.licenseKey || body.license || null;
|
|
154
|
-
if (!licenseKey) throw new Error('No license key returned from auth exchange');
|
|
155
|
-
return upsertStoredLicense(licenseKey, body.account || {});
|
|
156
|
-
} catch (err) {
|
|
157
|
-
lastErr = err;
|
|
158
|
-
if (Number(err && err.statusCode) === 404) continue;
|
|
159
|
-
throw err;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
throw lastErr || new Error('Auth token exchange failed');
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async function loginWithDeviceFlow(opts = {}) {
|
|
166
|
-
const startPath = String(opts.startPath || process.env.MUMPIX_AUTH_DEVICE_START_PATH || '/api/mumpix/auth/device/start');
|
|
167
|
-
const pollPath = String(opts.pollPath || process.env.MUMPIX_AUTH_DEVICE_POLL_PATH || '/api/mumpix/auth/device/poll');
|
|
168
|
-
const timeoutMs = Number(opts.timeoutMs || process.env.MUMPIX_AUTH_TIMEOUT_MS || 10000);
|
|
169
|
-
|
|
170
|
-
let start = null;
|
|
171
|
-
let base = '';
|
|
172
|
-
let lastErr = null;
|
|
173
|
-
for (const candidate of authBaseCandidates(opts.baseUrl)) {
|
|
174
|
-
try {
|
|
175
|
-
start = await requestJson('POST', `${candidate}${startPath}`, {
|
|
176
|
-
client: 'mumpix-cli'
|
|
177
|
-
}, {}, timeoutMs);
|
|
178
|
-
base = candidate;
|
|
179
|
-
break;
|
|
180
|
-
} catch (err) {
|
|
181
|
-
lastErr = err;
|
|
182
|
-
if (Number(err && err.statusCode) === 404) continue;
|
|
183
|
-
throw err;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
if (!start) throw lastErr || new Error('Device auth start failed');
|
|
187
|
-
|
|
188
|
-
const deviceCode = start.deviceCode || start.device_code;
|
|
189
|
-
const userCode = start.userCode || start.user_code;
|
|
190
|
-
const verifyUrl = start.verificationUrl || start.verification_url || `${base}/auth/device`;
|
|
191
|
-
const intervalSec = Number(start.intervalSec || start.interval || 5);
|
|
192
|
-
const expiresInSec = Number(start.expiresInSec || start.expires_in || 600);
|
|
193
|
-
if (!deviceCode || !userCode) {
|
|
194
|
-
throw new Error('Invalid device auth start response');
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const startedAt = Date.now();
|
|
198
|
-
const expiresAt = startedAt + (expiresInSec * 1000);
|
|
199
|
-
if (typeof opts.onPrompt === 'function') {
|
|
200
|
-
opts.onPrompt({ userCode, verifyUrl, expiresInSec, intervalSec });
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
while (Date.now() < expiresAt) {
|
|
204
|
-
await sleep(intervalSec * 1000);
|
|
205
|
-
const poll = await requestJson('POST', `${base}${pollPath}`, { deviceCode }, {}, timeoutMs);
|
|
206
|
-
const status = String(poll.status || '').toLowerCase();
|
|
207
|
-
if (status === 'pending' || status === 'authorization_pending') continue;
|
|
208
|
-
if (status === 'denied' || status === 'access_denied') {
|
|
209
|
-
throw new Error('Login denied');
|
|
210
|
-
}
|
|
211
|
-
const licenseKey = poll.licenseKey || poll.license || null;
|
|
212
|
-
if (!licenseKey) {
|
|
213
|
-
throw new Error('Approved login did not return a license key');
|
|
214
|
-
}
|
|
215
|
-
return upsertStoredLicense(licenseKey, poll.account || {});
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
throw new Error('Device login timed out');
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
module.exports = {
|
|
222
|
-
configDir,
|
|
223
|
-
authStatePath,
|
|
224
|
-
loadAuthState,
|
|
225
|
-
saveAuthState,
|
|
226
|
-
clearAuthState,
|
|
227
|
-
getStoredLicenseKey,
|
|
228
|
-
upsertStoredLicense,
|
|
229
|
-
exchangeTokenForLicense,
|
|
230
|
-
loginWithDeviceFlow,
|
|
231
|
-
decodeLicenseClaims,
|
|
232
|
-
};
|
|
1
|
+
"use strict";const e=require("fs"),t=require("os"),n=require("path"),r=require("https");function o(){const e=String(process.env.MUMPIX_CONFIG_DIR||"").trim();return e||("win32"===process.platform?n.join(process.env.APPDATA||n.join(t.homedir(),"AppData","Roaming"),"mumpix"):n.join(t.homedir(),".config","mumpix"))}function i(){return n.join(o(),"auth.json")}function u(e){return new Promise(t=>setTimeout(t,e))}function c(e){try{return JSON.parse(e)}catch{return null}}function s(e,t,n=null,o={},i=1e4){return new Promise((u,s)=>{const a=new URL(t),l=null==n?null:JSON.stringify(n),d=r.request({protocol:a.protocol,hostname:a.hostname,port:a.port||443,path:`${a.pathname}${a.search||""}`,method:e,timeout:i,headers:{"content-type":"application/json",...l?{"content-length":Buffer.byteLength(l)}:{},...o}},e=>{let n="";e.on("data",e=>{n+=String(e)}),e.on("end",()=>{const r=c(n||"{}")||{};if(e.statusCode>=200&&e.statusCode<300)return void u(r);const o=r.error||r.message||`HTTP ${e.statusCode}`,i=new Error(o);i.statusCode=Number(e.statusCode||0),i.response=r,i.url=t,s(i)})});d.on("error",s),d.on("timeout",()=>d.destroy(new Error("auth request timeout"))),l&&d.write(l),d.end()})}function a(e){const t=String(e||process.env.MUMPIX_AUTH_BASE_URL||"https://mumpixdb.com").replace(/\/+$/,""),n=[t];return t.endsWith("/benchmark")||n.push(`${t}/benchmark`),Array.from(new Set(n))}function l(e){if(!e||"string"!=typeof e)return null;const[t]=e.split(".");if(!t)return null;try{return JSON.parse(Buffer.from(t,"base64").toString("utf8"))}catch{return null}}function d(){try{const t=c(e.readFileSync(i(),"utf8"));return t&&"object"==typeof t?t:null}catch{return null}}function p(t){const r=i();e.mkdirSync(n.dirname(r),{recursive:!0});const o=`${r}.tmp`;e.writeFileSync(o,JSON.stringify(t,null,2),"utf8"),e.renameSync(o,r)}function h(e,t={}){const n=l(e)||{},r={version:1,provider:"mumpix",updatedAt:Date.now(),account:{id:t.id||n.id||null,email:t.email||null,tier:t.tier||n.tier||null},license:{key:e,iat:n.iat||null,exp:n.exp||null,tier:n.tier||t.tier||null,fid:n.fid||null}};return p(r),r}module.exports={configDir:o,authStatePath:i,loadAuthState:d,saveAuthState:p,clearAuthState:function(){try{e.unlinkSync(i())}catch(e){}},getStoredLicenseKey:function(){const e=d();return e&&e.license&&"string"==typeof e.license.key?e.license.key:null},upsertStoredLicense:h,exchangeTokenForLicense:async function(e,t={}){const n=String(t.exchangePath||process.env.MUMPIX_AUTH_TOKEN_EXCHANGE_PATH||"/api/mumpix/auth/token/exchange"),r=Number(t.timeoutMs||process.env.MUMPIX_AUTH_TIMEOUT_MS||1e4);let o=null;for(const i of a(t.baseUrl))try{const t=await s("POST",`${i}${n}`,{},{authorization:`Bearer ${e}`},r),o=t.licenseKey||t.license||null;if(!o)throw new Error("No license key returned from auth exchange");return h(o,t.account||{})}catch(e){if(o=e,404===Number(e&&e.statusCode))continue;throw e}throw o||new Error("Auth token exchange failed")},loginWithDeviceFlow:async function(e={}){const t=String(e.startPath||process.env.MUMPIX_AUTH_DEVICE_START_PATH||"/api/mumpix/auth/device/start"),n=String(e.pollPath||process.env.MUMPIX_AUTH_DEVICE_POLL_PATH||"/api/mumpix/auth/device/poll"),r=Number(e.timeoutMs||process.env.MUMPIX_AUTH_TIMEOUT_MS||1e4);let o=null,i="",c=null;for(const n of a(e.baseUrl))try{o=await s("POST",`${n}${t}`,{client:"mumpix-cli"},{},r),i=n;break}catch(e){if(c=e,404===Number(e&&e.statusCode))continue;throw e}if(!o)throw c||new Error("Device auth start failed");const l=o.deviceCode||o.device_code,d=o.userCode||o.user_code,p=o.verificationUrl||o.verification_url||`${i}/auth/device`,f=Number(o.intervalSec||o.interval||5),m=Number(o.expiresInSec||o.expires_in||600);if(!l||!d)throw new Error("Invalid device auth start response");const S=Date.now()+1e3*m;for("function"==typeof e.onPrompt&&e.onPrompt({userCode:d,verifyUrl:p,expiresInSec:m,intervalSec:f});Date.now()<S;){await u(1e3*f);const e=await s("POST",`${i}${n}`,{deviceCode:l},{},r),t=String(e.status||"").toLowerCase();if("pending"===t||"authorization_pending"===t)continue;if("denied"===t||"access_denied"===t)throw new Error("Login denied");const o=e.licenseKey||e.license||null;if(!o)throw new Error("Approved login did not return a license key");return h(o,e.account||{})}throw new Error("Device login timed out")},decodeLicenseClaims:l};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
|
|
5
|
+
class InvertedIndex {
|
|
6
|
+
constructor(tokenize, sidecarPath) {
|
|
7
|
+
this._tokenize = tokenize;
|
|
8
|
+
this._sidecarPath = sidecarPath;
|
|
9
|
+
this._postings = new Map();
|
|
10
|
+
this._df = new Map();
|
|
11
|
+
this._byId = new Map();
|
|
12
|
+
this._highWaterSeq = 0;
|
|
13
|
+
this._docCount = 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
add(record) {
|
|
17
|
+
if (!record || !Number.isInteger(record.id) || this._byId.has(record.id)) return;
|
|
18
|
+
|
|
19
|
+
this._byId.set(record.id, record);
|
|
20
|
+
this._docCount++;
|
|
21
|
+
if (record.id > this._highWaterSeq) this._highWaterSeq = record.id;
|
|
22
|
+
|
|
23
|
+
const terms = new Set(this._tokenize(record.content || ""));
|
|
24
|
+
for (const term of terms) {
|
|
25
|
+
let set = this._postings.get(term);
|
|
26
|
+
if (!set) {
|
|
27
|
+
set = new Set();
|
|
28
|
+
this._postings.set(term, set);
|
|
29
|
+
}
|
|
30
|
+
set.add(record.id);
|
|
31
|
+
this._df.set(term, (this._df.get(term) || 0) + 1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
hydrate(records) {
|
|
36
|
+
for (const record of records) {
|
|
37
|
+
if (record && Number.isInteger(record.id)) {
|
|
38
|
+
this._byId.set(record.id, record);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
candidates(query, cap = 400) {
|
|
44
|
+
const terms = new Set(this._tokenize(query || ""));
|
|
45
|
+
if (!terms.size) return [];
|
|
46
|
+
|
|
47
|
+
const overlap = new Map();
|
|
48
|
+
for (const term of terms) {
|
|
49
|
+
const set = this._postings.get(term);
|
|
50
|
+
if (!set) continue;
|
|
51
|
+
for (const id of set) {
|
|
52
|
+
overlap.set(id, (overlap.get(id) || 0) + 1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!overlap.size) return [];
|
|
57
|
+
|
|
58
|
+
const ranked = [...overlap.entries()].sort((a, b) => b[1] - a[1]);
|
|
59
|
+
const out = [];
|
|
60
|
+
for (const [id] of ranked) {
|
|
61
|
+
const record = this._byId.get(id);
|
|
62
|
+
if (record) out.push(record);
|
|
63
|
+
if (out.length >= cap) break;
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get highWaterSeq() {
|
|
69
|
+
return this._highWaterSeq;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
stats() {
|
|
73
|
+
return {
|
|
74
|
+
terms: this._postings.size,
|
|
75
|
+
docCount: this._docCount,
|
|
76
|
+
highWaterSeq: this._highWaterSeq,
|
|
77
|
+
sidecarPath: this._sidecarPath,
|
|
78
|
+
sizeBytes: fs.existsSync(this._sidecarPath)
|
|
79
|
+
? fs.statSync(this._sidecarPath).size
|
|
80
|
+
: 0,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
persist() {
|
|
85
|
+
const postings = {};
|
|
86
|
+
for (const [term, set] of this._postings) {
|
|
87
|
+
postings[term] = [...set].sort((a, b) => a - b);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const df = {};
|
|
91
|
+
for (const [term, count] of this._df) {
|
|
92
|
+
df[term] = count;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const payload = {
|
|
96
|
+
v: 1,
|
|
97
|
+
highWaterSeq: this._highWaterSeq,
|
|
98
|
+
docCount: this._docCount,
|
|
99
|
+
postings,
|
|
100
|
+
df,
|
|
101
|
+
};
|
|
102
|
+
const tmp = `${this._sidecarPath}.tmp.${process.pid}`;
|
|
103
|
+
fs.writeFileSync(tmp, JSON.stringify(payload), "utf8");
|
|
104
|
+
fs.renameSync(tmp, this._sidecarPath);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
loadSidecar() {
|
|
108
|
+
try {
|
|
109
|
+
const raw = fs.readFileSync(this._sidecarPath, "utf8");
|
|
110
|
+
const data = JSON.parse(raw);
|
|
111
|
+
if (!data || data.v !== 1 || typeof data.postings !== "object") {
|
|
112
|
+
this.reset();
|
|
113
|
+
return 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this._postings.clear();
|
|
117
|
+
this._df.clear();
|
|
118
|
+
this._byId.clear();
|
|
119
|
+
|
|
120
|
+
for (const [term, ids] of Object.entries(data.postings)) {
|
|
121
|
+
this._postings.set(term, new Set(Array.isArray(ids) ? ids : []));
|
|
122
|
+
}
|
|
123
|
+
for (const [term, count] of Object.entries(data.df || {})) {
|
|
124
|
+
this._df.set(term, count);
|
|
125
|
+
}
|
|
126
|
+
this._highWaterSeq = Number(data.highWaterSeq) || 0;
|
|
127
|
+
this._docCount = Number(data.docCount) || 0;
|
|
128
|
+
return this._highWaterSeq;
|
|
129
|
+
} catch (_) {
|
|
130
|
+
this.reset();
|
|
131
|
+
return 0;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
reset() {
|
|
136
|
+
this._postings.clear();
|
|
137
|
+
this._df.clear();
|
|
138
|
+
this._byId.clear();
|
|
139
|
+
this._highWaterSeq = 0;
|
|
140
|
+
this._docCount = 0;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = { InvertedIndex };
|