spect8-mcp 0.1.4 → 0.2.1
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.
|
@@ -8,18 +8,18 @@ export interface QueuedRow {
|
|
|
8
8
|
attempts: number;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
|
-
* Durable,
|
|
12
|
-
* unreachable so no captured event is lost
|
|
13
|
-
*
|
|
14
|
-
* Rows are keyed by auto-incremented id to preserve ordering; `dequeue()`
|
|
15
|
-
* returns the oldest unsent rows and callers call `ack()` after a successful
|
|
16
|
-
* POST. On retry storms we also track `attempts` so pathological rows can be
|
|
17
|
-
* drained manually by an operator.
|
|
11
|
+
* Durable, pure-JS FIFO buffer. Used when the ingest endpoint is
|
|
12
|
+
* unreachable so no captured event is lost. Backed by a simple JSON file
|
|
13
|
+
* to avoid native dependencies like SQLite which break on Windows/Node 24.
|
|
18
14
|
*/
|
|
19
15
|
export declare class OfflineQueue {
|
|
16
|
+
private readonly dbPath;
|
|
20
17
|
private readonly logger;
|
|
21
|
-
private
|
|
18
|
+
private rows;
|
|
19
|
+
private nextId;
|
|
22
20
|
constructor(dbPath: string, logger: Logger);
|
|
21
|
+
private load;
|
|
22
|
+
private save;
|
|
23
23
|
enqueueMany(kind: QueueKind, payloads: string[]): void;
|
|
24
24
|
dequeue(kind: QueueKind, limit: number): QueuedRow[];
|
|
25
25
|
ack(ids: number[]): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"offline_queue.d.ts","sourceRoot":"","sources":["../../src/transport/offline_queue.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"offline_queue.d.ts","sourceRoot":"","sources":["../../src/transport/offline_queue.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE3C,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,aAAa,CAAC;AAEhD,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,qBAAa,YAAY;IAIX,OAAO,CAAC,QAAQ,CAAC,MAAM;IAAU,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHpE,OAAO,CAAC,IAAI,CAAmB;IAC/B,OAAO,CAAC,MAAM,CAAa;gBAEE,MAAM,EAAE,MAAM,EAAmB,MAAM,EAAE,MAAM;IAI5E,OAAO,CAAC,IAAI;IAqBZ,OAAO,CAAC,IAAI;IAYZ,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI;IAetD,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE;IAMpD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI;IAOxB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI;IAWzB,IAAI,IAAI,MAAM;IAId,KAAK,IAAI,IAAI;CAGd"}
|
|
@@ -1,83 +1,95 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
-
import { mkdirSync, existsSync } from "node:fs";
|
|
4
3
|
/**
|
|
5
|
-
* Durable,
|
|
6
|
-
* unreachable so no captured event is lost
|
|
7
|
-
*
|
|
8
|
-
* Rows are keyed by auto-incremented id to preserve ordering; `dequeue()`
|
|
9
|
-
* returns the oldest unsent rows and callers call `ack()` after a successful
|
|
10
|
-
* POST. On retry storms we also track `attempts` so pathological rows can be
|
|
11
|
-
* drained manually by an operator.
|
|
4
|
+
* Durable, pure-JS FIFO buffer. Used when the ingest endpoint is
|
|
5
|
+
* unreachable so no captured event is lost. Backed by a simple JSON file
|
|
6
|
+
* to avoid native dependencies like SQLite which break on Windows/Node 24.
|
|
12
7
|
*/
|
|
13
8
|
export class OfflineQueue {
|
|
9
|
+
dbPath;
|
|
14
10
|
logger;
|
|
15
|
-
|
|
11
|
+
rows = [];
|
|
12
|
+
nextId = 1;
|
|
16
13
|
constructor(dbPath, logger) {
|
|
14
|
+
this.dbPath = dbPath;
|
|
17
15
|
this.logger = logger;
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
this.load();
|
|
17
|
+
}
|
|
18
|
+
load() {
|
|
19
|
+
if (!existsSync(this.dbPath)) {
|
|
20
|
+
if (!existsSync(dirname(this.dbPath))) {
|
|
21
|
+
mkdirSync(dirname(this.dbPath), { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
this.save();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const raw = readFileSync(this.dbPath, "utf8");
|
|
28
|
+
const data = JSON.parse(raw);
|
|
29
|
+
this.rows = data.rows || [];
|
|
30
|
+
this.nextId = data.nextId || 1;
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
this.logger.error("Failed to load offline queue", err.message);
|
|
34
|
+
this.rows = [];
|
|
35
|
+
this.nextId = 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
save() {
|
|
39
|
+
try {
|
|
40
|
+
const data = {
|
|
41
|
+
rows: this.rows,
|
|
42
|
+
nextId: this.nextId
|
|
43
|
+
};
|
|
44
|
+
writeFileSync(this.dbPath, JSON.stringify(data, null, 2), "utf8");
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
this.logger.error("Failed to save offline queue", err.message);
|
|
20
48
|
}
|
|
21
|
-
this.db = new Database(dbPath);
|
|
22
|
-
this.db.pragma("journal_mode = WAL");
|
|
23
|
-
this.db.pragma("synchronous = NORMAL");
|
|
24
|
-
this.db.exec(`
|
|
25
|
-
CREATE TABLE IF NOT EXISTS spect8_queue (
|
|
26
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
27
|
-
kind TEXT NOT NULL,
|
|
28
|
-
payload TEXT NOT NULL,
|
|
29
|
-
created_at_ms INTEGER NOT NULL,
|
|
30
|
-
attempts INTEGER NOT NULL DEFAULT 0
|
|
31
|
-
);
|
|
32
|
-
CREATE INDEX IF NOT EXISTS ix_spect8_queue_kind ON spect8_queue(kind, id);
|
|
33
|
-
`);
|
|
34
49
|
}
|
|
35
50
|
enqueueMany(kind, payloads) {
|
|
36
51
|
if (payloads.length === 0)
|
|
37
52
|
return;
|
|
38
|
-
const stmt = this.db.prepare("INSERT INTO spect8_queue(kind, payload, created_at_ms) VALUES (?, ?, ?)");
|
|
39
53
|
const now = Date.now();
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
this.logger.error("offline queue enqueue failed", err.message);
|
|
54
|
+
for (const p of payloads) {
|
|
55
|
+
this.rows.push({
|
|
56
|
+
id: this.nextId++,
|
|
57
|
+
kind,
|
|
58
|
+
payload: p,
|
|
59
|
+
created_at_ms: now,
|
|
60
|
+
attempts: 0
|
|
61
|
+
});
|
|
49
62
|
}
|
|
63
|
+
this.save();
|
|
50
64
|
}
|
|
51
65
|
dequeue(kind, limit) {
|
|
52
|
-
|
|
53
|
-
.
|
|
54
|
-
.
|
|
55
|
-
return rows;
|
|
66
|
+
return this.rows
|
|
67
|
+
.filter(r => r.kind === kind)
|
|
68
|
+
.slice(0, limit);
|
|
56
69
|
}
|
|
57
70
|
ack(ids) {
|
|
58
71
|
if (ids.length === 0)
|
|
59
72
|
return;
|
|
60
|
-
const
|
|
61
|
-
this.
|
|
62
|
-
|
|
63
|
-
.run(...ids);
|
|
73
|
+
const set = new Set(ids);
|
|
74
|
+
this.rows = this.rows.filter(r => !set.has(r.id));
|
|
75
|
+
this.save();
|
|
64
76
|
}
|
|
65
77
|
bump(ids) {
|
|
66
78
|
if (ids.length === 0)
|
|
67
79
|
return;
|
|
68
|
-
const
|
|
69
|
-
this.
|
|
70
|
-
.
|
|
71
|
-
|
|
80
|
+
const set = new Set(ids);
|
|
81
|
+
for (const r of this.rows) {
|
|
82
|
+
if (set.has(r.id)) {
|
|
83
|
+
r.attempts++;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
this.save();
|
|
72
87
|
}
|
|
73
88
|
size() {
|
|
74
|
-
|
|
75
|
-
.prepare("SELECT COUNT(*) AS n FROM spect8_queue")
|
|
76
|
-
.get();
|
|
77
|
-
return row.n;
|
|
89
|
+
return this.rows.length;
|
|
78
90
|
}
|
|
79
91
|
close() {
|
|
80
|
-
this.
|
|
92
|
+
this.save();
|
|
81
93
|
}
|
|
82
94
|
}
|
|
83
95
|
//# sourceMappingURL=offline_queue.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"offline_queue.js","sourceRoot":"","sources":["../../src/transport/offline_queue.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"offline_queue.js","sourceRoot":"","sources":["../../src/transport/offline_queue.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spect8-mcp",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Spect8 local MCP server: captures tool events + token usage from Cursor and Claude Code, posts HMAC-signed batches to the Spect8 ingest API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server.js",
|
|
@@ -12,9 +12,11 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"dist",
|
|
14
14
|
"examples",
|
|
15
|
+
"scripts",
|
|
15
16
|
"README.md"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
19
|
+
"preinstall": "node scripts/preinstall.js",
|
|
18
20
|
"build": "tsc -p tsconfig.json",
|
|
19
21
|
"dev": "tsc -w -p tsconfig.json",
|
|
20
22
|
"start": "node dist/server.js",
|
|
@@ -25,13 +27,11 @@
|
|
|
25
27
|
},
|
|
26
28
|
"dependencies": {
|
|
27
29
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
28
|
-
"better-sqlite3": "^11.5.0",
|
|
29
30
|
"chokidar": "^3.6.0",
|
|
30
31
|
"undici": "^6.20.0",
|
|
31
32
|
"zod": "^3.23.8"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
|
-
"@types/better-sqlite3": "^7.6.11",
|
|
35
35
|
"@types/node": "^22.0.0",
|
|
36
36
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
|
37
37
|
"@typescript-eslint/parser": "^7.18.0",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pre-install check for Spect8 MCP.
|
|
5
|
+
* Ensures the environment is ready and provides clear, human-readable
|
|
6
|
+
* guidance if any common issues are detected.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const nodeVersion = process.versions.node;
|
|
10
|
+
const majorVersion = parseInt(nodeVersion.split('.')[0], 10);
|
|
11
|
+
const isWindows = process.platform === 'win32';
|
|
12
|
+
|
|
13
|
+
console.log('--- Spect8 Environment Check ---');
|
|
14
|
+
|
|
15
|
+
if (majorVersion < 20) {
|
|
16
|
+
console.error('❌ Error: Node.js 20 or higher is required.');
|
|
17
|
+
console.error(` Your current version is: ${nodeVersion}`);
|
|
18
|
+
console.error(' Please upgrade at: https://nodejs.org');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (isWindows && majorVersion >= 24) {
|
|
23
|
+
console.warn('⚠️ Notice: You are using Node 24 on Windows.');
|
|
24
|
+
console.warn(' This version is very new. We have removed all native dependencies');
|
|
25
|
+
console.warn(' to ensure it works, but if you encounter issues, Node 22 LTS is recommended.');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log('✅ Environment compatible. Proceeding with installation...');
|
|
29
|
+
console.log('--------------------------------');
|