neozip-cli 0.80.0 ā 0.95.0
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/AGENTS.md +93 -0
- package/CHANGELOG.md +117 -12
- package/DOCUMENTATION.md +33 -38
- package/README.md +137 -63
- package/dist/src/account/account-state.js +95 -0
- package/dist/src/account/format-account-status.js +62 -0
- package/dist/src/account/identity-provision.js +79 -0
- package/dist/src/account/identity-wrap.js +106 -0
- package/dist/src/account/profile-crypto.js +85 -0
- package/dist/src/account/profile-store.js +129 -0
- package/dist/src/account/require-account.js +32 -0
- package/dist/src/account/types.js +3 -0
- package/dist/src/account/wallet-evm.js +46 -0
- package/dist/src/account/wallet-setup.js +33 -0
- package/dist/src/archive/crypto-self.js +117 -0
- package/dist/src/archive/identity-key.js +217 -0
- package/dist/src/archive/recipient-lookup.js +61 -0
- package/dist/src/cli/output.js +100 -0
- package/dist/src/cli/params.js +122 -0
- package/dist/src/cli/schema.js +186 -0
- package/dist/src/cli/validate.js +119 -0
- package/dist/src/commands/mintTimestampProof.js +26 -14
- package/dist/src/config/ConfigSetup.js +82 -423
- package/dist/src/connect/command.js +364 -0
- package/dist/src/connection/bootstrap.js +50 -0
- package/dist/src/connection/cli-guidance.js +101 -0
- package/dist/src/connection/cli-prefs.js +180 -0
- package/dist/src/connection/cli-settings.js +140 -0
- package/dist/src/connection/cli-types.js +14 -0
- package/dist/src/connection/coordinator.js +83 -0
- package/dist/src/connection/credentials.js +33 -0
- package/dist/src/connection/crypto.js +96 -0
- package/dist/src/connection/dump.js +117 -0
- package/dist/src/connection/funding.js +187 -0
- package/dist/src/connection/incomplete-setup.js +89 -0
- package/dist/src/connection/interactive.js +871 -0
- package/dist/src/connection/legacy-profile-reader.js +87 -0
- package/dist/src/connection/magic-link.js +142 -0
- package/dist/src/connection/migrate.js +115 -0
- package/dist/src/connection/onboarding.js +616 -0
- package/dist/src/connection/origin.js +69 -0
- package/dist/src/connection/phase.js +101 -0
- package/dist/src/connection/phone.js +26 -0
- package/dist/src/connection/promote-active.js +56 -0
- package/dist/src/connection/reset.js +56 -0
- package/dist/src/connection/status-report.js +52 -0
- package/dist/src/connection/store.js +406 -0
- package/dist/src/connection/token-auth.js +44 -0
- package/dist/src/connection/types.js +3 -0
- package/dist/src/connection/wallet-json-migration.js +155 -0
- package/dist/src/connection/wallet-login.js +65 -0
- package/dist/src/connection/wallet-setup.js +83 -0
- package/dist/src/constants/wallet-identity.js +14 -0
- package/dist/src/exit-codes.js +54 -10
- package/dist/src/neolist.js +100 -10
- package/dist/src/neounzip.js +236 -65
- package/dist/src/neozip/blockchain.js +18 -18
- package/dist/src/neozip/createZip.js +118 -94
- package/dist/src/neozip/upgradeZip.js +14 -11
- package/dist/src/neozip.js +269 -91
- package/dist/src/skills/command.js +256 -0
- package/dist/src/skills/locate.js +99 -0
- package/dist/src/util/mask.js +34 -0
- package/dist/src/util/token-service-fetch.js +26 -0
- package/env.example +18 -85
- package/package.json +89 -73
- package/skills/neozip-connect/SKILL.md +95 -0
- package/skills/neozip-create/SKILL.md +57 -0
- package/skills/neozip-extract/SKILL.md +58 -0
- package/skills/neozip-legacy/SKILL.md +48 -0
- package/skills/neozip-list/SKILL.md +56 -0
- package/skills/neozip-shared/SKILL.md +60 -0
- package/dist/src/commands/verifyEmail.js +0 -146
- package/dist/src/config/ConfigStore.js +0 -406
package/dist/src/neounzip.js
CHANGED
|
@@ -51,10 +51,14 @@ const neozipkit_1 = require("neozipkit");
|
|
|
51
51
|
const node_1 = __importDefault(require("neozipkit/node"));
|
|
52
52
|
const neozip_blockchain_1 = require("neozip-blockchain");
|
|
53
53
|
const ots_1 = require("neozip-blockchain/ots");
|
|
54
|
-
const
|
|
54
|
+
const token_service_1 = require("neozip-blockchain/token-service");
|
|
55
55
|
const readline = __importStar(require("readline"));
|
|
56
56
|
const version_1 = require("./version");
|
|
57
57
|
const exit_codes_1 = require("./exit-codes");
|
|
58
|
+
const bootstrap_1 = require("./connection/bootstrap");
|
|
59
|
+
const output_1 = require("./cli/output");
|
|
60
|
+
const params_1 = require("./cli/params");
|
|
61
|
+
const validate_1 = require("./cli/validate");
|
|
58
62
|
/**
|
|
59
63
|
* Format bytes for display
|
|
60
64
|
*/
|
|
@@ -286,9 +290,9 @@ async function promptForTimeoutAction(networkConfig, currentRpcIndex, nonInterac
|
|
|
286
290
|
});
|
|
287
291
|
}
|
|
288
292
|
/**
|
|
289
|
-
* Perform
|
|
293
|
+
* Perform Token Service upgrade on the archive (TS-SUBMIT.NZIP -> TIMESTAMP.NZIP)
|
|
290
294
|
*/
|
|
291
|
-
async function
|
|
295
|
+
async function performTokenServiceUpgrade(archivePath) {
|
|
292
296
|
try {
|
|
293
297
|
// Dynamic import: use path without .js for ts-node; tsc emits .js for dist
|
|
294
298
|
// @ts-expect-error TS2835 - node16 requires .js but ts-node resolves .ts from source
|
|
@@ -302,9 +306,9 @@ async function performZipstampUpgrade(archivePath) {
|
|
|
302
306
|
}
|
|
303
307
|
}
|
|
304
308
|
/**
|
|
305
|
-
* Prompt user for
|
|
309
|
+
* Prompt user for Token Service upgrade confirmation
|
|
306
310
|
*/
|
|
307
|
-
function
|
|
311
|
+
function promptForTokenServiceUpgrade(archiveName) {
|
|
308
312
|
return new Promise((resolve) => {
|
|
309
313
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
310
314
|
rl.question(` - Batch confirmed. Upgrade archive to TIMESTAMP.NZIP? (y/N): `, (answer) => {
|
|
@@ -582,8 +586,8 @@ async function testArchive(zip, options) {
|
|
|
582
586
|
return filename !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
583
587
|
filename !== ots_1.TIMESTAMP_METADATA &&
|
|
584
588
|
filename !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
585
|
-
filename !==
|
|
586
|
-
filename !==
|
|
589
|
+
filename !== token_service_1.SUBMIT_METADATA &&
|
|
590
|
+
filename !== token_service_1.TIMESTAMP_METADATA &&
|
|
587
591
|
!entry.isDirectory;
|
|
588
592
|
});
|
|
589
593
|
for (const entry of entries) {
|
|
@@ -611,8 +615,8 @@ async function testArchive(zip, options) {
|
|
|
611
615
|
const isContentFile = name !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
612
616
|
name !== ots_1.TIMESTAMP_METADATA &&
|
|
613
617
|
name !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
614
|
-
name !==
|
|
615
|
-
name !==
|
|
618
|
+
name !== token_service_1.SUBMIT_METADATA &&
|
|
619
|
+
name !== token_service_1.TIMESTAMP_METADATA &&
|
|
616
620
|
!entry.isDirectory;
|
|
617
621
|
if (isContentFile) {
|
|
618
622
|
// Use the verified hash returned from testEntry()
|
|
@@ -625,14 +629,17 @@ async function testArchive(zip, options) {
|
|
|
625
629
|
throw new Error('ZIP file not loaded. Cannot determine backend type.');
|
|
626
630
|
}
|
|
627
631
|
// Report success
|
|
632
|
+
const encTag = entry.isEncrypted
|
|
633
|
+
? (entry.aesVersion > 0 ? ' [AES-256]' : ' [PKZIP]')
|
|
634
|
+
: '';
|
|
628
635
|
if (entry.sha256 && !options.skipBlockchain) {
|
|
629
|
-
console.log(`testing: ${name} ...OK SHA-256`);
|
|
636
|
+
console.log(`testing: ${name} ...OK SHA-256${encTag}`);
|
|
630
637
|
}
|
|
631
638
|
else if (entry.sha256 && options.skipBlockchain) {
|
|
632
|
-
console.log(`testing: ${name} ...OK (SHA-256 skipped)`);
|
|
639
|
+
console.log(`testing: ${name} ...OK (SHA-256 skipped)${encTag}`);
|
|
633
640
|
}
|
|
634
641
|
else {
|
|
635
|
-
console.log(`testing: ${name} ...OK`);
|
|
642
|
+
console.log(`testing: ${name} ...OK${encTag}`);
|
|
636
643
|
}
|
|
637
644
|
}
|
|
638
645
|
catch (err) {
|
|
@@ -645,8 +652,8 @@ async function testArchive(zip, options) {
|
|
|
645
652
|
const isContentFile = name !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
646
653
|
name !== ots_1.TIMESTAMP_METADATA &&
|
|
647
654
|
name !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
648
|
-
name !==
|
|
649
|
-
name !==
|
|
655
|
+
name !== token_service_1.SUBMIT_METADATA &&
|
|
656
|
+
name !== token_service_1.TIMESTAMP_METADATA &&
|
|
650
657
|
!entry.isDirectory;
|
|
651
658
|
if (isEncrypted) {
|
|
652
659
|
// For encrypted files, decompression errors after decryption usually mean wrong password
|
|
@@ -965,8 +972,8 @@ async function extractArchive(zip, destination, options) {
|
|
|
965
972
|
return filename !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
966
973
|
filename !== ots_1.TIMESTAMP_METADATA &&
|
|
967
974
|
filename !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
968
|
-
filename !==
|
|
969
|
-
filename !==
|
|
975
|
+
filename !== token_service_1.SUBMIT_METADATA &&
|
|
976
|
+
filename !== token_service_1.TIMESTAMP_METADATA &&
|
|
970
977
|
!entry.isDirectory;
|
|
971
978
|
});
|
|
972
979
|
for (const entry of filteredEntries) {
|
|
@@ -1036,18 +1043,21 @@ async function extractArchive(zip, destination, options) {
|
|
|
1036
1043
|
totalBytes += result.bytesExtracted;
|
|
1037
1044
|
const hasSha = entry.sha256 ? true : false;
|
|
1038
1045
|
const suffix = hasSha ? (options.skipBlockchain ? ' (SHA-256 skipped)' : ' (SHA-256 OK)') : '';
|
|
1046
|
+
const encTag = entry.isEncrypted
|
|
1047
|
+
? (entry.aesVersion > 0 ? ' [AES-256]' : ' [PKZIP]')
|
|
1048
|
+
: '';
|
|
1039
1049
|
if (options.verbose)
|
|
1040
|
-
log(`extracting: ${filename} (${result.bytesExtracted} bytes)${suffix}`, options);
|
|
1050
|
+
log(`extracting: ${filename} (${result.bytesExtracted} bytes)${suffix}${encTag}`, options);
|
|
1041
1051
|
else
|
|
1042
|
-
log(`extracting: ${filename}${suffix}`, options);
|
|
1052
|
+
log(`extracting: ${filename}${suffix}${encTag}`, options);
|
|
1043
1053
|
// Collect verified SHA-256 hash for merkle root calculation (single pass - no double check)
|
|
1044
1054
|
// Only if this is a content file (not metadata) and SHA-256 verification was performed
|
|
1045
1055
|
if (!options.skipBlockchain && hasSha) {
|
|
1046
1056
|
const isContentFile = filename !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
1047
1057
|
filename !== ots_1.TIMESTAMP_METADATA &&
|
|
1048
1058
|
filename !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
1049
|
-
filename !==
|
|
1050
|
-
filename !==
|
|
1059
|
+
filename !== token_service_1.SUBMIT_METADATA &&
|
|
1060
|
+
filename !== token_service_1.TIMESTAMP_METADATA &&
|
|
1051
1061
|
!entry.isDirectory;
|
|
1052
1062
|
if (isContentFile && entry.sha256) {
|
|
1053
1063
|
// Use the verified hash from the entry (already verified during extraction)
|
|
@@ -1062,8 +1072,8 @@ async function extractArchive(zip, destination, options) {
|
|
|
1062
1072
|
const isContentFile = filename !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
1063
1073
|
filename !== ots_1.TIMESTAMP_METADATA &&
|
|
1064
1074
|
filename !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
1065
|
-
filename !==
|
|
1066
|
-
filename !==
|
|
1075
|
+
filename !== token_service_1.SUBMIT_METADATA &&
|
|
1076
|
+
filename !== token_service_1.TIMESTAMP_METADATA &&
|
|
1067
1077
|
!entry.isDirectory;
|
|
1068
1078
|
if (isContentFile && !options.skipBlockchain) {
|
|
1069
1079
|
// Check if failure was due to SHA-256 mismatch
|
|
@@ -1203,8 +1213,8 @@ async function verifyTokenization(zip, archiveName, options, extractionResult) {
|
|
|
1203
1213
|
return filename !== neozip_blockchain_1.TOKENIZED_METADATA &&
|
|
1204
1214
|
filename !== ots_1.TIMESTAMP_METADATA &&
|
|
1205
1215
|
filename !== ots_1.TIMESTAMP_SUBMITTED &&
|
|
1206
|
-
filename !==
|
|
1207
|
-
filename !==
|
|
1216
|
+
filename !== token_service_1.SUBMIT_METADATA &&
|
|
1217
|
+
filename !== token_service_1.TIMESTAMP_METADATA &&
|
|
1208
1218
|
!entry.isDirectory; // Skip directories
|
|
1209
1219
|
});
|
|
1210
1220
|
// Extract each file once and verify SHA-256 during extraction
|
|
@@ -1554,8 +1564,8 @@ async function verifyTokenization(zip, archiveName, options, extractionResult) {
|
|
|
1554
1564
|
} // End of token verification if statement
|
|
1555
1565
|
// Check for OpenTimestamp verification and upgrade
|
|
1556
1566
|
await verifyAndUpgradeOts(zip, archiveName, options);
|
|
1557
|
-
// Check for
|
|
1558
|
-
await
|
|
1567
|
+
// Check for Token Service verification
|
|
1568
|
+
await verifyTokenServiceTimestamp(zip, archiveName, options, extractionResult);
|
|
1559
1569
|
}
|
|
1560
1570
|
catch (e) {
|
|
1561
1571
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -1667,19 +1677,19 @@ async function verifyAndUpgradeOts(zip, archiveName, options) {
|
|
|
1667
1677
|
}
|
|
1668
1678
|
}
|
|
1669
1679
|
/**
|
|
1670
|
-
* Verify
|
|
1680
|
+
* Verify Token Service timestamp if present
|
|
1671
1681
|
* Uses our extraction logic (extractEntry/extractToFile) for compatibility with file-based ZIPs
|
|
1672
1682
|
* When batch is confirmed but archive has TS-SUBMIT.NZIP, offers upgrade (prompt/auto/skip)
|
|
1673
1683
|
*/
|
|
1674
|
-
async function
|
|
1684
|
+
async function verifyTokenServiceTimestamp(zip, archiveName, options, extractionResult) {
|
|
1675
1685
|
try {
|
|
1676
1686
|
if (options.skipBlockchain) {
|
|
1677
1687
|
return;
|
|
1678
1688
|
}
|
|
1679
1689
|
const entries = await getDirectory(zip, false);
|
|
1680
|
-
const
|
|
1690
|
+
const tokenServiceEntry = entries.find((e) => (e.filename || '') === 'META-INF/TS-SUBMIT.NZIP' ||
|
|
1681
1691
|
(e.filename || '') === 'META-INF/TIMESTAMP.NZIP');
|
|
1682
|
-
if (!
|
|
1692
|
+
if (!tokenServiceEntry) {
|
|
1683
1693
|
return;
|
|
1684
1694
|
}
|
|
1685
1695
|
// Extract timestamp metadata using our extraction (works for both buffer and file-based)
|
|
@@ -1687,7 +1697,7 @@ async function verifyZipstampTimestamp(zip, archiveName, options, extractionResu
|
|
|
1687
1697
|
const bufferBased = isBufferBased(zip);
|
|
1688
1698
|
const fileBased = isFileBased(zip);
|
|
1689
1699
|
if (bufferBased) {
|
|
1690
|
-
const buf = await extractEntry(zip,
|
|
1700
|
+
const buf = await extractEntry(zip, tokenServiceEntry, true);
|
|
1691
1701
|
if (buf) {
|
|
1692
1702
|
try {
|
|
1693
1703
|
timestampData = JSON.parse(buf.toString('utf-8'));
|
|
@@ -1698,9 +1708,9 @@ async function verifyZipstampTimestamp(zip, archiveName, options, extractionResu
|
|
|
1698
1708
|
}
|
|
1699
1709
|
}
|
|
1700
1710
|
else if (fileBased) {
|
|
1701
|
-
const tmpPath = path.join(require('os').tmpdir(), `neounzip-
|
|
1711
|
+
const tmpPath = path.join(require('os').tmpdir(), `neounzip-token-service-${Date.now()}-${(tokenServiceEntry.filename || 'meta').replace(/[^a-zA-Z0-9]/g, '_')}`);
|
|
1702
1712
|
try {
|
|
1703
|
-
await zip.extractToFile(
|
|
1713
|
+
await zip.extractToFile(tokenServiceEntry, tmpPath, { skipHashCheck: true });
|
|
1704
1714
|
const buf = fs.readFileSync(tmpPath);
|
|
1705
1715
|
timestampData = JSON.parse(buf.toString('utf-8'));
|
|
1706
1716
|
}
|
|
@@ -1719,7 +1729,7 @@ async function verifyZipstampTimestamp(zip, archiveName, options, extractionResu
|
|
|
1719
1729
|
}
|
|
1720
1730
|
}
|
|
1721
1731
|
if (!timestampData || !timestampData.digest) {
|
|
1722
|
-
console.log('\nš
|
|
1732
|
+
console.log('\nš Token Service Verification:');
|
|
1723
1733
|
console.log(' - Status: ā ERROR');
|
|
1724
1734
|
console.log(' - Error: Failed to extract timestamp metadata');
|
|
1725
1735
|
return;
|
|
@@ -1728,13 +1738,13 @@ async function verifyZipstampTimestamp(zip, archiveName, options, extractionResu
|
|
|
1728
1738
|
zip.getMerkleRoot?.() ??
|
|
1729
1739
|
null;
|
|
1730
1740
|
if (!merkleRoot) {
|
|
1731
|
-
console.log('\nš
|
|
1741
|
+
console.log('\nš Token Service Verification:');
|
|
1732
1742
|
console.log(' - Status: ā ERROR');
|
|
1733
1743
|
console.log(' - Error: Merkle root not found in ZIP file');
|
|
1734
1744
|
return;
|
|
1735
1745
|
}
|
|
1736
1746
|
if (timestampData.digest.toLowerCase() !== merkleRoot.toLowerCase()) {
|
|
1737
|
-
console.log('\nš
|
|
1747
|
+
console.log('\nš Token Service Verification:');
|
|
1738
1748
|
console.log(' - Status: ā ERROR');
|
|
1739
1749
|
console.log(' - Error: Timestamp digest does not match ZIP merkle root');
|
|
1740
1750
|
return;
|
|
@@ -1742,8 +1752,9 @@ async function verifyZipstampTimestamp(zip, archiveName, options, extractionResu
|
|
|
1742
1752
|
// Use verifyDigest directly - pass undefined for chainId when 0/invalid (server may reject chainId 0)
|
|
1743
1753
|
const chainId = timestampData.chainId && timestampData.chainId > 0 ? timestampData.chainId : undefined;
|
|
1744
1754
|
const batchId = timestampData.batchId || undefined;
|
|
1745
|
-
const response = await (0,
|
|
1755
|
+
const response = await (0, token_service_1.verifyDigest)(merkleRoot, chainId, batchId, undefined, {
|
|
1746
1756
|
debug: options.debug ?? false,
|
|
1757
|
+
serverUrl: (0, bootstrap_1.resolveVerifyServerUrl)(timestampData.serverUrl),
|
|
1747
1758
|
});
|
|
1748
1759
|
const res = response.success
|
|
1749
1760
|
? response.verified
|
|
@@ -1758,10 +1769,10 @@ async function verifyZipstampTimestamp(zip, archiveName, options, extractionResu
|
|
|
1758
1769
|
}
|
|
1759
1770
|
: { status: 'pending', message: 'Timestamp is pending confirmation' }
|
|
1760
1771
|
: { status: 'error', message: response.error || 'Verification failed' };
|
|
1761
|
-
console.log('\nš
|
|
1772
|
+
console.log('\nš Token Service Verification:');
|
|
1762
1773
|
if (res.status === 'valid') {
|
|
1763
1774
|
console.log(' - Status: ā
VERIFIED');
|
|
1764
|
-
console.log(' - Stamp type:
|
|
1775
|
+
console.log(' - Stamp type: Token Service (blockchain timestamp)');
|
|
1765
1776
|
if (res.network) {
|
|
1766
1777
|
console.log(` - Network: ${res.network}`);
|
|
1767
1778
|
}
|
|
@@ -1788,24 +1799,24 @@ async function verifyZipstampTimestamp(zip, archiveName, options, extractionResu
|
|
|
1788
1799
|
console.log(` - Block timestamp (minted): ${mintedStr}`);
|
|
1789
1800
|
}
|
|
1790
1801
|
// Offer upgrade when archive has TS-SUBMIT.NZIP but batch is confirmed
|
|
1791
|
-
const hasPendingMetadata = (
|
|
1802
|
+
const hasPendingMetadata = (tokenServiceEntry.filename || '') === 'META-INF/TS-SUBMIT.NZIP';
|
|
1792
1803
|
if (hasPendingMetadata) {
|
|
1793
1804
|
let doUpgrade = false;
|
|
1794
|
-
if (options.
|
|
1805
|
+
if (options.tokenServiceSkipUpgrade) {
|
|
1795
1806
|
doUpgrade = false;
|
|
1796
|
-
console.log(' - Upgrade: Skipped (--
|
|
1807
|
+
console.log(' - Upgrade: Skipped (--token-service-skip-upgrade)');
|
|
1797
1808
|
}
|
|
1798
|
-
else if (options.
|
|
1809
|
+
else if (options.tokenServiceAutoUpgrade) {
|
|
1799
1810
|
doUpgrade = true;
|
|
1800
|
-
console.log(' - Upgrade: Automatically applying (--
|
|
1811
|
+
console.log(' - Upgrade: Automatically applying (--token-service-auto-upgrade)');
|
|
1801
1812
|
}
|
|
1802
1813
|
else {
|
|
1803
|
-
doUpgrade = await
|
|
1814
|
+
doUpgrade = await promptForTokenServiceUpgrade(archiveName);
|
|
1804
1815
|
}
|
|
1805
1816
|
if (doUpgrade) {
|
|
1806
|
-
await
|
|
1817
|
+
await performTokenServiceUpgrade(archiveName);
|
|
1807
1818
|
}
|
|
1808
|
-
else if (!options.
|
|
1819
|
+
else if (!options.tokenServiceSkipUpgrade && !options.tokenServiceAutoUpgrade) {
|
|
1809
1820
|
console.log(' - Note: Run "neozip upgrade <archive>" to upgrade when ready');
|
|
1810
1821
|
}
|
|
1811
1822
|
}
|
|
@@ -1829,11 +1840,11 @@ async function verifyZipstampTimestamp(zip, archiveName, options, extractionResu
|
|
|
1829
1840
|
}
|
|
1830
1841
|
catch (error) {
|
|
1831
1842
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
1832
|
-
console.log('\nš
|
|
1843
|
+
console.log('\nš Token Service Verification:');
|
|
1833
1844
|
if (errorMessage.includes('ESOCKETTIMEDOUT') || errorMessage.includes('ECONNRESET') ||
|
|
1834
1845
|
errorMessage.includes('ENOTFOUND') || errorMessage.includes('ECONNREFUSED') ||
|
|
1835
1846
|
errorMessage.includes('timeout') || errorMessage.includes('network')) {
|
|
1836
|
-
console.log(' - Status: š” COMMUNICATION ERROR (unable to contact
|
|
1847
|
+
console.log(' - Status: š” COMMUNICATION ERROR (unable to contact Token Service)');
|
|
1837
1848
|
console.log(` - Error: ${errorMessage}`);
|
|
1838
1849
|
}
|
|
1839
1850
|
else {
|
|
@@ -1845,7 +1856,23 @@ async function verifyZipstampTimestamp(zip, archiveName, options, extractionResu
|
|
|
1845
1856
|
/**
|
|
1846
1857
|
* Parse command line arguments
|
|
1847
1858
|
*/
|
|
1848
|
-
function
|
|
1859
|
+
function unzipParamsToOptions(params) {
|
|
1860
|
+
const allowed = [
|
|
1861
|
+
'test', 'skipBlockchain', 'verbose', 'quiet', 'overwrite', 'testOnly', 'bufferSize',
|
|
1862
|
+
'password', 'debug', 'showFiles', 'encryptionMethod', 'exclude', 'include', 'junkPaths',
|
|
1863
|
+
'never', 'exdir', 'preservePerms', 'symlinks', 'hardLinks', 'listOnly', 'freshenOnly',
|
|
1864
|
+
'updateOnly', 'commentOnly', 'otsAutoUpgrade', 'otsSkipUpgrade', 'tokenServiceAutoUpgrade',
|
|
1865
|
+
'tokenServiceSkipUpgrade', 'inMemory', 'nonInteractive', 'preVerify', 'dryRun',
|
|
1866
|
+
];
|
|
1867
|
+
const out = {};
|
|
1868
|
+
for (const key of allowed) {
|
|
1869
|
+
if (key in params) {
|
|
1870
|
+
out[key] = params[key];
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
return out;
|
|
1874
|
+
}
|
|
1875
|
+
function parseArgs(args, seed) {
|
|
1849
1876
|
const options = {
|
|
1850
1877
|
dest: '.',
|
|
1851
1878
|
test: false,
|
|
@@ -1857,11 +1884,13 @@ function parseArgs(args) {
|
|
|
1857
1884
|
bufferSize: 512 * 1024, // 512KB default (optimal for modern systems)
|
|
1858
1885
|
enableProgress: true,
|
|
1859
1886
|
junkPaths: false,
|
|
1860
|
-
never: false
|
|
1887
|
+
never: false,
|
|
1888
|
+
// Seed from agent --params/--json first; explicit CLI flags below override.
|
|
1889
|
+
...(seed?.options ?? {}),
|
|
1861
1890
|
};
|
|
1862
|
-
let archive = '';
|
|
1863
|
-
let destination = '.';
|
|
1864
|
-
let filePatterns = [];
|
|
1891
|
+
let archive = seed?.archive ?? '';
|
|
1892
|
+
let destination = seed?.destination ?? '.';
|
|
1893
|
+
let filePatterns = seed?.filePatterns ? [...seed.filePatterns] : [];
|
|
1865
1894
|
for (let i = 0; i < args.length; i++) {
|
|
1866
1895
|
const arg = args[i];
|
|
1867
1896
|
switch (arg) {
|
|
@@ -2009,11 +2038,11 @@ function parseArgs(args) {
|
|
|
2009
2038
|
case '--ots-skip-upgrade':
|
|
2010
2039
|
options.otsSkipUpgrade = true;
|
|
2011
2040
|
break;
|
|
2012
|
-
case '--
|
|
2013
|
-
options.
|
|
2041
|
+
case '--token-service-auto-upgrade':
|
|
2042
|
+
options.tokenServiceAutoUpgrade = true;
|
|
2014
2043
|
break;
|
|
2015
|
-
case '--
|
|
2016
|
-
options.
|
|
2044
|
+
case '--token-service-skip-upgrade':
|
|
2045
|
+
options.tokenServiceSkipUpgrade = true;
|
|
2017
2046
|
break;
|
|
2018
2047
|
case '--in-memory':
|
|
2019
2048
|
options.inMemory = true;
|
|
@@ -2021,6 +2050,9 @@ function parseArgs(args) {
|
|
|
2021
2050
|
case '--non-interactive':
|
|
2022
2051
|
options.nonInteractive = true;
|
|
2023
2052
|
break;
|
|
2053
|
+
case '--dry-run':
|
|
2054
|
+
options.dryRun = true;
|
|
2055
|
+
break;
|
|
2024
2056
|
default:
|
|
2025
2057
|
if (arg.startsWith('-')) {
|
|
2026
2058
|
console.error(`Error: Unknown option ${arg}`);
|
|
@@ -2061,6 +2093,8 @@ function parseArgs(args) {
|
|
|
2061
2093
|
* Show help information
|
|
2062
2094
|
*/
|
|
2063
2095
|
function showHelp() {
|
|
2096
|
+
if ((0, output_1.isJsonOutput)())
|
|
2097
|
+
return;
|
|
2064
2098
|
console.log(`
|
|
2065
2099
|
NeoUnZip Version: ${version_1.APP_VERSION} (${version_1.APP_RELEASE_DATE})
|
|
2066
2100
|
|
|
@@ -2086,7 +2120,7 @@ Options:
|
|
|
2086
2120
|
--progress Enable enhanced progress reporting
|
|
2087
2121
|
--debug Enable debug output
|
|
2088
2122
|
-sf, --show-files Show files as they are extracted
|
|
2089
|
-
-P, --password [pwd] Password for encrypted files
|
|
2123
|
+
-P, --password [pwd] Password for encrypted files (AES-256 and PKZIP supported)
|
|
2090
2124
|
-x, --exclude <pattern> Exclude files matching pattern (can be used multiple times)
|
|
2091
2125
|
-i, --include <pattern> Include only files matching pattern (can be used multiple times)
|
|
2092
2126
|
-j, --junk-paths Extract files without directory structure (flatten to destination)
|
|
@@ -2097,9 +2131,13 @@ Options:
|
|
|
2097
2131
|
-H, --hard-links Restore hard links efficiently (recreate link relationships)
|
|
2098
2132
|
--ots-auto-upgrade Automatically upgrade OTS timestamps when available
|
|
2099
2133
|
--ots-skip-upgrade Skip OTS timestamp upgrades (no prompt)
|
|
2100
|
-
--
|
|
2101
|
-
--
|
|
2134
|
+
--token-service-auto-upgrade Automatically upgrade Token Service timestamps when confirmed
|
|
2135
|
+
--token-service-skip-upgrade Skip Token Service timestamp upgrades (no prompt)
|
|
2102
2136
|
--in-memory Force in-memory processing mode (browser compatible)
|
|
2137
|
+
--dry-run Show the extraction plan without writing any files
|
|
2138
|
+
--format <fmt> Output format: text (default) or json (machine-readable)
|
|
2139
|
+
--params <json> Provide options as a JSON object (agent input; '-' = stdin)
|
|
2140
|
+
--json <json> Provide payload {archive, exdir, filePatterns} as JSON ('-' = stdin)
|
|
2103
2141
|
|
|
2104
2142
|
Arguments:
|
|
2105
2143
|
<archive> ZIP file to extract or test
|
|
@@ -2115,6 +2153,8 @@ Examples:
|
|
|
2115
2153
|
* Show extended help information (equivalent to InfoZip's -h2)
|
|
2116
2154
|
*/
|
|
2117
2155
|
function showExtendedHelp() {
|
|
2156
|
+
if ((0, output_1.isJsonOutput)())
|
|
2157
|
+
return;
|
|
2118
2158
|
console.log('Extended Help for NeoUnzip');
|
|
2119
2159
|
console.log('');
|
|
2120
2160
|
console.log('NeoUnzip is a next-generation ZIP extraction utility that builds upon the strengths');
|
|
@@ -2237,13 +2277,47 @@ async function main() {
|
|
|
2237
2277
|
let zip = null;
|
|
2238
2278
|
let options = null;
|
|
2239
2279
|
try {
|
|
2280
|
+
(0, bootstrap_1.bootstrapConnectionContext)();
|
|
2240
2281
|
// Parse command line arguments
|
|
2241
2282
|
const args = process.argv.slice(2);
|
|
2242
|
-
|
|
2283
|
+
// Resolve machine-readable output mode early so error exits are structured.
|
|
2284
|
+
(0, output_1.setOutputFormat)((0, output_1.detectOutputFormat)(args));
|
|
2285
|
+
// Extract agent input (--params/--json) and strip --format, then parse.
|
|
2286
|
+
const agent = (0, params_1.extractAgentInput)((0, output_1.stripFormatFlag)(args));
|
|
2287
|
+
const seed = {};
|
|
2288
|
+
if (agent.params)
|
|
2289
|
+
seed.options = unzipParamsToOptions(agent.params);
|
|
2290
|
+
if (agent.payload) {
|
|
2291
|
+
if (typeof agent.payload.archive === 'string')
|
|
2292
|
+
seed.archive = agent.payload.archive;
|
|
2293
|
+
if (typeof agent.payload.destination === 'string')
|
|
2294
|
+
seed.destination = agent.payload.destination;
|
|
2295
|
+
if (typeof agent.payload.exdir === 'string') {
|
|
2296
|
+
seed.options = { ...(seed.options ?? {}), exdir: agent.payload.exdir };
|
|
2297
|
+
}
|
|
2298
|
+
if (Array.isArray(agent.payload.filePatterns)) {
|
|
2299
|
+
seed.filePatterns = agent.payload.filePatterns.map(String);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
const parsed = parseArgs(agent.argv, seed);
|
|
2243
2303
|
const { archive, destination, filePatterns } = parsed;
|
|
2244
2304
|
options = parsed.options;
|
|
2305
|
+
// In JSON mode, suppress decorative human logs so stdout stays pure JSON.
|
|
2306
|
+
if ((0, output_1.isJsonOutput)()) {
|
|
2307
|
+
options.quiet = true;
|
|
2308
|
+
}
|
|
2245
2309
|
// Use exdir option if provided, otherwise use destination
|
|
2246
2310
|
const extractDestination = options.exdir || destination;
|
|
2311
|
+
// Input hardening: reject control characters in the extraction directory.
|
|
2312
|
+
try {
|
|
2313
|
+
(0, validate_1.validateSafeOutputDir)(extractDestination, 'Extraction directory');
|
|
2314
|
+
}
|
|
2315
|
+
catch (err) {
|
|
2316
|
+
if (err instanceof validate_1.ValidationError) {
|
|
2317
|
+
(0, exit_codes_1.exitUnzip)(exit_codes_1.UNZIP_EXIT_CODES.INVALID_OPTIONS, `Error: ${err.message}`);
|
|
2318
|
+
}
|
|
2319
|
+
throw err;
|
|
2320
|
+
}
|
|
2247
2321
|
// Load the ZIP file
|
|
2248
2322
|
zip = new node_1.default();
|
|
2249
2323
|
if (options.inMemory) {
|
|
@@ -2270,6 +2344,13 @@ async function main() {
|
|
|
2270
2344
|
log(`š NEOUNZIP v${version_1.APP_VERSION} (${version_1.APP_RELEASE_DATE})`, options);
|
|
2271
2345
|
log(`š¦ Archive: ${archive}`, options);
|
|
2272
2346
|
log(`š Destination: ${extractDestination}`, options);
|
|
2347
|
+
const allEntries = zip.getDirectory();
|
|
2348
|
+
const hasEncrypted = allEntries.some((e) => e.isEncrypted);
|
|
2349
|
+
if (hasEncrypted) {
|
|
2350
|
+
const hasAes = allEntries.some((e) => e.isEncrypted && e.aesVersion > 0);
|
|
2351
|
+
const encLabel = hasAes ? 'AES-256' : 'PKZIP';
|
|
2352
|
+
log(`š Encryption: ${encLabel}`, options);
|
|
2353
|
+
}
|
|
2273
2354
|
if (options.debug) {
|
|
2274
2355
|
// Set global Logger level to 'debug' for debug output
|
|
2275
2356
|
// Note: Individual class logging is controlled by their loggingEnabled static property
|
|
@@ -2284,17 +2365,71 @@ async function main() {
|
|
|
2284
2365
|
log('', options);
|
|
2285
2366
|
}
|
|
2286
2367
|
}
|
|
2368
|
+
// Dry-run: report the plan without extracting or writing anything.
|
|
2369
|
+
if (options.dryRun) {
|
|
2370
|
+
const dirEntries = zip.getDirectory();
|
|
2371
|
+
const planned = dirEntries
|
|
2372
|
+
.filter((e) => !e.isDirectory && !(typeof e.filename === 'string' && e.filename.endsWith('/')))
|
|
2373
|
+
.map((e) => ({
|
|
2374
|
+
entry: e.filename,
|
|
2375
|
+
target: zip.prepareExtractionPath(e, extractDestination, { junkPaths: options.junkPaths }),
|
|
2376
|
+
size: e.size ?? e.uncompressedSize,
|
|
2377
|
+
}));
|
|
2378
|
+
const plan = {
|
|
2379
|
+
command: 'neounzip',
|
|
2380
|
+
action: options.testOnly ? 'test' : options.listOnly ? 'list' : 'extract',
|
|
2381
|
+
dryRun: true,
|
|
2382
|
+
archive,
|
|
2383
|
+
destination: extractDestination,
|
|
2384
|
+
entryCount: planned.length,
|
|
2385
|
+
entries: planned,
|
|
2386
|
+
};
|
|
2387
|
+
if ((0, output_1.isJsonOutput)()) {
|
|
2388
|
+
(0, output_1.emitResult)(plan);
|
|
2389
|
+
}
|
|
2390
|
+
else {
|
|
2391
|
+
console.log('Dry run ā no files will be written.');
|
|
2392
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
2393
|
+
}
|
|
2394
|
+
process.exit(exit_codes_1.UNZIP_EXIT_CODES.SUCCESS);
|
|
2395
|
+
}
|
|
2287
2396
|
if (options.testOnly) {
|
|
2288
2397
|
const testResult = await testArchive(zip, options);
|
|
2289
2398
|
// Always verify tokenization info after testing so users see results at the end
|
|
2290
2399
|
// Pass pre-calculated merkle root to avoid double extraction (single pass)
|
|
2291
2400
|
await verifyTokenization(zip, archive, options, testResult.extractionResult);
|
|
2401
|
+
if ((0, output_1.isJsonOutput)()) {
|
|
2402
|
+
(0, output_1.emitResult)({
|
|
2403
|
+
command: 'neounzip',
|
|
2404
|
+
action: 'test',
|
|
2405
|
+
archive,
|
|
2406
|
+
failures: testResult.failures,
|
|
2407
|
+
passed: testResult.failures === 0,
|
|
2408
|
+
});
|
|
2409
|
+
}
|
|
2292
2410
|
process.exit(testResult.failures === 0 ? exit_codes_1.UNZIP_EXIT_CODES.SUCCESS : exit_codes_1.UNZIP_EXIT_CODES.WARNING);
|
|
2293
2411
|
}
|
|
2294
2412
|
else if (options.listOnly) {
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2413
|
+
if ((0, output_1.isJsonOutput)()) {
|
|
2414
|
+
const dirEntries = zip.getDirectory();
|
|
2415
|
+
(0, output_1.emitResult)({
|
|
2416
|
+
command: 'neounzip',
|
|
2417
|
+
action: 'list',
|
|
2418
|
+
archive,
|
|
2419
|
+
entryCount: dirEntries.length,
|
|
2420
|
+
entries: dirEntries.map((e) => ({
|
|
2421
|
+
name: e.filename,
|
|
2422
|
+
size: e.size ?? e.uncompressedSize,
|
|
2423
|
+
compressedSize: e.compressedSize,
|
|
2424
|
+
encrypted: !!e.isEncrypted,
|
|
2425
|
+
})),
|
|
2426
|
+
});
|
|
2427
|
+
}
|
|
2428
|
+
else {
|
|
2429
|
+
await listArchive(zip, archive, options);
|
|
2430
|
+
// Show tokenization info after listing (no pre-calculated result, will extract once)
|
|
2431
|
+
await verifyTokenization(zip, archive, options);
|
|
2432
|
+
}
|
|
2298
2433
|
}
|
|
2299
2434
|
else if (options.commentOnly) {
|
|
2300
2435
|
// Comment mode - show only archive comment, no other output
|
|
@@ -2330,12 +2465,48 @@ async function main() {
|
|
|
2330
2465
|
// Pre-verification passed - proceed with extraction
|
|
2331
2466
|
console.log('\nā
Pre-verification passed - proceeding with extraction...\n');
|
|
2332
2467
|
}
|
|
2468
|
+
// Zip-slip protection: validate the ACTUAL output path each entry would be
|
|
2469
|
+
// written to (as computed by neozipkit) and refuse any that escape the
|
|
2470
|
+
// target directory.
|
|
2471
|
+
try {
|
|
2472
|
+
const resolvedDest = path.resolve(extractDestination);
|
|
2473
|
+
const withSep = resolvedDest.endsWith(path.sep) ? resolvedDest : resolvedDest + path.sep;
|
|
2474
|
+
const dirEntries = zip.getDirectory();
|
|
2475
|
+
for (const e of dirEntries) {
|
|
2476
|
+
if (e.isDirectory)
|
|
2477
|
+
continue;
|
|
2478
|
+
const name = e.filename;
|
|
2479
|
+
if (typeof name !== 'string' || name.endsWith('/'))
|
|
2480
|
+
continue;
|
|
2481
|
+
const outPath = zip.prepareExtractionPath(e, extractDestination, { junkPaths: options.junkPaths });
|
|
2482
|
+
const resolved = path.resolve(outPath);
|
|
2483
|
+
if (resolved !== resolvedDest && !resolved.startsWith(withSep)) {
|
|
2484
|
+
throw new validate_1.ValidationError(`Entry "${name}" escapes the extraction directory (path traversal)`);
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
catch (err) {
|
|
2489
|
+
if (err instanceof validate_1.ValidationError) {
|
|
2490
|
+
(0, exit_codes_1.exitUnzip)(exit_codes_1.UNZIP_EXIT_CODES.ZIP_FORMAT_ERROR, `Error: ${err.message}`);
|
|
2491
|
+
}
|
|
2492
|
+
throw err;
|
|
2493
|
+
}
|
|
2333
2494
|
const extractionResult = await extractArchive(zip, extractDestination, options);
|
|
2334
2495
|
// Show tokenization info after extraction (pass pre-calculated merkle root to avoid double extraction)
|
|
2335
2496
|
// Skip blockchain verification if we already did it during pre-verify
|
|
2336
2497
|
if (!options.preVerify) {
|
|
2337
2498
|
await verifyTokenization(zip, archive, options, extractionResult);
|
|
2338
2499
|
}
|
|
2500
|
+
if ((0, output_1.isJsonOutput)()) {
|
|
2501
|
+
(0, output_1.emitResult)({
|
|
2502
|
+
command: 'neounzip',
|
|
2503
|
+
action: 'extract',
|
|
2504
|
+
archive,
|
|
2505
|
+
destination: extractDestination,
|
|
2506
|
+
allFilesValid: extractionResult.allFilesValid,
|
|
2507
|
+
failedFiles: extractionResult.failedFiles,
|
|
2508
|
+
});
|
|
2509
|
+
}
|
|
2339
2510
|
}
|
|
2340
2511
|
log('ā NeoUnZip completed successfully', options);
|
|
2341
2512
|
// Close file handle if file-based ZIP was loaded
|