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.
Files changed (74) hide show
  1. package/AGENTS.md +93 -0
  2. package/CHANGELOG.md +117 -12
  3. package/DOCUMENTATION.md +33 -38
  4. package/README.md +137 -63
  5. package/dist/src/account/account-state.js +95 -0
  6. package/dist/src/account/format-account-status.js +62 -0
  7. package/dist/src/account/identity-provision.js +79 -0
  8. package/dist/src/account/identity-wrap.js +106 -0
  9. package/dist/src/account/profile-crypto.js +85 -0
  10. package/dist/src/account/profile-store.js +129 -0
  11. package/dist/src/account/require-account.js +32 -0
  12. package/dist/src/account/types.js +3 -0
  13. package/dist/src/account/wallet-evm.js +46 -0
  14. package/dist/src/account/wallet-setup.js +33 -0
  15. package/dist/src/archive/crypto-self.js +117 -0
  16. package/dist/src/archive/identity-key.js +217 -0
  17. package/dist/src/archive/recipient-lookup.js +61 -0
  18. package/dist/src/cli/output.js +100 -0
  19. package/dist/src/cli/params.js +122 -0
  20. package/dist/src/cli/schema.js +186 -0
  21. package/dist/src/cli/validate.js +119 -0
  22. package/dist/src/commands/mintTimestampProof.js +26 -14
  23. package/dist/src/config/ConfigSetup.js +82 -423
  24. package/dist/src/connect/command.js +364 -0
  25. package/dist/src/connection/bootstrap.js +50 -0
  26. package/dist/src/connection/cli-guidance.js +101 -0
  27. package/dist/src/connection/cli-prefs.js +180 -0
  28. package/dist/src/connection/cli-settings.js +140 -0
  29. package/dist/src/connection/cli-types.js +14 -0
  30. package/dist/src/connection/coordinator.js +83 -0
  31. package/dist/src/connection/credentials.js +33 -0
  32. package/dist/src/connection/crypto.js +96 -0
  33. package/dist/src/connection/dump.js +117 -0
  34. package/dist/src/connection/funding.js +187 -0
  35. package/dist/src/connection/incomplete-setup.js +89 -0
  36. package/dist/src/connection/interactive.js +871 -0
  37. package/dist/src/connection/legacy-profile-reader.js +87 -0
  38. package/dist/src/connection/magic-link.js +142 -0
  39. package/dist/src/connection/migrate.js +115 -0
  40. package/dist/src/connection/onboarding.js +616 -0
  41. package/dist/src/connection/origin.js +69 -0
  42. package/dist/src/connection/phase.js +101 -0
  43. package/dist/src/connection/phone.js +26 -0
  44. package/dist/src/connection/promote-active.js +56 -0
  45. package/dist/src/connection/reset.js +56 -0
  46. package/dist/src/connection/status-report.js +52 -0
  47. package/dist/src/connection/store.js +406 -0
  48. package/dist/src/connection/token-auth.js +44 -0
  49. package/dist/src/connection/types.js +3 -0
  50. package/dist/src/connection/wallet-json-migration.js +155 -0
  51. package/dist/src/connection/wallet-login.js +65 -0
  52. package/dist/src/connection/wallet-setup.js +83 -0
  53. package/dist/src/constants/wallet-identity.js +14 -0
  54. package/dist/src/exit-codes.js +54 -10
  55. package/dist/src/neolist.js +100 -10
  56. package/dist/src/neounzip.js +236 -65
  57. package/dist/src/neozip/blockchain.js +18 -18
  58. package/dist/src/neozip/createZip.js +118 -94
  59. package/dist/src/neozip/upgradeZip.js +14 -11
  60. package/dist/src/neozip.js +269 -91
  61. package/dist/src/skills/command.js +256 -0
  62. package/dist/src/skills/locate.js +99 -0
  63. package/dist/src/util/mask.js +34 -0
  64. package/dist/src/util/token-service-fetch.js +26 -0
  65. package/env.example +18 -85
  66. package/package.json +89 -73
  67. package/skills/neozip-connect/SKILL.md +95 -0
  68. package/skills/neozip-create/SKILL.md +57 -0
  69. package/skills/neozip-extract/SKILL.md +58 -0
  70. package/skills/neozip-legacy/SKILL.md +48 -0
  71. package/skills/neozip-list/SKILL.md +56 -0
  72. package/skills/neozip-shared/SKILL.md +60 -0
  73. package/dist/src/commands/verifyEmail.js +0 -146
  74. package/dist/src/config/ConfigStore.js +0 -406
@@ -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 zipstamp_server_1 = require("neozip-blockchain/zipstamp-server");
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 Zipstamp upgrade on the archive (TS-SUBMIT.NZIP -> TIMESTAMP.NZIP)
293
+ * Perform Token Service upgrade on the archive (TS-SUBMIT.NZIP -> TIMESTAMP.NZIP)
290
294
  */
