neozip-mcp 0.1.0-beta
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/.cursor/mcp.json.global.example +10 -0
- package/CHANGELOG.md +16 -0
- package/DOCUMENTATION.md +40 -0
- package/LICENSE +16 -0
- package/README.md +223 -0
- package/SECURITY.md +37 -0
- package/dist/account/account-state.js +86 -0
- package/dist/account/format-account-status.js +37 -0
- package/dist/account/identity-provision.js +75 -0
- package/dist/account/identity-wrap.js +69 -0
- package/dist/account/profile-crypto.js +47 -0
- package/dist/account/profile-store.js +108 -0
- package/dist/account/require-account.js +29 -0
- package/dist/account/token-service-identity.js +395 -0
- package/dist/account/types.js +2 -0
- package/dist/account/wallet-evm.js +39 -0
- package/dist/archive/blockchain-status.js +303 -0
- package/dist/archive/crypto-self.js +114 -0
- package/dist/archive/detect-text.js +56 -0
- package/dist/archive/embed-metadata.js +283 -0
- package/dist/archive/encryption.js +166 -0
- package/dist/archive/extract-entry-buffer.js +18 -0
- package/dist/archive/find-entry.js +21 -0
- package/dist/archive/grep-content.js +141 -0
- package/dist/archive/identity-key.js +176 -0
- package/dist/archive/manifest.js +55 -0
- package/dist/archive/merkle.js +31 -0
- package/dist/archive/metadata-paths.js +14 -0
- package/dist/archive/mint-archive.js +61 -0
- package/dist/archive/open-archive.js +23 -0
- package/dist/archive/read-entry-buffer.js +11 -0
- package/dist/archive/read-entry-content.js +51 -0
- package/dist/archive/recipient-access.js +26 -0
- package/dist/archive/recipient-decrypt.js +21 -0
- package/dist/archive/recipient-lookup.js +55 -0
- package/dist/archive/timestamp-network.js +54 -0
- package/dist/config/capabilities.js +37 -0
- package/dist/config/index.js +74 -0
- package/dist/connect-cli.js +312 -0
- package/dist/connection/coordinator.js +74 -0
- package/dist/connection/credentials.js +29 -0
- package/dist/connection/crypto.js +56 -0
- package/dist/connection/dump.js +79 -0
- package/dist/connection/incomplete-setup.js +81 -0
- package/dist/connection/interactive.js +814 -0
- package/dist/connection/legacy-profile-reader.js +47 -0
- package/dist/connection/magic-link.js +138 -0
- package/dist/connection/migrate.js +76 -0
- package/dist/connection/onboarding.js +524 -0
- package/dist/connection/origin.js +63 -0
- package/dist/connection/phase.js +93 -0
- package/dist/connection/phone.js +20 -0
- package/dist/connection/promote-active.js +53 -0
- package/dist/connection/reset.js +20 -0
- package/dist/connection/setup-guidance.js +154 -0
- package/dist/connection/status-report.js +40 -0
- package/dist/connection/store.js +352 -0
- package/dist/connection/token-auth.js +42 -0
- package/dist/connection/types.js +2 -0
- package/dist/connection/wallet-setup.js +70 -0
- package/dist/constants/wallet-identity.js +11 -0
- package/dist/index.js +47 -0
- package/dist/load-env.js +16 -0
- package/dist/neozipkit-node.js +11 -0
- package/dist/register/resources.js +14 -0
- package/dist/register/tools.js +77 -0
- package/dist/resources/zip-resource.js +40 -0
- package/dist/resources/zip-uri.js +23 -0
- package/dist/security/auth.js +28 -0
- package/dist/security/capabilities.js +85 -0
- package/dist/security/rate-limiter.js +43 -0
- package/dist/security/resource-limiter.js +44 -0
- package/dist/security/sandbox.js +61 -0
- package/dist/server.js +32 -0
- package/dist/startup-account-gate.js +101 -0
- package/dist/startup-summary.js +40 -0
- package/dist/token-service/require-configured.js +23 -0
- package/dist/tools/account.js +504 -0
- package/dist/tools/compress.js +237 -0
- package/dist/tools/connect-status.js +143 -0
- package/dist/tools/extract.js +62 -0
- package/dist/tools/grep-entries.js +42 -0
- package/dist/tools/identity-status.js +157 -0
- package/dist/tools/info.js +147 -0
- package/dist/tools/list.js +118 -0
- package/dist/tools/lookup-recipient.js +37 -0
- package/dist/tools/mint.js +41 -0
- package/dist/tools/read-entry.js +35 -0
- package/dist/tools/search-entries.js +71 -0
- package/dist/tools/stamp.js +60 -0
- package/dist/tools/test.js +90 -0
- package/dist/tools/token-service-account.js +143 -0
- package/dist/tools/upgrade.js +60 -0
- package/dist/tools/verify.js +75 -0
- package/dist/tools/wallet-config-status.js +119 -0
- package/dist/tools/wallet-info.js +64 -0
- package/dist/translators/index.js +106 -0
- package/dist/types/index.js +7 -0
- package/dist/util/mask.js +30 -0
- package/dist/util/token-service-fetch.js +23 -0
- package/dist/vendor/neozipkit-pro.js +3 -0
- package/docs/NEOZIP_CONNECTION_STORE.md +238 -0
- package/docs/NEOZIP_CONNECT_CLI.md +185 -0
- package/docs/OPERATIONS.md +992 -0
- package/docs/examples/CLAUDE.md.example +22 -0
- package/docs/examples/claude/skills/neozip-mcp/SKILL.md +54 -0
- package/docs/examples/claude/skills/neozip-notarization/SKILL.md +75 -0
- package/docs/examples/mcp.json.claude.example +11 -0
- package/docs/examples/neozip-mcp-cursor-rule.mdc +31 -0
- package/docs/installation-guides/INSTALL_CLAUDE_CODE.md +286 -0
- package/docs/installation-guides/INSTALL_CLAUDE_WORKSPACE.md +301 -0
- package/docs/installation-guides/README.md +76 -0
- package/package.json +99 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import * as crypto from "node:crypto";
|
|
2
|
+
import { Wallet } from "ethers";
|
|
3
|
+
import { WRAP_AEAD, WRAP_FORMAT_VERSION, WRAP_IV_LEN, WRAP_KDF_INFO, WRAP_KDF_SALT_LEN, X25519_SPKI_PREFIX, } from "../constants/wallet-identity.js";
|
|
4
|
+
import { fetchCryptoSelf } from "./crypto-self.js";
|
|
5
|
+
import { resolveTokenServiceBaseUrl } from "./recipient-lookup.js";
|
|
6
|
+
export class IdentityKeyTamperError extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "IdentityKeyTamperError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class IdentityKeyVersionError extends Error {
|
|
13
|
+
constructor(message) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = "IdentityKeyVersionError";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function buildCanonicalAad(params) {
|
|
19
|
+
const lowerAddr = params.evmAddress.toLowerCase();
|
|
20
|
+
const addrBytes = Buffer.from(lowerAddr, "ascii");
|
|
21
|
+
if (params.x25519SpkiDer.length !== 44) {
|
|
22
|
+
throw new Error(`Unexpected SPKI DER length ${params.x25519SpkiDer.length}, expected 44 bytes`);
|
|
23
|
+
}
|
|
24
|
+
return Buffer.concat([
|
|
25
|
+
addrBytes,
|
|
26
|
+
Buffer.from([WRAP_FORMAT_VERSION]),
|
|
27
|
+
params.x25519SpkiDer,
|
|
28
|
+
]);
|
|
29
|
+
}
|
|
30
|
+
function deriveKek(rawEoaPrivBytes, saltB64) {
|
|
31
|
+
if (rawEoaPrivBytes.length !== 32) {
|
|
32
|
+
throw new Error("EOA private key must be 32 bytes");
|
|
33
|
+
}
|
|
34
|
+
const salt = Buffer.from(saltB64, "base64");
|
|
35
|
+
if (salt.length !== WRAP_KDF_SALT_LEN) {
|
|
36
|
+
throw new Error(`Wrap KDF salt must be ${WRAP_KDF_SALT_LEN} bytes`);
|
|
37
|
+
}
|
|
38
|
+
const okm = crypto.hkdfSync("sha256", rawEoaPrivBytes, salt, WRAP_KDF_INFO, 32);
|
|
39
|
+
return Buffer.from(okm);
|
|
40
|
+
}
|
|
41
|
+
function pkcs8FromRaw(rawPriv) {
|
|
42
|
+
const prefix = Buffer.from("302e020100300506032b656e04200420", "hex");
|
|
43
|
+
return Buffer.concat([prefix, rawPriv]);
|
|
44
|
+
}
|
|
45
|
+
export function unwrapX25519PrivateKey(params) {
|
|
46
|
+
const { bundle } = params;
|
|
47
|
+
if (bundle.wrapFormatVersion !== WRAP_FORMAT_VERSION) {
|
|
48
|
+
throw new IdentityKeyVersionError(`Unsupported wrap format version ${bundle.wrapFormatVersion}`);
|
|
49
|
+
}
|
|
50
|
+
if (bundle.wrapAead !== WRAP_AEAD || bundle.wrapKdfInfo !== WRAP_KDF_INFO) {
|
|
51
|
+
throw new IdentityKeyVersionError("Unsupported wrap format");
|
|
52
|
+
}
|
|
53
|
+
const expectedSpki = Buffer.from(params.expectedSpkiDerB64, "base64");
|
|
54
|
+
if (expectedSpki.length !== 44 ||
|
|
55
|
+
!expectedSpki.subarray(0, X25519_SPKI_PREFIX.length).equals(X25519_SPKI_PREFIX)) {
|
|
56
|
+
throw new IdentityKeyTamperError("Invalid server X25519 SPKI");
|
|
57
|
+
}
|
|
58
|
+
const recomputedAad = buildCanonicalAad({
|
|
59
|
+
evmAddress: params.expectedEvmAddress,
|
|
60
|
+
x25519SpkiDer: expectedSpki,
|
|
61
|
+
});
|
|
62
|
+
const advertisedAad = Buffer.from(bundle.wrapAadB64, "base64");
|
|
63
|
+
if (advertisedAad.length !== recomputedAad.length ||
|
|
64
|
+
!crypto.timingSafeEqual(advertisedAad, recomputedAad)) {
|
|
65
|
+
throw new IdentityKeyTamperError("Wrap AAD does not match local recomputation");
|
|
66
|
+
}
|
|
67
|
+
const kek = deriveKek(params.rawEoaPrivBytes, bundle.wrapKdfSaltB64);
|
|
68
|
+
const iv = Buffer.from(bundle.wrapIvB64, "base64");
|
|
69
|
+
if (iv.length !== WRAP_IV_LEN) {
|
|
70
|
+
kek.fill(0);
|
|
71
|
+
throw new IdentityKeyVersionError(`Invalid wrap IV length ${iv.length}`);
|
|
72
|
+
}
|
|
73
|
+
const wrapped = Buffer.from(bundle.wrappedPrivateKeyB64, "base64");
|
|
74
|
+
const ct = wrapped.subarray(0, wrapped.length - 16);
|
|
75
|
+
const tag = wrapped.subarray(wrapped.length - 16);
|
|
76
|
+
try {
|
|
77
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", kek, iv);
|
|
78
|
+
decipher.setAAD(recomputedAad);
|
|
79
|
+
decipher.setAuthTag(tag);
|
|
80
|
+
const pt = Buffer.concat([decipher.update(ct), decipher.final()]);
|
|
81
|
+
if (pt.length !== 32) {
|
|
82
|
+
pt.fill(0);
|
|
83
|
+
throw new IdentityKeyTamperError("Unwrapped key length is not 32 bytes");
|
|
84
|
+
}
|
|
85
|
+
return pt;
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
kek.fill(0);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async function fetchJson(url, init) {
|
|
92
|
+
const response = await fetch(url, init);
|
|
93
|
+
const ct = response.headers.get("content-type") || "";
|
|
94
|
+
if (!ct.includes("json")) {
|
|
95
|
+
return { ok: response.ok, status: response.status, data: null };
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
return {
|
|
99
|
+
ok: response.ok,
|
|
100
|
+
status: response.status,
|
|
101
|
+
data: (await response.json()),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return { ok: response.ok, status: response.status, data: null };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function loadIdentityKeyFromWallet(config) {
|
|
109
|
+
const accessToken = config.tokenServiceAccessToken?.trim();
|
|
110
|
+
const walletKey = config.walletKey?.trim();
|
|
111
|
+
if (!accessToken || !walletKey) {
|
|
112
|
+
throw new Error("Wallet identity key requires NEOZIP_TOKEN_SERVICE_ACCESS_TOKEN and NEOZIP_WALLET_PASSKEY in MCP env.");
|
|
113
|
+
}
|
|
114
|
+
const self = await fetchCryptoSelf(config);
|
|
115
|
+
if (!self.ok) {
|
|
116
|
+
throw new Error(self.hint
|
|
117
|
+
? `${self.error} (${self.hint})`
|
|
118
|
+
: self.error);
|
|
119
|
+
}
|
|
120
|
+
const walletId = self.data.walletId;
|
|
121
|
+
const expectedSpkiB64 = self.data.x25519PublicKey;
|
|
122
|
+
const baseUrl = resolveTokenServiceBaseUrl(config);
|
|
123
|
+
const auth = { Authorization: `Bearer ${accessToken}` };
|
|
124
|
+
const keyRes = await fetchJson(`${baseUrl}/identity/wallets/${walletId}/identity-key`, {
|
|
125
|
+
headers: auth,
|
|
126
|
+
});
|
|
127
|
+
if (!keyRes.ok || !keyRes.data?.success || !keyRes.data.identityKey) {
|
|
128
|
+
throw new Error(keyRes.data?.error ||
|
|
129
|
+
`Failed to fetch identity key (HTTP ${keyRes.status})`);
|
|
130
|
+
}
|
|
131
|
+
const bundle = keyRes.data.identityKey;
|
|
132
|
+
const wallet = new Wallet(walletKey);
|
|
133
|
+
const rawEoa = Buffer.from(wallet.privateKey.startsWith("0x")
|
|
134
|
+
? wallet.privateKey.slice(2)
|
|
135
|
+
: wallet.privateKey, "hex");
|
|
136
|
+
const expectedAddr = bundle.evmAddress || wallet.address;
|
|
137
|
+
let x25519Private = null;
|
|
138
|
+
try {
|
|
139
|
+
x25519Private = unwrapX25519PrivateKey({
|
|
140
|
+
rawEoaPrivBytes: rawEoa,
|
|
141
|
+
bundle,
|
|
142
|
+
expectedEvmAddress: expectedAddr,
|
|
143
|
+
expectedSpkiDerB64: expectedSpkiB64,
|
|
144
|
+
});
|
|
145
|
+
const hex = x25519Private.toString("hex");
|
|
146
|
+
return hex;
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
rawEoa.fill(0);
|
|
150
|
+
if (x25519Private)
|
|
151
|
+
x25519Private.fill(0);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export function hasConfiguredIdentityKey(config) {
|
|
155
|
+
return Boolean(config.identityPrivateKeyHex?.trim() ||
|
|
156
|
+
(config.tokenServiceAccessToken?.trim() && config.walletKey?.trim()));
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Resolve X25519 private key hex for recipient-encrypted archives.
|
|
160
|
+
* Never pass through MCP tool arguments — env/config only.
|
|
161
|
+
*/
|
|
162
|
+
export async function resolveIdentityPrivateKeyHex(config) {
|
|
163
|
+
const envHex = config.identityPrivateKeyHex?.trim();
|
|
164
|
+
if (envHex) {
|
|
165
|
+
const clean = envHex.startsWith("0x") ? envHex.slice(2) : envHex;
|
|
166
|
+
if (!/^[0-9a-fA-F]{64}$/.test(clean)) {
|
|
167
|
+
throw new Error("NEOZIP_IDENTITY_PRIVATE_KEY_HEX must be 64 hex characters (32-byte X25519 key)");
|
|
168
|
+
}
|
|
169
|
+
return clean.toLowerCase();
|
|
170
|
+
}
|
|
171
|
+
if (config.tokenServiceAccessToken && config.walletKey) {
|
|
172
|
+
return loadIdentityKeyFromWallet(config);
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=identity-key.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { detectTextSafety } from "./detect-text.js";
|
|
2
|
+
import { getEntryEncryptionLabel, isEntryEncrypted } from "./encryption.js";
|
|
3
|
+
function compressionMethodName(cmpMethod) {
|
|
4
|
+
if (cmpMethod === 0)
|
|
5
|
+
return "Stored";
|
|
6
|
+
if (cmpMethod === 8)
|
|
7
|
+
return "Deflate";
|
|
8
|
+
if (cmpMethod === 93)
|
|
9
|
+
return "Zstd";
|
|
10
|
+
return `Method ${cmpMethod}`;
|
|
11
|
+
}
|
|
12
|
+
export function buildEntrySummary(entry) {
|
|
13
|
+
const filePath = entry.fileName || entry.filename || "";
|
|
14
|
+
const detection = detectTextSafety(filePath);
|
|
15
|
+
const encrypted = isEntryEncrypted(entry);
|
|
16
|
+
const encryptionScheme = getEntryEncryptionLabel(entry);
|
|
17
|
+
return {
|
|
18
|
+
path: filePath,
|
|
19
|
+
uncompressedSize: entry.uncompressedSize || 0,
|
|
20
|
+
compressedSize: entry.compressedSize || 0,
|
|
21
|
+
compressionMethod: compressionMethodName(entry.cmpMethod ?? -1),
|
|
22
|
+
lastModified: entry.lastModDate
|
|
23
|
+
? new Date(entry.lastModDate).toISOString()
|
|
24
|
+
: null,
|
|
25
|
+
encrypted,
|
|
26
|
+
encryptionScheme,
|
|
27
|
+
hasSha256: Boolean(entry.sha256),
|
|
28
|
+
isDirectory: Boolean(entry.isDirectory),
|
|
29
|
+
isTextSafe: detection.isTextSafe && !entry.isDirectory,
|
|
30
|
+
contentType: detection.contentType,
|
|
31
|
+
recommendedTool: entry.isDirectory ? "extract" : detection.recommendedTool,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function buildArchiveManifest(archivePath, entries, extras) {
|
|
35
|
+
const summaries = entries.map(buildEntrySummary);
|
|
36
|
+
let totalUncompressed = 0;
|
|
37
|
+
let totalCompressed = 0;
|
|
38
|
+
for (const s of summaries) {
|
|
39
|
+
totalUncompressed += s.uncompressedSize;
|
|
40
|
+
totalCompressed += s.compressedSize;
|
|
41
|
+
}
|
|
42
|
+
const compressionRatio = totalUncompressed > 0
|
|
43
|
+
? Number(((1 - totalCompressed / totalUncompressed) * 100).toFixed(2))
|
|
44
|
+
: 0;
|
|
45
|
+
return {
|
|
46
|
+
archive: archivePath,
|
|
47
|
+
entryCount: entries.length,
|
|
48
|
+
totalUncompressed,
|
|
49
|
+
totalCompressed,
|
|
50
|
+
compressionRatio,
|
|
51
|
+
entries: summaries,
|
|
52
|
+
...extras,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import neozipkit from "neozipkit";
|
|
2
|
+
const { HashCalculator } = neozipkit;
|
|
3
|
+
import { isMetadataEntryPath } from "./metadata-paths.js";
|
|
4
|
+
export function computeMerkleRoot(entries) {
|
|
5
|
+
let hasSHA256 = false;
|
|
6
|
+
for (const entry of entries) {
|
|
7
|
+
const name = entry.fileName || entry.filename || "";
|
|
8
|
+
if (isMetadataEntryPath(name))
|
|
9
|
+
continue;
|
|
10
|
+
if (entry.sha256)
|
|
11
|
+
hasSHA256 = true;
|
|
12
|
+
}
|
|
13
|
+
if (!hasSHA256)
|
|
14
|
+
return null;
|
|
15
|
+
try {
|
|
16
|
+
const hashCalc = new HashCalculator({ enableAccumulation: true });
|
|
17
|
+
for (const entry of entries) {
|
|
18
|
+
const name = entry.fileName || entry.filename || "";
|
|
19
|
+
if (isMetadataEntryPath(name))
|
|
20
|
+
continue;
|
|
21
|
+
if (entry.sha256) {
|
|
22
|
+
hashCalc.addHash(Buffer.from(entry.sha256, "hex"));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return hashCalc.merkleRoot();
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=merkle.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TOKENIZED_METADATA } from "neozip-blockchain";
|
|
2
|
+
import { ACCESS_NZIP_PATH } from "./recipient-access.js";
|
|
3
|
+
const META_INF_PREFIX = "META-INF/";
|
|
4
|
+
export function isMetadataEntryPath(entryPath) {
|
|
5
|
+
const name = entryPath.replace(/\\/g, "/");
|
|
6
|
+
return (name.startsWith(META_INF_PREFIX) ||
|
|
7
|
+
name === TOKENIZED_METADATA ||
|
|
8
|
+
name === ACCESS_NZIP_PATH ||
|
|
9
|
+
name.endsWith("/TOKEN.NZIP") ||
|
|
10
|
+
name.endsWith("/TS-SUBMIT.NZIP") ||
|
|
11
|
+
name.endsWith("/TIMESTAMP.NZIP") ||
|
|
12
|
+
name.endsWith("/ACCESS.NZIP"));
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=metadata-paths.js.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { ZipkitMinter } from "neozip-blockchain";
|
|
2
|
+
function suppressMinterStdoutProgress() {
|
|
3
|
+
const rawStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
4
|
+
process.stdout.write = function (chunk, encodingOrCb, cb) {
|
|
5
|
+
const text = typeof chunk === "string" ? chunk : Buffer.from(chunk).toString();
|
|
6
|
+
if (text.includes("Waiting for confirmation") ||
|
|
7
|
+
text.includes("⏳") ||
|
|
8
|
+
text === ".") {
|
|
9
|
+
const done = typeof encodingOrCb === "function"
|
|
10
|
+
? encodingOrCb
|
|
11
|
+
: typeof cb === "function"
|
|
12
|
+
? cb
|
|
13
|
+
: undefined;
|
|
14
|
+
if (done)
|
|
15
|
+
process.nextTick(() => done());
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return rawStdoutWrite(chunk, encodingOrCb, cb);
|
|
19
|
+
};
|
|
20
|
+
return () => {
|
|
21
|
+
process.stdout.write = rawStdoutWrite;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async function forceCloseProvider(minter) {
|
|
25
|
+
try {
|
|
26
|
+
minter.destroy();
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// ignore cleanup errors
|
|
30
|
+
}
|
|
31
|
+
const provider = minter
|
|
32
|
+
.provider;
|
|
33
|
+
if (provider?.destroy) {
|
|
34
|
+
await Promise.resolve(provider.destroy());
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export async function mintArchiveToken(merkleRoot, options) {
|
|
38
|
+
const minter = new ZipkitMinter(merkleRoot, {
|
|
39
|
+
walletPrivateKey: options.walletPrivateKey,
|
|
40
|
+
network: options.network,
|
|
41
|
+
debug: options.debug ?? false,
|
|
42
|
+
});
|
|
43
|
+
const restoreStdout = options.suppressProgress !== false
|
|
44
|
+
? suppressMinterStdoutProgress()
|
|
45
|
+
: undefined;
|
|
46
|
+
try {
|
|
47
|
+
if (!options.debug) {
|
|
48
|
+
console.error("Minting on-chain (submitting transaction)...");
|
|
49
|
+
}
|
|
50
|
+
const result = await minter.completeMinting();
|
|
51
|
+
return {
|
|
52
|
+
mintingResult: result.mintingResult,
|
|
53
|
+
tokenMetadata: result.tokenMetadata,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
restoreStdout?.();
|
|
58
|
+
await forceCloseProvider(minter);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=mint-archive.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ZipkitNode } from "../neozipkit-node.js";
|
|
2
|
+
import { applyArchivePassword } from "./encryption.js";
|
|
3
|
+
import { applyRecipientDecryptIfNeeded } from "./recipient-decrypt.js";
|
|
4
|
+
export async function openArchive(input, sandbox, resourceLimiter, config, options) {
|
|
5
|
+
const archivePath = await sandbox.validatePath(input);
|
|
6
|
+
await resourceLimiter.checkFileSize(archivePath);
|
|
7
|
+
const zip = new ZipkitNode();
|
|
8
|
+
await zip.loadZipFile(archivePath);
|
|
9
|
+
let entries = zip.getDirectory() || [];
|
|
10
|
+
resourceLimiter.checkEntryCount(entries.length);
|
|
11
|
+
const recipientResult = await applyRecipientDecryptIfNeeded(zip, archivePath, entries, config, options);
|
|
12
|
+
if (!recipientResult.decrypted) {
|
|
13
|
+
applyArchivePassword(zip, options?.password);
|
|
14
|
+
}
|
|
15
|
+
entries = zip.getDirectory() || [];
|
|
16
|
+
return {
|
|
17
|
+
archivePath,
|
|
18
|
+
zip,
|
|
19
|
+
entries,
|
|
20
|
+
recipientDecrypted: recipientResult.decrypted,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=open-archive.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { findEntryByPath } from "./find-entry.js";
|
|
2
|
+
import { extractEntryToBuffer } from "./extract-entry-buffer.js";
|
|
3
|
+
export async function readEntryBuffer(zip, entries, entryPath) {
|
|
4
|
+
const entry = findEntryByPath(entries, entryPath);
|
|
5
|
+
if (entry.isDirectory) {
|
|
6
|
+
throw new Error(`Entry is a directory, not a file: ${entryPath}`);
|
|
7
|
+
}
|
|
8
|
+
const buffer = await extractEntryToBuffer(zip, entry);
|
|
9
|
+
return { entry, buffer };
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=read-entry-buffer.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { detectTextSafety, isBufferTextSafe } from "./detect-text.js";
|
|
2
|
+
import { readEntryBuffer } from "./read-entry-buffer.js";
|
|
3
|
+
import { translateContent } from "../translators/index.js";
|
|
4
|
+
export function paginateText(text, offset, maxChars) {
|
|
5
|
+
const totalCharacters = text.length;
|
|
6
|
+
const slice = text.slice(offset, offset + maxChars);
|
|
7
|
+
const nextOffset = offset + maxChars;
|
|
8
|
+
const hasMore = nextOffset < totalCharacters;
|
|
9
|
+
return {
|
|
10
|
+
total_characters: totalCharacters,
|
|
11
|
+
has_more: hasMore,
|
|
12
|
+
next_offset: hasMore ? nextOffset : null,
|
|
13
|
+
content: slice,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export async function readEntryContent(zip, entries, config, options) {
|
|
17
|
+
const offset = Math.max(0, options.offset ?? 0);
|
|
18
|
+
let maxChars = options.maxChars ?? config.defaultMaxChars;
|
|
19
|
+
maxChars = Math.min(maxChars, config.maxReadChars);
|
|
20
|
+
const { buffer } = await readEntryBuffer(zip, entries, options.entryPath);
|
|
21
|
+
const detection = detectTextSafety(options.entryPath, buffer);
|
|
22
|
+
if (!detection.isTextSafe && !isBufferTextSafe(buffer)) {
|
|
23
|
+
throw new Error(`Entry is not text-safe (${detection.contentType}). Use extract for binary files.`);
|
|
24
|
+
}
|
|
25
|
+
let text = buffer.toString("utf-8");
|
|
26
|
+
const shouldTranslate = options.translate !== false;
|
|
27
|
+
if (shouldTranslate) {
|
|
28
|
+
const translated = translateContent(options.entryPath, text, {
|
|
29
|
+
maxChars: config.maxReadChars,
|
|
30
|
+
});
|
|
31
|
+
text = translated.content;
|
|
32
|
+
}
|
|
33
|
+
const paginated = paginateText(text, offset, maxChars);
|
|
34
|
+
const result = {
|
|
35
|
+
file_path: options.entryPath.replace(/\\/g, "/").replace(/^\/+/, ""),
|
|
36
|
+
...paginated,
|
|
37
|
+
content: paginated.content,
|
|
38
|
+
};
|
|
39
|
+
if (shouldTranslate) {
|
|
40
|
+
const ext = options.entryPath.includes(".")
|
|
41
|
+
? options.entryPath.slice(options.entryPath.lastIndexOf(".")).toLowerCase()
|
|
42
|
+
: "";
|
|
43
|
+
if (ext === ".json" || ext === ".csv") {
|
|
44
|
+
result.translation = {
|
|
45
|
+
format: ext === ".json" ? "json" : "csv-markdown",
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=read-entry-content.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ACCESS_NZIP_PATH, parseAccessMetadata, } from "../vendor/neozipkit-pro.js";
|
|
2
|
+
import { extractEntryToBuffer } from "./extract-entry-buffer.js";
|
|
3
|
+
export { ACCESS_NZIP_PATH };
|
|
4
|
+
export function hasRecipientAccessMetadata(entries) {
|
|
5
|
+
return entries.some((entry) => {
|
|
6
|
+
const name = entry.fileName || entry.filename || "";
|
|
7
|
+
return name.replace(/\\/g, "/") === ACCESS_NZIP_PATH;
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
export function findAccessMetadataEntry(entries) {
|
|
11
|
+
return entries.find((entry) => {
|
|
12
|
+
const name = entry.fileName || entry.filename || "";
|
|
13
|
+
return name.replace(/\\/g, "/") === ACCESS_NZIP_PATH;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
export async function parseRecipientAccessMetadata(zip, entries) {
|
|
17
|
+
const accessEntry = findAccessMetadataEntry(entries);
|
|
18
|
+
if (!accessEntry)
|
|
19
|
+
return null;
|
|
20
|
+
const raw = await extractEntryToBuffer(zip, accessEntry);
|
|
21
|
+
return parseAccessMetadata(raw);
|
|
22
|
+
}
|
|
23
|
+
export function recipientIdentitiesFromMetadata(metadata) {
|
|
24
|
+
return metadata.recipients.map((r) => r.identity);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=recipient-access.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { decryptAsRecipient } from "../vendor/neozipkit-pro.js";
|
|
2
|
+
import { resolveIdentityPrivateKeyHex } from "./identity-key.js";
|
|
3
|
+
import { hasRecipientAccessMetadata } from "./recipient-access.js";
|
|
4
|
+
export async function applyRecipientDecryptIfNeeded(zip, archivePath, entries, config, options) {
|
|
5
|
+
if (!hasRecipientAccessMetadata(entries)) {
|
|
6
|
+
return { decrypted: false };
|
|
7
|
+
}
|
|
8
|
+
if (options?.password?.trim()) {
|
|
9
|
+
return { decrypted: false };
|
|
10
|
+
}
|
|
11
|
+
const privateKeyHex = await resolveIdentityPrivateKeyHex(config);
|
|
12
|
+
if (!privateKeyHex) {
|
|
13
|
+
if (options?.allowRecipientMetadataOnly) {
|
|
14
|
+
return { decrypted: false };
|
|
15
|
+
}
|
|
16
|
+
throw new Error("Recipient-encrypted archive (META-INF/ACCESS.NZIP). Configure NEOZIP_IDENTITY_PRIVATE_KEY_HEX or NEOZIP_TOKEN_SERVICE_ACCESS_TOKEN + NEOZIP_WALLET_PASSKEY in MCP env (not in tool arguments).");
|
|
17
|
+
}
|
|
18
|
+
await decryptAsRecipient(zip, archivePath, privateKeyHex);
|
|
19
|
+
return { decrypted: true };
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=recipient-decrypt.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { getTokenServiceUrl } from "neozip-blockchain/token-service";
|
|
2
|
+
import { TOKEN_SERVICE_RECIPIENT_IDENTITY_TYPE } from "../constants/wallet-identity.js";
|
|
3
|
+
export function spkiBase64ToRawHex(spkiBase64) {
|
|
4
|
+
const spkiDer = Buffer.from(spkiBase64, "base64");
|
|
5
|
+
if (spkiDer.length !== 44) {
|
|
6
|
+
throw new Error(`Unexpected SPKI DER length ${spkiDer.length}, expected 44 bytes for X25519`);
|
|
7
|
+
}
|
|
8
|
+
return spkiDer.subarray(12).toString("hex");
|
|
9
|
+
}
|
|
10
|
+
export function resolveTokenServiceBaseUrl(config, serverUrl) {
|
|
11
|
+
return getTokenServiceUrl({
|
|
12
|
+
serverUrl: serverUrl || config.tokenServiceUrl || undefined,
|
|
13
|
+
}).replace(/\/+$/, "");
|
|
14
|
+
}
|
|
15
|
+
export async function lookupRecipientByEmail(email, config, options) {
|
|
16
|
+
const trimmed = email.trim().toLowerCase();
|
|
17
|
+
if (!trimmed.includes("@")) {
|
|
18
|
+
throw new Error(`Invalid recipient email: ${email}`);
|
|
19
|
+
}
|
|
20
|
+
const baseUrl = resolveTokenServiceBaseUrl(config, options?.serverUrl);
|
|
21
|
+
const url = `${baseUrl}/crypto/public?email=${encodeURIComponent(trimmed)}`;
|
|
22
|
+
const response = await fetch(url, { method: "GET" });
|
|
23
|
+
const data = (await response.json());
|
|
24
|
+
if (!response.ok || !data.success || !data.crypto?.x25519PublicKey) {
|
|
25
|
+
throw new Error(data.error ||
|
|
26
|
+
`No public key found for ${trimmed} (HTTP ${response.status})`);
|
|
27
|
+
}
|
|
28
|
+
const publicKeyHex = spkiBase64ToRawHex(data.crypto.x25519PublicKey);
|
|
29
|
+
return {
|
|
30
|
+
identity: {
|
|
31
|
+
identity: trimmed,
|
|
32
|
+
identityType: TOKEN_SERVICE_RECIPIENT_IDENTITY_TYPE,
|
|
33
|
+
address: data.crypto.evmAddress || "",
|
|
34
|
+
publicKeyHex,
|
|
35
|
+
keyFormat: "x25519",
|
|
36
|
+
},
|
|
37
|
+
evmAddress: data.crypto.evmAddress || "",
|
|
38
|
+
recipientSuites: data.crypto.recipientSuites,
|
|
39
|
+
cryptoProvisionedAt: data.crypto.cryptoProvisionedAt,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export async function resolveRecipientsFromEmails(emails, config, options) {
|
|
43
|
+
const seen = new Set();
|
|
44
|
+
const recipients = [];
|
|
45
|
+
for (const email of emails) {
|
|
46
|
+
const normalized = email.trim().toLowerCase();
|
|
47
|
+
if (seen.has(normalized))
|
|
48
|
+
continue;
|
|
49
|
+
seen.add(normalized);
|
|
50
|
+
const { identity } = await lookupRecipientByEmail(normalized, config, options);
|
|
51
|
+
recipients.push(identity);
|
|
52
|
+
}
|
|
53
|
+
return recipients;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=recipient-lookup.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import neoBlockchain from "neozip-blockchain";
|
|
2
|
+
const { getChainIdByName, getNetworkByName, getContractConfig } = neoBlockchain;
|
|
3
|
+
/** Infer network slug from Token Service batch IDs like `base-sep-v0.90-n58`. */
|
|
4
|
+
export function inferNetworkFromBatchId(batchId) {
|
|
5
|
+
if (!batchId)
|
|
6
|
+
return null;
|
|
7
|
+
const slug = batchId.split("-")[0]?.toLowerCase();
|
|
8
|
+
if (slug === "base" && batchId.toLowerCase().includes("sep")) {
|
|
9
|
+
return "base-sepolia";
|
|
10
|
+
}
|
|
11
|
+
if (slug === "base")
|
|
12
|
+
return "base";
|
|
13
|
+
if (slug === "eth" && batchId.toLowerCase().includes("sep")) {
|
|
14
|
+
return "sepolia";
|
|
15
|
+
}
|
|
16
|
+
if (slug === "eth")
|
|
17
|
+
return "mainnet";
|
|
18
|
+
return slug || null;
|
|
19
|
+
}
|
|
20
|
+
export function resolveTimestampNetwork(metadata, configNetwork, networkOverride) {
|
|
21
|
+
if (networkOverride)
|
|
22
|
+
return networkOverride;
|
|
23
|
+
if (metadata.network && metadata.network !== "unknown") {
|
|
24
|
+
const byDisplay = getNetworkByName(metadata.network);
|
|
25
|
+
if (byDisplay?.nameAliases?.length) {
|
|
26
|
+
return configNetwork || byDisplay.nameAliases[0];
|
|
27
|
+
}
|
|
28
|
+
return metadata.network;
|
|
29
|
+
}
|
|
30
|
+
return inferNetworkFromBatchId(metadata.batchId) || configNetwork;
|
|
31
|
+
}
|
|
32
|
+
export function enrichTimestampMetadata(metadata, configNetwork, networkOverride) {
|
|
33
|
+
const networkSlug = resolveTimestampNetwork(metadata, configNetwork, networkOverride);
|
|
34
|
+
const chainId = getChainIdByName(networkSlug);
|
|
35
|
+
const networkConfig = getNetworkByName(networkSlug);
|
|
36
|
+
const contractConfig = chainId ? getContractConfig(chainId) : null;
|
|
37
|
+
const enriched = { ...metadata };
|
|
38
|
+
if (chainId && (!enriched.chainId || enriched.chainId === 0)) {
|
|
39
|
+
enriched.chainId = chainId;
|
|
40
|
+
}
|
|
41
|
+
if (networkConfig?.network &&
|
|
42
|
+
(!enriched.network || enriched.network === "unknown")) {
|
|
43
|
+
enriched.network = networkConfig.network;
|
|
44
|
+
}
|
|
45
|
+
if (!enriched.contractAddress && contractConfig?.address) {
|
|
46
|
+
enriched.contractAddress = contractConfig.address;
|
|
47
|
+
}
|
|
48
|
+
else if (!enriched.contractAddress &&
|
|
49
|
+
networkConfig?.registryAddress) {
|
|
50
|
+
enriched.contractAddress = networkConfig.registryAddress;
|
|
51
|
+
}
|
|
52
|
+
return enriched;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=timestamp-network.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { computeConnectionPhase } from "../connection/phase.js";
|
|
2
|
+
import { readActiveConnectionSecrets } from "../connection/store.js";
|
|
3
|
+
import { isAccessTokenExpired } from "../connection/token-auth.js";
|
|
4
|
+
const AUTO_READY = ["zip", "blockchain", "readonly"];
|
|
5
|
+
const AUTO_PENDING = ["zip", "readonly"];
|
|
6
|
+
export function isAutoCapabilitiesMode(raw) {
|
|
7
|
+
const trimmed = raw?.trim().toLowerCase();
|
|
8
|
+
return trimmed === undefined || trimmed === "" || trimmed === "auto";
|
|
9
|
+
}
|
|
10
|
+
export function isConnectReadyForAutoCapabilities() {
|
|
11
|
+
if (computeConnectionPhase() !== "ready") {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
const secrets = readActiveConnectionSecrets();
|
|
15
|
+
return !isAccessTokenExpired(secrets);
|
|
16
|
+
}
|
|
17
|
+
function parseExplicitCapabilities(raw) {
|
|
18
|
+
const groups = raw
|
|
19
|
+
.split(",")
|
|
20
|
+
.map((s) => s.trim().toLowerCase())
|
|
21
|
+
.filter((s) => ["zip", "blockchain", "readonly", "account"].includes(s));
|
|
22
|
+
return groups.length > 0 ? groups : ["zip"];
|
|
23
|
+
}
|
|
24
|
+
export function resolveCapabilities(raw) {
|
|
25
|
+
if (isAutoCapabilitiesMode(raw)) {
|
|
26
|
+
const ready = isConnectReadyForAutoCapabilities();
|
|
27
|
+
return {
|
|
28
|
+
capabilities: ready ? [...AUTO_READY] : [...AUTO_PENDING],
|
|
29
|
+
mode: "auto",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
capabilities: parseExplicitCapabilities(raw),
|
|
34
|
+
mode: "explicit",
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=capabilities.js.map
|