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
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WALLET_SETUP_HINT = void 0;
4
+ exports.fetchServerLinkedWalletAddress = fetchServerLinkedWalletAddress;
5
+ exports.validateWalletImportAgainstServer = validateWalletImportAgainstServer;
6
+ exports.requireWalletPreconditions = requireWalletPreconditions;
7
+ exports.requirePhonePreconditions = requirePhonePreconditions;
8
+ exports.walletAlreadyConfigured = walletAlreadyConfigured;
9
+ exports.saveImportedWallet = saveImportedWallet;
10
+ exports.createLocalWalletIdentity = createLocalWalletIdentity;
11
+ exports.importWalletFromMnemonic = importWalletFromMnemonic;
12
+ exports.importWalletFromPrivateKey = importWalletFromPrivateKey;
13
+ exports.activeWalletContext = activeWalletContext;
14
+ const ethers_1 = require("ethers");
15
+ const crypto_self_1 = require("../archive/crypto-self");
16
+ const wallet_evm_1 = require("../account/wallet-evm");
17
+ const store_1 = require("./store");
18
+ const phone_1 = require("./phone");
19
+ exports.WALLET_SETUP_HINT = "Configure a Data Wallet first: neozip connect wallet create --ack-backup, wallet import-mnemonic, or wallet import-key. Or use finish --create-wallet to generate one (no recovery phrase shown).";
20
+ async function fetchServerLinkedWalletAddress(baseUrl, accessToken) {
21
+ const result = await (0, crypto_self_1.fetchCryptoSelf)({
22
+ tokenServiceAccessToken: accessToken,
23
+ tokenServiceUrl: baseUrl,
24
+ }, { serverUrl: baseUrl });
25
+ if (!result.ok)
26
+ return null;
27
+ try {
28
+ return (0, ethers_1.getAddress)(result.data.evmAddress);
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
34
+ async function validateWalletImportAgainstServer(input) {
35
+ if (input.force)
36
+ return;
37
+ const serverAddress = await fetchServerLinkedWalletAddress(input.origin, input.accessToken);
38
+ if (!serverAddress)
39
+ return;
40
+ const derived = (0, ethers_1.getAddress)(input.derivedAddress);
41
+ if (derived.toLowerCase() !== serverAddress.toLowerCase()) {
42
+ throw new Error(`Imported wallet (${derived}) differs from the wallet linked on Token Service (${serverAddress}). ` +
43
+ "Restore the correct recovery phrase or private key, or pass --force to replace the server wallet.");
44
+ }
45
+ }
46
+ function requireWalletPreconditions(conn, secrets) {
47
+ if (!secrets?.accessToken || !conn.emailVerified) {
48
+ throw new Error("Verify email first. Run neozip connect verify.");
49
+ }
50
+ }
51
+ async function requirePhonePreconditions(conn, origin) {
52
+ if (await (0, phone_1.needsPhoneVerification)(conn, origin)) {
53
+ throw new Error("Phone verification required. Run neozip connect phone request and phone verify.");
54
+ }
55
+ }
56
+ function walletAlreadyConfigured(conn, secrets) {
57
+ return Boolean(secrets?.evmPrivateKeyHex && conn.evmAddress);
58
+ }
59
+ async function saveImportedWallet(connectionId, evm) {
60
+ return (0, store_1.saveEvmWallet)(connectionId, evm.privateKey, evm.address);
61
+ }
62
+ function createLocalWalletIdentity() {
63
+ const evm = (0, wallet_evm_1.generateEvmIdentity)();
64
+ if (!evm.mnemonic) {
65
+ throw new Error("Failed to generate recovery phrase for new Data Wallet");
66
+ }
67
+ return evm;
68
+ }
69
+ function importWalletFromMnemonic(mnemonic) {
70
+ return (0, wallet_evm_1.evmIdentityFromMnemonic)(mnemonic);
71
+ }
72
+ function importWalletFromPrivateKey(privateKey) {
73
+ return (0, wallet_evm_1.evmIdentityFromPrivateKey)(privateKey);
74
+ }
75
+ function activeWalletContext() {
76
+ const conn = (0, store_1.getActiveConnection)();
77
+ const secrets = (0, store_1.readActiveConnectionSecrets)();
78
+ if (!conn || !secrets) {
79
+ throw new Error("No active connection. Run neozip connect first.");
80
+ }
81
+ return { conn, secrets, origin: conn.tokenServiceOrigin };
82
+ }
83
+ //# sourceMappingURL=wallet-setup.js.map
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TOKEN_SERVICE_RECIPIENT_IDENTITY_TYPE = exports.X25519_SPKI_PREFIX = exports.WRAP_IV_LEN = exports.WRAP_KDF_SALT_LEN = exports.WRAP_AEAD = exports.WRAP_KDF_INFO = exports.WRAP_FORMAT_VERSION = void 0;
4
+ /** Wallet-encrypted X25519 identity key wrap format (Token Service API_IDENTITY.md). */
5
+ exports.WRAP_FORMAT_VERSION = 1;
6
+ exports.WRAP_KDF_INFO = "NeoZip/WalletEncIK/v1";
7
+ exports.WRAP_AEAD = "AES-256-GCM";
8
+ exports.WRAP_KDF_SALT_LEN = 32;
9
+ exports.WRAP_IV_LEN = 12;
10
+ /** SPKI-DER prefix for X25519 public keys (12 bytes). */
11
+ exports.X25519_SPKI_PREFIX = Buffer.from("302a300506032b656e032100", "hex");
12
+ /** neozipkit-pro identity type for Token Service email recipients. */
13
+ exports.TOKEN_SERVICE_RECIPIENT_IDENTITY_TYPE = "zipstamp";
14
+ //# sourceMappingURL=wallet-identity.js.map
@@ -11,6 +11,7 @@ exports.exitZip = exitZip;
11
11
  exports.exitUnzip = exitUnzip;
12
12
  exports.exitZipinfo = exitZipinfo;
13
13
  exports.getExitCodeDescription = getExitCodeDescription;
14
+ const output_1 = require("./cli/output");
14
15
  /**
15
16
  * Exit codes for NEOZIP application
16
17
  * Based on InfoZip ZIP exit codes
@@ -97,31 +98,74 @@ exports.ZIPINFO_EXIT_CODES = {
97
98
  OTS_VERIFY_FAILED: 89, // OpenTimestamp verification failed
98
99
  };
99
100
  /**
100
- * Helper function to exit with a ZIP exit code
101
+ * Reverse lookup: numeric exit code -> symbolic name, per application.
101
102
  */
102
- function exitZip(code, message) {
103
+ function symbolicName(table, code) {
104
+ for (const [name, value] of Object.entries(table)) {
105
+ if (value === code)
106
+ return name;
107
+ }
108
+ return 'UNKNOWN_ERROR';
109
+ }
110
+ /**
111
+ * Suggested recovery command for an agent, keyed by symbolic error name.
112
+ * Only a subset of codes have a meaningful next step.
113
+ */
114
+ const NEXT_COMMAND_HINTS = {
115
+ BLOCKCHAIN_CONFIG_ERROR: 'neozip connect',
116
+ TOKEN_MINT_FAILED: 'neozip connect status',
117
+ TOKEN_MINT_TIMEOUT: 'neozip upgrade <archive> --wait',
118
+ TOKEN_MINT_INSUFFICIENT_BALANCE: 'neozip connect status',
119
+ TOKEN_MINT_USER_CANCELLED: 'neozip connect status',
120
+ BAD_PASSWORD: 'neounzip -P <password> <archive>',
121
+ TOKEN_VERIFY_FAILED: 'neounzip --skip-blockchain <archive>',
122
+ };
123
+ /**
124
+ * Emit a structured JSON error (when in JSON mode) and exit.
125
+ *
126
+ * In text mode this preserves the original behavior: print the message to stderr
127
+ * and exit with the numeric code. In JSON mode a structured error object is
128
+ * written to stdout so callers can parse it, and the human message (if any) goes
129
+ * to stderr as a hint.
130
+ */
131
+ function exitWithError(app, table, code, message) {
132
+ if ((0, output_1.isJsonOutput)()) {
133
+ const name = symbolicName(table, code);
134
+ const cleanMessage = (message ?? getExitCodeDescription(app, code)).replace(/^Error:\s*/i, '');
135
+ (0, output_1.emitError)({
136
+ app,
137
+ code: name,
138
+ exitCode: code,
139
+ message: cleanMessage,
140
+ ...(NEXT_COMMAND_HINTS[name] ? { nextCommand: NEXT_COMMAND_HINTS[name] } : {}),
141
+ });
142
+ if (message) {
143
+ console.error(message);
144
+ }
145
+ process.exit(code);
146
+ }
103
147
  if (message) {
104
148
  console.error(message);
105
149
  }
106
150
  process.exit(code);
107
151
  }
152
+ /**
153
+ * Helper function to exit with a ZIP exit code
154
+ */
155
+ function exitZip(code, message) {
156
+ return exitWithError('zip', exports.ZIP_EXIT_CODES, code, message);
157
+ }
108
158
  /**
109
159
  * Helper function to exit with an UNZIP exit code
110
160
  */
111
161
  function exitUnzip(code, message) {
112
- if (message) {
113
- console.error(message);
114
- }
115
- process.exit(code);
162
+ return exitWithError('unzip', exports.UNZIP_EXIT_CODES, code, message);
116
163
  }
117
164
  /**
118
165
  * Helper function to exit with a ZIPINFO exit code
119
166
  */
120
167
  function exitZipinfo(code, message) {
121
- if (message) {
122
- console.error(message);
123
- }
124
- process.exit(code);
168
+ return exitWithError('zipinfo', exports.ZIPINFO_EXIT_CODES, code, message);
125
169
  }
126
170
  /**
127
171
  * Get exit code description for documentation
@@ -49,11 +49,14 @@ const fs = __importStar(require("fs"));
49
49
  const node_1 = __importDefault(require("neozipkit/node"));
50
50
  const neozip_blockchain_1 = require("neozip-blockchain");
51
51
  const ots_1 = require("neozip-blockchain/ots");
52
- const zipstamp_server_1 = require("neozip-blockchain/zipstamp-server");
52
+ const token_service_1 = require("neozip-blockchain/token-service");
53
53
  const chalk_1 = __importDefault(require("chalk"));
54
54
  const ora_1 = __importDefault(require("ora"));
55
55
  const version_1 = require("./version");
56
56
  const exit_codes_1 = require("./exit-codes");
57
+ const output_1 = require("./cli/output");
58
+ const params_1 = require("./cli/params");
59
+ const validate_1 = require("./cli/validate");
57
60
  /**
58
61
  * Helper function to check if ZIP is file-based
59
62
  */
@@ -259,11 +262,11 @@ function formatEntryName(entry) {
259
262
  if (entry.filename === ots_1.TIMESTAMP_SUBMITTED) {
260
263
  return `${ots_1.TIMESTAMP_SUBMITTED} (OTS Pending)`;
261
264
  }
262
- if (entry.filename === zipstamp_server_1.SUBMIT_METADATA) {
263
- return `${zipstamp_server_1.SUBMIT_METADATA} (Zipstamp Pending)`;
265
+ if (entry.filename === token_service_1.SUBMIT_METADATA) {
266
+ return `${token_service_1.SUBMIT_METADATA} (Token Service Pending)`;
264
267
  }
265
- if (entry.filename === zipstamp_server_1.TIMESTAMP_METADATA) {
266
- return `${zipstamp_server_1.TIMESTAMP_METADATA} (Zipstamp Timestamp)`;
268
+ if (entry.filename === token_service_1.TIMESTAMP_METADATA) {
269
+ return `${token_service_1.TIMESTAMP_METADATA} (Token Service Timestamp)`;
267
270
  }
268
271
  return entry.filename;
269
272
  }
@@ -461,8 +464,11 @@ function outputVerbose(entries, archivePath, zip, options = { verbose: false, qu
461
464
  entries.forEach((entry, index) => {
462
465
  // Use ZipEntry's showVerboseInfo() method for detailed information
463
466
  log(`File ${index + 1}: ${formatEntryName(entry)}`, options);
464
- // Call showVerboseInfo() directly - it will output to console
465
467
  entry.showVerboseInfo();
468
+ if (entry.isEncrypted) {
469
+ const method = entry.aesVersion > 0 ? 'AES-256' : 'PKZIP';
470
+ log(` Encryption method: ${method}`, options);
471
+ }
466
472
  log('', options);
467
473
  });
468
474
  // Summary
@@ -715,6 +721,9 @@ function outputJson(entries, archivePath, zip, options = { verbose: false, quiet
715
721
  if (entry.isStrongEncrypt !== undefined) {
716
722
  fileInfo.isStrongEncrypt = entry.isStrongEncrypt;
717
723
  }
724
+ if (entry.isEncrypted) {
725
+ fileInfo.encryptionMethod = entry.aesVersion > 0 ? 'AES-256' : 'PKZIP';
726
+ }
718
727
  // Add symbolic link information if present
719
728
  if (entry.isSymlink !== undefined) {
720
729
  fileInfo.isSymlink = entry.isSymlink;
@@ -833,6 +842,44 @@ function parseArgs(args) {
833
842
  case '--json':
834
843
  options.json = true;
835
844
  break;
845
+ case '--format': {
846
+ const value = args[++i];
847
+ if (value === 'json') {
848
+ options.json = true;
849
+ }
850
+ else if (value !== 'text') {
851
+ console.error("Error: --format must be 'json' or 'text'");
852
+ showHelp();
853
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.INVALID_OPTIONS);
854
+ }
855
+ break;
856
+ }
857
+ case '--params': {
858
+ const value = args[++i];
859
+ if (value === undefined) {
860
+ console.error("Error: --params requires a JSON value (or '-' for stdin)");
861
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.INVALID_OPTIONS);
862
+ }
863
+ try {
864
+ const params = (0, params_1.parseJsonObjectArg)(value, '--params');
865
+ const allowed = [
866
+ 'verbose', 'quiet', 'json', 'short', 'unix', 'basic', 'include', 'exclude',
867
+ 'sortBy', 'noComments', 'debug', 'inMemory',
868
+ ];
869
+ for (const key of allowed) {
870
+ if (key in params) {
871
+ options[key] = params[key];
872
+ }
873
+ }
874
+ }
875
+ catch (err) {
876
+ if (err instanceof validate_1.ValidationError) {
877
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.INVALID_OPTIONS, `Error: ${err.message}`);
878
+ }
879
+ throw err;
880
+ }
881
+ break;
882
+ }
836
883
  case '-s':
837
884
  case '--short':
838
885
  options.short = true;
@@ -908,7 +955,39 @@ function parseArgs(args) {
908
955
  options.inMemory = true;
909
956
  break;
910
957
  default:
911
- if (arg.startsWith('-')) {
958
+ if (arg.startsWith('--format=')) {
959
+ const value = arg.slice('--format='.length);
960
+ if (value === 'json') {
961
+ options.json = true;
962
+ }
963
+ else if (value !== 'text') {
964
+ console.error("Error: --format must be 'json' or 'text'");
965
+ showHelp();
966
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.INVALID_OPTIONS);
967
+ }
968
+ }
969
+ else if (arg.startsWith('--params=')) {
970
+ const value = arg.slice('--params='.length);
971
+ try {
972
+ const params = (0, params_1.parseJsonObjectArg)(value, '--params');
973
+ const allowed = [
974
+ 'verbose', 'quiet', 'json', 'short', 'unix', 'basic', 'include', 'exclude',
975
+ 'sortBy', 'noComments', 'debug', 'inMemory',
976
+ ];
977
+ for (const key of allowed) {
978
+ if (key in params) {
979
+ options[key] = params[key];
980
+ }
981
+ }
982
+ }
983
+ catch (err) {
984
+ if (err instanceof validate_1.ValidationError) {
985
+ (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.INVALID_OPTIONS, `Error: ${err.message}`);
986
+ }
987
+ throw err;
988
+ }
989
+ }
990
+ else if (arg.startsWith('-')) {
912
991
  console.error(`Error: Unknown option ${arg}`);
913
992
  showHelp();
914
993
  (0, exit_codes_1.exitZipinfo)(exit_codes_1.ZIPINFO_EXIT_CODES.INVALID_OPTIONS);
@@ -934,6 +1013,8 @@ function parseArgs(args) {
934
1013
  * Show help information
935
1014
  */
936
1015
  function showHelp() {
1016
+ if ((0, output_1.isJsonOutput)())
1017
+ return;
937
1018
  console.log(`
938
1019
  NeoList Version: ${version_1.APP_VERSION} (${version_1.APP_RELEASE_DATE})
939
1020
 
@@ -944,7 +1025,9 @@ Options:
944
1025
  -V, --version Show version information
945
1026
  -v, --verbose Detailed central directory information
946
1027
  -q, --quiet Suppress output
947
- -j, --json Output in JSON format
1028
+ -j, --json Output in JSON format (alias for --format json)
1029
+ --format <fmt> Output format: text (default) or json (machine-readable)
1030
+ --params <json> Provide options as a JSON object (agent input; '-' = stdin)
948
1031
  -s, --short Short output (names only)
949
1032
  -u, --unix Unix-style output
950
1033
  -b, --basic Basic table output
@@ -972,6 +1055,8 @@ Examples:
972
1055
  * Show extended help information (equivalent to InfoZip's -h2)
973
1056
  */
974
1057
  function showExtendedHelp() {
1058
+ if ((0, output_1.isJsonOutput)())
1059
+ return;
975
1060
  console.log('Extended Help for NeoList');
976
1061
  console.log('');
977
1062
  console.log('NeoList is a next-generation ZIP listing utility that builds upon the strengths');
@@ -1089,6 +1174,11 @@ async function main() {
1089
1174
  try {
1090
1175
  // Parse command line arguments
1091
1176
  const args = process.argv.slice(2);
1177
+ // Resolve machine-readable output mode early so error exits are structured.
1178
+ // neolist's legacy -j/--json flag and the shared --format json both select JSON.
1179
+ if (args.includes('-j') || args.includes('--json') || (0, output_1.detectOutputFormat)(args) === 'json') {
1180
+ (0, output_1.setOutputFormat)('json');
1181
+ }
1092
1182
  const parsed = parseArgs(args);
1093
1183
  const { archive } = parsed;
1094
1184
  options = parsed.options;
@@ -1143,8 +1233,8 @@ async function main() {
1143
1233
  // Default is now the unzip -v format (verbose table)
1144
1234
  outputTable(entries, archive, true, zip, options);
1145
1235
  }
1146
- // Don't show success message for filenames-only modes
1147
- if (!options.opt1 && !options.opt2 && !options.optT) {
1236
+ // Don't show success message for filenames-only modes or JSON output
1237
+ if (!options.opt1 && !options.opt2 && !options.optT && !options.json) {
1148
1238
  log('✓ NeoList completed successfully', options);
1149
1239
  }
1150
1240
  // Close file handle if file-based ZIP was loaded