291
- async function performZipstampUpgrade(archivePath) {
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 Zipstamp upgrade confirmation
309
+ * Prompt user for Token Service upgrade confirmation
306
310
  */
307
- function promptForZipstampUpgrade(archiveName) {
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 !== zipstamp_server_1.SUBMIT_METADATA &&
586
- filename !== zipstamp_server_1.TIMESTAMP_METADATA &&
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 !== zipstamp_server_1.SUBMIT_METADATA &&
615
- name !== zipstamp_server_1.TIMESTAMP_METADATA &&
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 !== zipstamp_server_1.SUBMIT_METADATA &&
649
- name !== zipstamp_server_1.TIMESTAMP_METADATA &&
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 !== zipstamp_server_1.SUBMIT_METADATA &&
969
- filename !== zipstamp_server_1.TIMESTAMP_METADATA &&
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 !== zipstamp_server_1.SUBMIT_METADATA &&
1050
- filename !== zipstamp_server_1.TIMESTAMP_METADATA &&
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 !== zipstamp_server_1.SUBMIT_METADATA &&
1066
- filename !== zipstamp_server_1.TIMESTAMP_METADATA &&
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 !== zipstamp_server_1.SUBMIT_METADATA &&
1207
- filename !== zipstamp_server_1.TIMESTAMP_METADATA &&
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 Zipstamp verification
1558
- await verifyZipstampTimestamp(zip, archiveName, options, extractionResult);
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 Zipstamp timestamp if present
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 verifyZipstampTimestamp(zip, archiveName, options, extractionResult) {
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 zipstampEntry = entries.find((e) => (e.filename || '') === 'META-INF/TS-SUBMIT.NZIP' ||
1690
+ const tokenServiceEntry = entries.find((e) => (e.filename || '') === 'META-INF/TS-SUBMIT.NZIP' ||
1681
1691
  (e.filename || '') === 'META-INF/TIMESTAMP.NZIP');
1682
- if (!zipstampEntry) {
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, zipstampEntry, true);
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-zipstamp-${Date.now()}-${(zipstampEntry.filename || 'meta').replace(/[^a-zA-Z0-9]/g, '_')}`);
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(zipstampEntry, tmpPath, { skipHashCheck: true });
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šŸ”— Zipstamp Verification:');
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šŸ”— Zipstamp Verification:');
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šŸ”— Zipstamp Verification:');
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, zipstamp_server_1.verifyDigest)(merkleRoot, chainId, batchId, undefined, {
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šŸ”— Zipstamp Verification:');
1772
+ console.log('\nšŸ”— Token Service Verification:');
1762
1773
  if (res.status === 'valid') {
1763
1774
  console.log(' - Status: āœ… VERIFIED');
1764
- console.log(' - Stamp type: Zipstamp (blockchain timestamp)');
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 = (zipstampEntry.filename || '') === 'META-INF/TS-SUBMIT.NZIP';
1802
+ const hasPendingMetadata = (tokenServiceEntry.filename || '') === 'META-INF/TS-SUBMIT.NZIP';
1792
1803
  if (hasPendingMetadata) {
1793
1804
  let doUpgrade = false;
1794
- if (options.zipstampSkipUpgrade) {
1805
+ if (options.tokenServiceSkipUpgrade) {
1795
1806
  doUpgrade = false;
1796
- console.log(' - Upgrade: Skipped (--zipstamp-skip-upgrade)');
1807
+ console.log(' - Upgrade: Skipped (--token-service-skip-upgrade)');
1797
1808
  }
1798
- else if (options.zipstampAutoUpgrade) {
1809
+ else if (options.tokenServiceAutoUpgrade) {
1799
1810
  doUpgrade = true;
1800
- console.log(' - Upgrade: Automatically applying (--zipstamp-auto-upgrade)');
1811
+ console.log(' - Upgrade: Automatically applying (--token-service-auto-upgrade)');
1801
1812
  }
1802
1813
  else {
1803
- doUpgrade = await promptForZipstampUpgrade(archiveName);
1814
+ doUpgrade = await promptForTokenServiceUpgrade(archiveName);
1804
1815
  }
1805
1816
  if (doUpgrade) {
1806
- await performZipstampUpgrade(archiveName);
1817
+ await performTokenServiceUpgrade(archiveName);
1807
1818
  }
1808
- else if (!options.zipstampSkipUpgrade && !options.zipstampAutoUpgrade) {
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šŸ”— Zipstamp Verification:');
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 Zipstamp server)');
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 parseArgs(args) {
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 '--zipstamp-auto-upgrade':
2013
- options.zipstampAutoUpgrade = true;
2041
+ case '--token-service-auto-upgrade':
2042
+ options.tokenServiceAutoUpgrade = true;
2014
2043
  break;
2015
- case '--zipstamp-skip-upgrade':
2016
- options.zipstampSkipUpgrade = true;
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. Provide password or will prompt
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
- --zipstamp-auto-upgrade Automatically upgrade Zipstamp timestamps when confirmed
2101
- --zipstamp-skip-upgrade Skip Zipstamp timestamp upgrades (no prompt)
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
- const parsed = parseArgs(args);
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
- await listArchive(zip, archive, options);
2296
- // Show tokenization info after listing (no pre-calculated result, will extract once)
2297
- await verifyTokenization(zip, archive, options);
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