@otskit/mcp 0.1.3 → 0.1.5
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/README.md +79 -15
- package/dist/chunk-4ZDPWS7C.js +123 -0
- package/dist/{chunk-6IPLIM6I.js → chunk-55NLVWDQ.js} +12 -130
- package/dist/claude-QTEB3Z5V.js +54 -0
- package/dist/{cli-GQMURR5I.js → cli-ZD2OP45N.js} +8 -6
- package/dist/codex-P7EDO5GY.js +41 -0
- package/dist/index.js +38 -11
- package/dist/{server-NCO7JIUI.js → server-Q7XYIZRY.js} +22 -101
- package/dist/watch-7NPUWR6I.js +41 -0
- package/package.json +17 -10
- /package/dist/{scheduler-VVSCSTMC.js → scheduler-YF6WMSR4.js} +0 -0
package/README.md
CHANGED
|
@@ -4,33 +4,97 @@
|
|
|
4
4
|
|
|
5
5
|
# @otskit/mcp
|
|
6
6
|
|
|
7
|
-
OpenTimestamps MCP server — stamp, upgrade, and verify timestamps via AI agents
|
|
7
|
+
OpenTimestamps MCP server — stamp, upgrade, and verify Bitcoin timestamps via AI agents.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Exposes a set of tools to any MCP-compatible agent so it can timestamp documents, monitor confirmation status, and verify proofs against the Bitcoin blockchain — all from a conversation.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
10
12
|
|
|
11
13
|
```bash
|
|
12
|
-
|
|
13
|
-
pnpm build
|
|
14
|
+
npm install -g @otskit/mcp
|
|
14
15
|
```
|
|
15
16
|
|
|
16
|
-
##
|
|
17
|
+
## Agent setup
|
|
17
18
|
|
|
18
19
|
```bash
|
|
19
|
-
ots-mcp
|
|
20
|
+
ots-mcp setup claude # Claude Desktop
|
|
21
|
+
ots-mcp setup codex # Codex CLI
|
|
20
22
|
```
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
Each command writes the MCP entry into the agent's config file, makes a `.bak` backup if the file already exists, and skips if `ots-mcp` is already configured. Restart the agent afterwards to apply the changes.
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- `
|
|
26
|
+
## CLI commands
|
|
27
|
+
|
|
28
|
+
| Command | Description |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `ots-mcp serve` | Start the MCP server (stdio transport) |
|
|
31
|
+
| `ots-mcp stamp <sha256>` | Stamp a SHA-256 hash against Bitcoin calendars |
|
|
32
|
+
| `ots-mcp upgrade <id>` | Check if a pending stamp has been confirmed |
|
|
33
|
+
| `ots-mcp verify <id>` | Verify a stamp against Bitcoin |
|
|
34
|
+
| `ots-mcp list [status]` | List stamps (`pending` / `confirmed` / `failed`) |
|
|
35
|
+
| `ots-mcp watch [minutes]` | Poll pending stamps in real-time (default: 5 min) |
|
|
36
|
+
| `ots-mcp check-pending` | Run one upgrade pass over all pending stamps |
|
|
37
|
+
| `ots-mcp scheduler install\|remove\|status` | Manage OS-level scheduler for auto-upgrades |
|
|
38
|
+
| `ots-mcp backup [dest]` | Backup the SQLite database |
|
|
39
|
+
| `ots-mcp setup <claude\|codex>` | Configure MCP for an agent |
|
|
40
|
+
|
|
41
|
+
## MCP tools exposed to Claude
|
|
42
|
+
|
|
43
|
+
| Tool | Description |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `create_timestamp` | Stamp a SHA-256 hash against 4 public OTS calendars |
|
|
46
|
+
| `upgrade_timestamp` | Check if a pending stamp has been confirmed in Bitcoin |
|
|
47
|
+
| `verify_timestamp` | Verify a stamp — proves hash existed before a given Bitcoin block |
|
|
48
|
+
| `inspect_timestamp` | Inspect a stored proof file without network calls |
|
|
49
|
+
| `list_pending` | List stamps with status, retry count, and filters |
|
|
50
|
+
| `watch` | Open a terminal window monitoring pending stamps in real-time |
|
|
51
|
+
|
|
52
|
+
## Data directory
|
|
53
|
+
|
|
54
|
+
All data is stored in `~/.ots-mcp/`:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
~/.ots-mcp/
|
|
58
|
+
ots-mcp.db # SQLite database (stamps, proof files)
|
|
59
|
+
config.json # Optional config overrides
|
|
60
|
+
ots-mcp.log # Log file
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Configuration
|
|
64
|
+
|
|
65
|
+
Create `~/.ots-mcp/config.json` to override defaults:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"stamp_enabled": true,
|
|
70
|
+
"preserve_enabled": true,
|
|
71
|
+
"preserve_whitelist": ["/path/to/allowed/dir"],
|
|
72
|
+
"preserve_max_bytes": 104857600,
|
|
73
|
+
"preserve_max_files": 10000,
|
|
74
|
+
"scheduler_interval_minutes": 30,
|
|
75
|
+
"retry_max_attempts": 20,
|
|
76
|
+
"calendar_timeout_ms": 10000,
|
|
77
|
+
"esplora_url": "https://blockstream.info/api",
|
|
78
|
+
"calendars": [
|
|
79
|
+
"https://alice.btc.calendar.opentimestamps.org",
|
|
80
|
+
"https://bob.btc.calendar.opentimestamps.org",
|
|
81
|
+
"https://finney.calendar.eternitywall.com",
|
|
82
|
+
"https://btc.calendar.catallaxy.com"
|
|
83
|
+
]
|
|
84
|
+
}
|
|
85
|
+
```
|
|
29
86
|
|
|
30
87
|
## Development
|
|
31
88
|
|
|
32
89
|
```bash
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
90
|
+
npm run build # production build
|
|
91
|
+
npm run dev # watch mode
|
|
92
|
+
npm test # run tests
|
|
36
93
|
```
|
|
94
|
+
|
|
95
|
+
## Dependencies
|
|
96
|
+
|
|
97
|
+
- [`@otskit/core`](https://github.com/AlexAlves87/otskit-core) — OpenTimestamps core logic
|
|
98
|
+
- [`@otskit/client`](https://github.com/AlexAlves87/otskit-client) — OTS calendar client
|
|
99
|
+
- [`@modelcontextprotocol/sdk`](https://github.com/modelcontextprotocol/typescript-sdk) — MCP SDK
|
|
100
|
+
- `better-sqlite3` — local database
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import { readFileSync, existsSync, mkdirSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
function getDataDir() {
|
|
6
|
+
return process.env.OTS_MCP_DATA_DIR ?? join(homedir(), ".ots-mcp");
|
|
7
|
+
}
|
|
8
|
+
var DEFAULTS = {
|
|
9
|
+
stamp_enabled: true,
|
|
10
|
+
preserve_enabled: true,
|
|
11
|
+
preserve_whitelist: [],
|
|
12
|
+
preserve_max_bytes: 104857600,
|
|
13
|
+
preserve_max_files: 1e4,
|
|
14
|
+
scheduler_interval_minutes: 30,
|
|
15
|
+
calendar_timeout_ms: 1e4,
|
|
16
|
+
calendar_max_response_bytes: 1048576,
|
|
17
|
+
retry_max_attempts: 20,
|
|
18
|
+
log_file: join(getDataDir(), "ots-mcp.log"),
|
|
19
|
+
calendars: [
|
|
20
|
+
"https://alice.btc.calendar.opentimestamps.org",
|
|
21
|
+
"https://bob.btc.calendar.opentimestamps.org",
|
|
22
|
+
"https://finney.calendar.eternitywall.com",
|
|
23
|
+
"https://btc.calendar.catallaxy.com"
|
|
24
|
+
],
|
|
25
|
+
esplora_url: "https://blockstream.info/api"
|
|
26
|
+
};
|
|
27
|
+
function loadConfig() {
|
|
28
|
+
const dir = getDataDir();
|
|
29
|
+
mkdirSync(dir, { recursive: true });
|
|
30
|
+
const configPath = join(dir, "config.json");
|
|
31
|
+
if (!existsSync(configPath)) return { ...DEFAULTS };
|
|
32
|
+
const raw = JSON.parse(readFileSync(configPath, "utf8"));
|
|
33
|
+
return { ...DEFAULTS, ...raw };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/db/index.ts
|
|
37
|
+
import DatabaseConstructor from "better-sqlite3";
|
|
38
|
+
import { join as join2 } from "path";
|
|
39
|
+
import { mkdirSync as mkdirSync2, statSync } from "fs";
|
|
40
|
+
|
|
41
|
+
// src/db/schema.ts
|
|
42
|
+
function initDb(db) {
|
|
43
|
+
db.pragma("journal_mode = WAL");
|
|
44
|
+
db.pragma("busy_timeout = 5000");
|
|
45
|
+
db.pragma("foreign_keys = ON");
|
|
46
|
+
runMigrations(db);
|
|
47
|
+
}
|
|
48
|
+
function runMigrations(db) {
|
|
49
|
+
const version = db.pragma("user_version", { simple: true });
|
|
50
|
+
if (version < 1) migrateTo1(db);
|
|
51
|
+
}
|
|
52
|
+
function migrateTo1(db) {
|
|
53
|
+
db.transaction(() => {
|
|
54
|
+
db.exec(`
|
|
55
|
+
CREATE TABLE IF NOT EXISTS stamps (
|
|
56
|
+
id TEXT PRIMARY KEY,
|
|
57
|
+
hash TEXT NOT NULL,
|
|
58
|
+
status TEXT NOT NULL,
|
|
59
|
+
created_at TEXT NOT NULL,
|
|
60
|
+
confirmed_at TEXT,
|
|
61
|
+
bitcoin_block INTEGER,
|
|
62
|
+
bitcoin_time TEXT,
|
|
63
|
+
proof_path TEXT,
|
|
64
|
+
archive_path TEXT,
|
|
65
|
+
last_attempt_at TEXT,
|
|
66
|
+
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
67
|
+
last_error TEXT,
|
|
68
|
+
next_retry_at TEXT,
|
|
69
|
+
metadata TEXT
|
|
70
|
+
);
|
|
71
|
+
CREATE INDEX IF NOT EXISTS idx_stamps_hash ON stamps(hash);
|
|
72
|
+
CREATE INDEX IF NOT EXISTS idx_stamps_status ON stamps(status);
|
|
73
|
+
|
|
74
|
+
CREATE TABLE IF NOT EXISTS operations_log (
|
|
75
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
76
|
+
stamp_id TEXT NOT NULL REFERENCES stamps(id),
|
|
77
|
+
action TEXT NOT NULL,
|
|
78
|
+
result TEXT NOT NULL,
|
|
79
|
+
error_msg TEXT,
|
|
80
|
+
calendar_uri TEXT,
|
|
81
|
+
response_time_ms INTEGER,
|
|
82
|
+
created_at TEXT NOT NULL
|
|
83
|
+
);
|
|
84
|
+
CREATE INDEX IF NOT EXISTS idx_oplog_stamp_id ON operations_log(stamp_id);
|
|
85
|
+
CREATE INDEX IF NOT EXISTS idx_oplog_created ON operations_log(created_at);
|
|
86
|
+
`);
|
|
87
|
+
db.pragma("user_version = 1");
|
|
88
|
+
})();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/db/index.ts
|
|
92
|
+
var _db = null;
|
|
93
|
+
function getDb() {
|
|
94
|
+
if (_db) return _db;
|
|
95
|
+
const dir = getDataDir();
|
|
96
|
+
mkdirSync2(dir, { recursive: true });
|
|
97
|
+
_db = new DatabaseConstructor(join2(dir, "db.sqlite"));
|
|
98
|
+
initDb(_db);
|
|
99
|
+
reconcileOrphans(_db);
|
|
100
|
+
return _db;
|
|
101
|
+
}
|
|
102
|
+
function backupDb(destPath) {
|
|
103
|
+
getDb().backup(destPath);
|
|
104
|
+
}
|
|
105
|
+
function reconcileOrphans(db) {
|
|
106
|
+
const pending = db.prepare(
|
|
107
|
+
`SELECT id, proof_path FROM stamps WHERE status = 'pending' AND proof_path IS NOT NULL`
|
|
108
|
+
).all();
|
|
109
|
+
for (const row of pending) {
|
|
110
|
+
try {
|
|
111
|
+
statSync(row.proof_path);
|
|
112
|
+
} catch {
|
|
113
|
+
db.prepare(`UPDATE stamps SET status = 'failed', last_error = ? WHERE id = ?`).run("proof file missing on disk", row.id);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export {
|
|
119
|
+
getDataDir,
|
|
120
|
+
loadConfig,
|
|
121
|
+
getDb,
|
|
122
|
+
backupDb
|
|
123
|
+
};
|
|
@@ -1,127 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getDataDir
|
|
3
|
+
} from "./chunk-4ZDPWS7C.js";
|
|
1
4
|
import {
|
|
2
5
|
writeAtomic
|
|
3
6
|
} from "./chunk-YFSUDT24.js";
|
|
4
7
|
|
|
5
|
-
// src/config.ts
|
|
6
|
-
import { readFileSync, existsSync, mkdirSync } from "fs";
|
|
7
|
-
import { join } from "path";
|
|
8
|
-
import { homedir } from "os";
|
|
9
|
-
function getDataDir() {
|
|
10
|
-
return process.env.OTS_MCP_DATA_DIR ?? join(homedir(), ".ots-mcp");
|
|
11
|
-
}
|
|
12
|
-
var DEFAULTS = {
|
|
13
|
-
stamp_enabled: true,
|
|
14
|
-
preserve_enabled: true,
|
|
15
|
-
preserve_whitelist: [],
|
|
16
|
-
preserve_max_bytes: 104857600,
|
|
17
|
-
preserve_max_files: 1e4,
|
|
18
|
-
scheduler_interval_minutes: 30,
|
|
19
|
-
calendar_timeout_ms: 1e4,
|
|
20
|
-
calendar_max_response_bytes: 1048576,
|
|
21
|
-
retry_max_attempts: 20,
|
|
22
|
-
log_file: join(getDataDir(), "ots-mcp.log"),
|
|
23
|
-
calendars: [
|
|
24
|
-
"https://alice.btc.calendar.opentimestamps.org",
|
|
25
|
-
"https://bob.btc.calendar.opentimestamps.org",
|
|
26
|
-
"https://finney.calendar.eternitywall.com",
|
|
27
|
-
"https://btc.calendar.catallaxy.com"
|
|
28
|
-
],
|
|
29
|
-
esplora_url: "https://blockstream.info/api"
|
|
30
|
-
};
|
|
31
|
-
function loadConfig() {
|
|
32
|
-
const dir = getDataDir();
|
|
33
|
-
mkdirSync(dir, { recursive: true });
|
|
34
|
-
const configPath = join(dir, "config.json");
|
|
35
|
-
if (!existsSync(configPath)) return { ...DEFAULTS };
|
|
36
|
-
const raw = JSON.parse(readFileSync(configPath, "utf8"));
|
|
37
|
-
return { ...DEFAULTS, ...raw };
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// src/db/index.ts
|
|
41
|
-
import DatabaseConstructor from "better-sqlite3";
|
|
42
|
-
import { join as join2 } from "path";
|
|
43
|
-
import { mkdirSync as mkdirSync2, statSync } from "fs";
|
|
44
|
-
|
|
45
|
-
// src/db/schema.ts
|
|
46
|
-
function initDb(db) {
|
|
47
|
-
db.pragma("journal_mode = WAL");
|
|
48
|
-
db.pragma("busy_timeout = 5000");
|
|
49
|
-
db.pragma("foreign_keys = ON");
|
|
50
|
-
runMigrations(db);
|
|
51
|
-
}
|
|
52
|
-
function runMigrations(db) {
|
|
53
|
-
const version = db.pragma("user_version", { simple: true });
|
|
54
|
-
if (version < 1) migrateTo1(db);
|
|
55
|
-
}
|
|
56
|
-
function migrateTo1(db) {
|
|
57
|
-
db.transaction(() => {
|
|
58
|
-
db.exec(`
|
|
59
|
-
CREATE TABLE IF NOT EXISTS stamps (
|
|
60
|
-
id TEXT PRIMARY KEY,
|
|
61
|
-
hash TEXT NOT NULL,
|
|
62
|
-
status TEXT NOT NULL,
|
|
63
|
-
created_at TEXT NOT NULL,
|
|
64
|
-
confirmed_at TEXT,
|
|
65
|
-
bitcoin_block INTEGER,
|
|
66
|
-
bitcoin_time TEXT,
|
|
67
|
-
proof_path TEXT,
|
|
68
|
-
archive_path TEXT,
|
|
69
|
-
last_attempt_at TEXT,
|
|
70
|
-
attempt_count INTEGER NOT NULL DEFAULT 0,
|
|
71
|
-
last_error TEXT,
|
|
72
|
-
next_retry_at TEXT,
|
|
73
|
-
metadata TEXT
|
|
74
|
-
);
|
|
75
|
-
CREATE INDEX IF NOT EXISTS idx_stamps_hash ON stamps(hash);
|
|
76
|
-
CREATE INDEX IF NOT EXISTS idx_stamps_status ON stamps(status);
|
|
77
|
-
|
|
78
|
-
CREATE TABLE IF NOT EXISTS operations_log (
|
|
79
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
80
|
-
stamp_id TEXT NOT NULL REFERENCES stamps(id),
|
|
81
|
-
action TEXT NOT NULL,
|
|
82
|
-
result TEXT NOT NULL,
|
|
83
|
-
error_msg TEXT,
|
|
84
|
-
calendar_uri TEXT,
|
|
85
|
-
response_time_ms INTEGER,
|
|
86
|
-
created_at TEXT NOT NULL
|
|
87
|
-
);
|
|
88
|
-
CREATE INDEX IF NOT EXISTS idx_oplog_stamp_id ON operations_log(stamp_id);
|
|
89
|
-
CREATE INDEX IF NOT EXISTS idx_oplog_created ON operations_log(created_at);
|
|
90
|
-
`);
|
|
91
|
-
db.pragma("user_version = 1");
|
|
92
|
-
})();
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// src/db/index.ts
|
|
96
|
-
var _db = null;
|
|
97
|
-
function getDb() {
|
|
98
|
-
if (_db) return _db;
|
|
99
|
-
const dir = getDataDir();
|
|
100
|
-
mkdirSync2(dir, { recursive: true });
|
|
101
|
-
_db = new DatabaseConstructor(join2(dir, "db.sqlite"));
|
|
102
|
-
initDb(_db);
|
|
103
|
-
reconcileOrphans(_db);
|
|
104
|
-
return _db;
|
|
105
|
-
}
|
|
106
|
-
function backupDb(destPath) {
|
|
107
|
-
getDb().backup(destPath);
|
|
108
|
-
}
|
|
109
|
-
function reconcileOrphans(db) {
|
|
110
|
-
const pending = db.prepare(
|
|
111
|
-
`SELECT id, proof_path FROM stamps WHERE status = 'pending' AND proof_path IS NOT NULL`
|
|
112
|
-
).all();
|
|
113
|
-
for (const row of pending) {
|
|
114
|
-
try {
|
|
115
|
-
statSync(row.proof_path);
|
|
116
|
-
} catch {
|
|
117
|
-
db.prepare(`UPDATE stamps SET status = 'failed', last_error = ? WHERE id = ?`).run("proof file missing on disk", row.id);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
8
|
// src/tools/create-timestamp.ts
|
|
123
|
-
import { mkdirSync
|
|
124
|
-
import { join
|
|
9
|
+
import { mkdirSync } from "fs";
|
|
10
|
+
import { join } from "path";
|
|
125
11
|
import { randomUUID } from "crypto";
|
|
126
12
|
import { OpenTimestampsClient } from "@otskit/client";
|
|
127
13
|
|
|
@@ -212,9 +98,9 @@ async function createTimestamp(input, db, config) {
|
|
|
212
98
|
}
|
|
213
99
|
const responseTimeMs = Date.now() - t0;
|
|
214
100
|
const id = randomUUID();
|
|
215
|
-
const proofDir =
|
|
216
|
-
|
|
217
|
-
const proofPath =
|
|
101
|
+
const proofDir = join(getDataDir(), "proofs");
|
|
102
|
+
mkdirSync(proofDir, { recursive: true });
|
|
103
|
+
const proofPath = join(proofDir, `${id}.ots`);
|
|
218
104
|
try {
|
|
219
105
|
writeAtomic(proofPath, proofBuffer);
|
|
220
106
|
} catch (e) {
|
|
@@ -233,7 +119,7 @@ async function createTimestamp(input, db, config) {
|
|
|
233
119
|
}
|
|
234
120
|
|
|
235
121
|
// src/tools/upgrade-timestamp.ts
|
|
236
|
-
import { readFileSync
|
|
122
|
+
import { readFileSync } from "fs";
|
|
237
123
|
import { OpenTimestampsClient as OpenTimestampsClient2, UpgradeError } from "@otskit/client";
|
|
238
124
|
import { DetachedTimestampFile } from "@otskit/core";
|
|
239
125
|
function checkBitcoinConfirmation(bytes) {
|
|
@@ -259,7 +145,7 @@ async function upgradeTimestamp(input, db, config) {
|
|
|
259
145
|
const record = getStamp(db, input.id);
|
|
260
146
|
if (!record) return { error: "not_found", details: `No stamp with id ${input.id}` };
|
|
261
147
|
if (!record.proof_path) return { error: "storage_error", details: "No proof_path on record" };
|
|
262
|
-
const proofBefore =
|
|
148
|
+
const proofBefore = readFileSync(record.proof_path);
|
|
263
149
|
const client = new OpenTimestampsClient2({
|
|
264
150
|
calendars: config.calendars,
|
|
265
151
|
resilience: { timeout: config.calendar_timeout_ms }
|
|
@@ -301,7 +187,7 @@ async function upgradeTimestamp(input, db, config) {
|
|
|
301
187
|
}
|
|
302
188
|
|
|
303
189
|
// src/tools/verify-timestamp.ts
|
|
304
|
-
import { readFileSync as
|
|
190
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
305
191
|
import { OpenTimestampsClient as OpenTimestampsClient3 } from "@otskit/client";
|
|
306
192
|
async function verifyTimestamp(input, db, config) {
|
|
307
193
|
const record = getStamp(db, input.id);
|
|
@@ -309,7 +195,7 @@ async function verifyTimestamp(input, db, config) {
|
|
|
309
195
|
if (!record.proof_path) return { error: "storage_error", details: "No proof_path on record" };
|
|
310
196
|
let proofBytes;
|
|
311
197
|
try {
|
|
312
|
-
proofBytes =
|
|
198
|
+
proofBytes = readFileSync2(record.proof_path);
|
|
313
199
|
} catch (e) {
|
|
314
200
|
return { error: "storage_error", details: String(e) };
|
|
315
201
|
}
|
|
@@ -356,10 +242,6 @@ function listPending(input, db, _config) {
|
|
|
356
242
|
}
|
|
357
243
|
|
|
358
244
|
export {
|
|
359
|
-
getDataDir,
|
|
360
|
-
loadConfig,
|
|
361
|
-
getDb,
|
|
362
|
-
backupDb,
|
|
363
245
|
getStamp,
|
|
364
246
|
createTimestamp,
|
|
365
247
|
upgradeTimestamp,
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// src/setup/claude.ts
|
|
2
|
+
import { readFileSync, writeFileSync, copyFileSync, mkdirSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
function getConfigPath() {
|
|
6
|
+
if (process.platform === "win32") {
|
|
7
|
+
return join(process.env.APPDATA ?? "", "Claude", "claude_desktop_config.json");
|
|
8
|
+
} else if (process.platform === "darwin") {
|
|
9
|
+
return join(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
10
|
+
} else {
|
|
11
|
+
return join(homedir(), ".config", "Claude", "claude_desktop_config.json");
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function setupClaude() {
|
|
15
|
+
const configPath = getConfigPath();
|
|
16
|
+
const configDir = join(configPath, "..");
|
|
17
|
+
if (!existsSync(configDir)) {
|
|
18
|
+
mkdirSync(configDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
let config = {};
|
|
21
|
+
if (existsSync(configPath)) {
|
|
22
|
+
try {
|
|
23
|
+
config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
24
|
+
} catch {
|
|
25
|
+
process.stderr.write(` Aviso: no se pudo parsear la config existente, se crear\xE1 nueva.
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
const existing = config.mcpServers ?? {};
|
|
29
|
+
if ("otskit" in existing) {
|
|
30
|
+
process.stdout.write(` ots-mcp ya est\xE1 configurado en ${configPath}
|
|
31
|
+
`);
|
|
32
|
+
process.stdout.write(` No se hizo ning\xFAn cambio.
|
|
33
|
+
`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const backupPath = configPath + ".bak";
|
|
37
|
+
copyFileSync(configPath, backupPath);
|
|
38
|
+
process.stdout.write(` Backup guardado en ${backupPath}
|
|
39
|
+
`);
|
|
40
|
+
}
|
|
41
|
+
const mcpServers = config.mcpServers ?? {};
|
|
42
|
+
mcpServers["otskit"] = { command: "npx", args: ["-y", "@otskit/mcp", "serve"] };
|
|
43
|
+
config.mcpServers = mcpServers;
|
|
44
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
|
|
45
|
+
process.stdout.write(`OTSkit MCP configurado para Claude Desktop
|
|
46
|
+
`);
|
|
47
|
+
process.stdout.write(` Config: ${configPath}
|
|
48
|
+
`);
|
|
49
|
+
process.stdout.write(` Reinicia Claude Desktop para aplicar los cambios.
|
|
50
|
+
`);
|
|
51
|
+
}
|
|
52
|
+
export {
|
|
53
|
+
setupClaude
|
|
54
|
+
};
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
-
backupDb,
|
|
3
2
|
createTimestamp,
|
|
4
|
-
getDb,
|
|
5
3
|
listPending,
|
|
6
|
-
loadConfig,
|
|
7
4
|
upgradeTimestamp,
|
|
8
5
|
verifyTimestamp
|
|
9
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-55NLVWDQ.js";
|
|
7
|
+
import {
|
|
8
|
+
backupDb,
|
|
9
|
+
getDb,
|
|
10
|
+
loadConfig
|
|
11
|
+
} from "./chunk-4ZDPWS7C.js";
|
|
10
12
|
import "./chunk-YFSUDT24.js";
|
|
11
13
|
|
|
12
14
|
// src/cli.ts
|
|
@@ -75,7 +77,7 @@ async function runCli(command, args) {
|
|
|
75
77
|
process.stderr.write(`${record.id.slice(0, 8)}: ${statusStr}
|
|
76
78
|
`);
|
|
77
79
|
}
|
|
78
|
-
|
|
80
|
+
process.exit(0);
|
|
79
81
|
}
|
|
80
82
|
case "backup": {
|
|
81
83
|
const dest = args[0] ?? `ots-mcp-backup-${Date.now()}.sqlite`;
|
|
@@ -85,7 +87,7 @@ async function runCli(command, args) {
|
|
|
85
87
|
break;
|
|
86
88
|
}
|
|
87
89
|
case "scheduler": {
|
|
88
|
-
const { runScheduler } = await import("./scheduler-
|
|
90
|
+
const { runScheduler } = await import("./scheduler-YF6WMSR4.js");
|
|
89
91
|
await runScheduler(args);
|
|
90
92
|
break;
|
|
91
93
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// src/setup/codex.ts
|
|
2
|
+
import { readFileSync, writeFileSync, copyFileSync, mkdirSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
var BLOCK = `
|
|
6
|
+
[mcp_servers.ots-mcp]
|
|
7
|
+
command = "npx"
|
|
8
|
+
args = ["-y", "@otskit/mcp", "serve"]
|
|
9
|
+
`;
|
|
10
|
+
function setupCodex() {
|
|
11
|
+
const configPath = join(homedir(), ".codex", "config.toml");
|
|
12
|
+
const configDir = join(configPath, "..");
|
|
13
|
+
if (!existsSync(configDir)) {
|
|
14
|
+
mkdirSync(configDir, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
let content = "";
|
|
17
|
+
if (existsSync(configPath)) {
|
|
18
|
+
content = readFileSync(configPath, "utf8");
|
|
19
|
+
if (content.includes("[mcp_servers.ots-mcp]")) {
|
|
20
|
+
process.stdout.write(` ots-mcp ya est\xE1 configurado en ${configPath}
|
|
21
|
+
`);
|
|
22
|
+
process.stdout.write(` No se hizo ning\xFAn cambio.
|
|
23
|
+
`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const backupPath = configPath + ".bak";
|
|
27
|
+
copyFileSync(configPath, backupPath);
|
|
28
|
+
process.stdout.write(` Backup guardado en ${backupPath}
|
|
29
|
+
`);
|
|
30
|
+
}
|
|
31
|
+
writeFileSync(configPath, content.trimEnd() + "\n" + BLOCK, "utf8");
|
|
32
|
+
process.stdout.write(`OTSkit MCP configurado para Codex
|
|
33
|
+
`);
|
|
34
|
+
process.stdout.write(` Config: ${configPath}
|
|
35
|
+
`);
|
|
36
|
+
process.stdout.write(` Reinicia Codex para aplicar los cambios.
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
setupCodex
|
|
41
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -5,29 +5,56 @@ var [, , command, ...args] = process.argv;
|
|
|
5
5
|
if (!command || command === "--help" || command === "help") {
|
|
6
6
|
process.stderr.write(`Usage: ots-mcp <command>
|
|
7
7
|
Commands:
|
|
8
|
-
serve
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
serve Start MCP server (stdio transport)
|
|
9
|
+
setup <target> Configure MCP for an agent (claude | codex)
|
|
10
|
+
watch [interval] Watch pending stamps in real-time (default: 5 min)
|
|
11
|
+
stamp <hash> Stamp a SHA-256 hash
|
|
12
|
+
upgrade <id> Upgrade a pending stamp
|
|
13
|
+
verify <id> Verify a stamp
|
|
14
|
+
list [status] List stamps (default: pending)
|
|
15
|
+
check-pending Run pending upgrades (for scheduler)
|
|
16
|
+
backup [dest] Backup the SQLite database
|
|
17
|
+
scheduler Manage OS scheduler (install|remove|status)
|
|
17
18
|
`);
|
|
18
19
|
process.exit(command ? 0 : 1);
|
|
19
20
|
}
|
|
20
21
|
switch (command) {
|
|
21
22
|
case "serve": {
|
|
22
|
-
const { runServer } = await import("./server-
|
|
23
|
+
const { runServer } = await import("./server-Q7XYIZRY.js");
|
|
23
24
|
await runServer();
|
|
24
25
|
break;
|
|
25
26
|
}
|
|
27
|
+
case "setup": {
|
|
28
|
+
const target = args[0];
|
|
29
|
+
if (!target || target !== "claude" && target !== "codex") {
|
|
30
|
+
process.stderr.write(`Usage: ots-mcp setup <claude|codex>
|
|
31
|
+
`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
if (target === "claude") {
|
|
35
|
+
const { setupClaude } = await import("./claude-QTEB3Z5V.js");
|
|
36
|
+
setupClaude();
|
|
37
|
+
} else {
|
|
38
|
+
const { setupCodex } = await import("./codex-P7EDO5GY.js");
|
|
39
|
+
setupCodex();
|
|
40
|
+
}
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
26
43
|
case "install-claude": {
|
|
27
44
|
const { installClaude } = await import("./install-claude-UKSS65BW.js");
|
|
28
45
|
installClaude();
|
|
29
46
|
break;
|
|
30
47
|
}
|
|
48
|
+
case "watch": {
|
|
49
|
+
const { watchPending } = await import("./watch-7NPUWR6I.js");
|
|
50
|
+
const parsed = args[0] ? parseInt(args[0], 10) : NaN;
|
|
51
|
+
const interval = isNaN(parsed) || parsed < 1 ? 5 : parsed;
|
|
52
|
+
if (args[0] && (isNaN(parsed) || parsed < 1))
|
|
53
|
+
process.stderr.write(`Argumento inv\xE1lido "${args[0]}", usando intervalo por defecto: 5 min
|
|
54
|
+
`);
|
|
55
|
+
await watchPending(interval);
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
31
58
|
case "stamp":
|
|
32
59
|
case "upgrade":
|
|
33
60
|
case "verify":
|
|
@@ -35,7 +62,7 @@ switch (command) {
|
|
|
35
62
|
case "check-pending":
|
|
36
63
|
case "backup":
|
|
37
64
|
case "scheduler": {
|
|
38
|
-
const { runCli } = await import("./cli-
|
|
65
|
+
const { runCli } = await import("./cli-ZD2OP45N.js");
|
|
39
66
|
await runCli(command, args);
|
|
40
67
|
break;
|
|
41
68
|
}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createTimestamp,
|
|
3
|
-
getDataDir,
|
|
4
|
-
getDb,
|
|
5
3
|
getStamp,
|
|
6
4
|
listPending,
|
|
7
|
-
loadConfig,
|
|
8
5
|
upgradeTimestamp,
|
|
9
6
|
verifyTimestamp
|
|
10
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-55NLVWDQ.js";
|
|
8
|
+
import {
|
|
9
|
+
getDb,
|
|
10
|
+
loadConfig
|
|
11
|
+
} from "./chunk-4ZDPWS7C.js";
|
|
11
12
|
import "./chunk-YFSUDT24.js";
|
|
12
13
|
|
|
13
14
|
// src/server.ts
|
|
@@ -57,94 +58,16 @@ function inspectTimestamp(input, db, _config) {
|
|
|
57
58
|
};
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
// src/tools/
|
|
61
|
-
import {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const entryParts = resolve(entry).split(sep);
|
|
69
|
-
return entryParts.length <= parts.length && entryParts.every((part, i) => parts[i] === part);
|
|
61
|
+
// src/tools/watch-window.ts
|
|
62
|
+
import { exec } from "child_process";
|
|
63
|
+
function openWatchWindow(intervalMinutes = 5) {
|
|
64
|
+
const minutes = Math.max(1, Math.floor(intervalMinutes));
|
|
65
|
+
const cmd = `start powershell.exe -NoExit -Command "ots-mcp watch ${minutes}"`;
|
|
66
|
+
let errorMsg;
|
|
67
|
+
exec(cmd, { shell: "cmd" }, (err) => {
|
|
68
|
+
if (err) errorMsg = err.message;
|
|
70
69
|
});
|
|
71
|
-
}
|
|
72
|
-
async function preserve(input, db, config) {
|
|
73
|
-
if (!config.preserve_enabled || config.preserve_whitelist.length === 0) {
|
|
74
|
-
return { error: "whitelist_not_configured", details: "Set preserve_whitelist in ~/.ots-mcp/config.json" };
|
|
75
|
-
}
|
|
76
|
-
let resolvedInput;
|
|
77
|
-
try {
|
|
78
|
-
resolvedInput = realpathSync(resolve(input.dir_path));
|
|
79
|
-
} catch (e) {
|
|
80
|
-
return { error: "path_not_in_whitelist", details: `Cannot resolve path: ${e}` };
|
|
81
|
-
}
|
|
82
|
-
const resolvedWhitelist = config.preserve_whitelist.map((p) => {
|
|
83
|
-
try {
|
|
84
|
-
return realpathSync(resolve(p));
|
|
85
|
-
} catch {
|
|
86
|
-
return resolve(p);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
if (!isPathInWhitelist(resolvedInput, resolvedWhitelist)) {
|
|
90
|
-
return { error: "path_not_in_whitelist", details: `${resolvedInput} is not in the configured whitelist` };
|
|
91
|
-
}
|
|
92
|
-
let st;
|
|
93
|
-
try {
|
|
94
|
-
st = statSync2(resolvedInput);
|
|
95
|
-
} catch (e) {
|
|
96
|
-
return { error: "path_not_in_whitelist", details: String(e) };
|
|
97
|
-
}
|
|
98
|
-
if (!st.isDirectory()) {
|
|
99
|
-
return { error: "path_not_in_whitelist", details: "Path must be a directory" };
|
|
100
|
-
}
|
|
101
|
-
const archiveDir = join(getDataDir(), "archives");
|
|
102
|
-
mkdirSync(archiveDir, { recursive: true });
|
|
103
|
-
const label = input.label ? `-${input.label.replace(/[^a-z0-9-]/gi, "_")}` : "";
|
|
104
|
-
const archivePath = join(archiveDir, `${randomUUID()}${label}.zip`);
|
|
105
|
-
const warnings = [];
|
|
106
|
-
let filesCount = 0;
|
|
107
|
-
await new Promise((res, rej) => {
|
|
108
|
-
const output = createWriteStream(archivePath);
|
|
109
|
-
const arc = archiver("zip", { zlib: { level: 6 } });
|
|
110
|
-
arc.on("warning", (e) => {
|
|
111
|
-
if (e.code === "ENOENT") warnings.push(e.message);
|
|
112
|
-
else rej(e);
|
|
113
|
-
});
|
|
114
|
-
arc.on("error", rej);
|
|
115
|
-
arc.on("entry", () => {
|
|
116
|
-
filesCount++;
|
|
117
|
-
});
|
|
118
|
-
output.on("close", res);
|
|
119
|
-
arc.pipe(output);
|
|
120
|
-
arc.directory(resolvedInput, false);
|
|
121
|
-
arc.finalize();
|
|
122
|
-
});
|
|
123
|
-
const archiveSize = statSync2(archivePath).size;
|
|
124
|
-
if (archiveSize > config.preserve_max_bytes) {
|
|
125
|
-
return { error: "resource_limit_exceeded", details: `Archive ${archiveSize} bytes exceeds limit ${config.preserve_max_bytes}` };
|
|
126
|
-
}
|
|
127
|
-
const hash = await new Promise((res, rej) => {
|
|
128
|
-
const h = createHash("sha256");
|
|
129
|
-
createReadStream(archivePath).on("data", (d) => h.update(d)).on("end", () => res(h.digest("hex"))).on("error", rej);
|
|
130
|
-
});
|
|
131
|
-
const stampResult = await createTimestamp({ hash }, db, config);
|
|
132
|
-
if ("error" in stampResult) return { error: "stamp_error", details: stampResult.details };
|
|
133
|
-
db.prepare("UPDATE stamps SET archive_path = ?, metadata = ? WHERE id = ?").run(
|
|
134
|
-
archivePath,
|
|
135
|
-
JSON.stringify({ source_path: resolvedInput, files_count: filesCount, total_bytes: archiveSize }),
|
|
136
|
-
stampResult.id
|
|
137
|
-
);
|
|
138
|
-
return {
|
|
139
|
-
id: stampResult.id,
|
|
140
|
-
hash,
|
|
141
|
-
archive_path: archivePath,
|
|
142
|
-
proof_path: stampResult.proof_path,
|
|
143
|
-
status: "pending",
|
|
144
|
-
files_count: filesCount,
|
|
145
|
-
archive_size_bytes: archiveSize,
|
|
146
|
-
warnings
|
|
147
|
-
};
|
|
70
|
+
return { opened: true, interval_minutes: minutes, ...errorMsg ? { error: errorMsg } : {} };
|
|
148
71
|
}
|
|
149
72
|
|
|
150
73
|
// src/tool-definitions.ts
|
|
@@ -229,21 +152,19 @@ var TOOL_DEFINITIONS = [
|
|
|
229
152
|
}
|
|
230
153
|
},
|
|
231
154
|
{
|
|
232
|
-
name: "
|
|
233
|
-
description: "
|
|
155
|
+
name: "watch",
|
|
156
|
+
description: "Opens a new terminal window that monitors pending stamps in real-time, polling at the given interval. The window stays open so the user can watch progress.",
|
|
234
157
|
inputSchema: {
|
|
235
158
|
type: "object",
|
|
236
159
|
properties: {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
},
|
|
240
|
-
required: ["dir_path"]
|
|
160
|
+
interval_minutes: { type: "number", description: "Polling interval in minutes (default: 5, minimum: 1)" }
|
|
161
|
+
}
|
|
241
162
|
},
|
|
242
163
|
annotations: {
|
|
243
|
-
readOnlyHint:
|
|
164
|
+
readOnlyHint: true,
|
|
244
165
|
destructiveHint: false,
|
|
245
166
|
idempotentHint: false,
|
|
246
|
-
openWorldHint:
|
|
167
|
+
openWorldHint: false
|
|
247
168
|
}
|
|
248
169
|
}
|
|
249
170
|
];
|
|
@@ -279,8 +200,8 @@ async function runServer() {
|
|
|
279
200
|
case "list_pending":
|
|
280
201
|
result = listPending(args, db, config);
|
|
281
202
|
break;
|
|
282
|
-
case "
|
|
283
|
-
result =
|
|
203
|
+
case "watch":
|
|
204
|
+
result = openWatchWindow(args?.interval_minutes);
|
|
284
205
|
break;
|
|
285
206
|
default:
|
|
286
207
|
return { content: [{ type: "text", text: JSON.stringify({ error: "unknown_tool", tool: name }) }], isError: true };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getDb,
|
|
3
|
+
loadConfig
|
|
4
|
+
} from "./chunk-4ZDPWS7C.js";
|
|
5
|
+
|
|
6
|
+
// src/tools/watch.ts
|
|
7
|
+
async function watchPending(intervalMinutes = 5) {
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
const db = getDb(config);
|
|
10
|
+
process.stdout.write(`Watching pending stamps every ${intervalMinutes} min. Ctrl+C to stop.
|
|
11
|
+
|
|
12
|
+
`);
|
|
13
|
+
function tick() {
|
|
14
|
+
const rows = db.prepare(
|
|
15
|
+
`SELECT id, hash, status, attempt_count, bitcoin_block, confirmed_at FROM stamps WHERE status != 'confirmed' ORDER BY created_at DESC`
|
|
16
|
+
).all();
|
|
17
|
+
const confirmed = db.prepare(`SELECT COUNT(*) as n FROM stamps WHERE status = 'confirmed'`).get();
|
|
18
|
+
process.stdout.write(`${now()} \u2014 ${rows.length} pendientes, ${confirmed.n} confirmados
|
|
19
|
+
`);
|
|
20
|
+
for (const row of rows) {
|
|
21
|
+
process.stdout.write(` ${row.id.slice(0, 8)} ${row.status} (${row.attempt_count} intentos)
|
|
22
|
+
`);
|
|
23
|
+
}
|
|
24
|
+
if (rows.length === 0) {
|
|
25
|
+
process.stdout.write(` (ning\xFAn sello pendiente)
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
process.stdout.write("\n");
|
|
29
|
+
}
|
|
30
|
+
function now() {
|
|
31
|
+
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
|
32
|
+
}
|
|
33
|
+
function loop() {
|
|
34
|
+
tick();
|
|
35
|
+
setTimeout(loop, Math.max(6e4, intervalMinutes * 60 * 1e3));
|
|
36
|
+
}
|
|
37
|
+
loop();
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
watchPending
|
|
41
|
+
};
|
package/package.json
CHANGED
|
@@ -1,31 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@otskit/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "OpenTimestamps MCP server — stamp, upgrade, verify via AI agents",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"engines": {
|
|
7
|
-
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=18"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"ots-mcp": "dist/index.js"
|
|
11
|
+
},
|
|
8
12
|
"exports": "./dist/index.js",
|
|
9
|
-
"files": [
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
10
16
|
"scripts": {
|
|
11
17
|
"build": "tsup src/index.ts --format esm --clean",
|
|
12
|
-
"dev":
|
|
13
|
-
"test":
|
|
18
|
+
"dev": "tsup src/index.ts --format esm --watch",
|
|
19
|
+
"test": "vitest run",
|
|
14
20
|
"test:watch": "vitest"
|
|
15
21
|
},
|
|
16
22
|
"dependencies": {
|
|
17
23
|
"@otskit/core": "^0.1.0",
|
|
18
24
|
"@otskit/client": "^0.1.1",
|
|
19
25
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
20
|
-
"better-sqlite3": "^12.10.0"
|
|
21
|
-
"archiver": "^7.0.0"
|
|
26
|
+
"better-sqlite3": "^12.10.0"
|
|
22
27
|
},
|
|
23
28
|
"pnpm": {
|
|
24
|
-
"onlyBuiltDependencies": [
|
|
29
|
+
"onlyBuiltDependencies": [
|
|
30
|
+
"better-sqlite3",
|
|
31
|
+
"esbuild"
|
|
32
|
+
]
|
|
25
33
|
},
|
|
26
34
|
"devDependencies": {
|
|
27
35
|
"@types/better-sqlite3": "^7.6.13",
|
|
28
|
-
"@types/archiver": "^6.0.3",
|
|
29
36
|
"@types/node": "^22.0.0",
|
|
30
37
|
"tsup": "^8.3.5",
|
|
31
38
|
"typescript": "^5.6.3",
|
|
File without changes
|