neozip-cli 0.90.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 +58 -1
- package/DOCUMENTATION.md +22 -30
- package/README.md +104 -42
- 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 +93 -9
- package/dist/src/neounzip.js +217 -59
- package/dist/src/neozip/blockchain.js +18 -18
- package/dist/src/neozip/createZip.js +114 -91
- package/dist/src/neozip/upgradeZip.js +14 -11
- package/dist/src/neozip.js +252 -75
- 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 -82
- 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
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Machine-readable schema introspection.
|
|
4
|
+
*
|
|
5
|
+
* Unlike a REST CLI with a Discovery document, NeoZip's commands are described
|
|
6
|
+
* by a hand-authored registry. `neozip schema <command>` emits the accepted
|
|
7
|
+
* `--params` fields, the `--json` payload shape, output shape, and exit codes so
|
|
8
|
+
* an agent can construct calls without scraping `--help`.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.getCommandSchema = getCommandSchema;
|
|
12
|
+
exports.listSchemaCommands = listSchemaCommands;
|
|
13
|
+
exports.handleSchemaCommand = handleSchemaCommand;
|
|
14
|
+
const NEOZIP_SCHEMA = {
|
|
15
|
+
command: 'neozip',
|
|
16
|
+
summary: 'Create or update an archive, optionally with blockchain tokenization.',
|
|
17
|
+
output: 'Human text by default. Pass --format json for a structured result. --dry-run previews without writing.',
|
|
18
|
+
params: [
|
|
19
|
+
{ name: 'level', type: 'number', description: 'Compression level 0-9 (0=store).', default: 6, legacySafe: true },
|
|
20
|
+
{ name: 'compression', type: 'string', enum: ['zstd', 'deflate'], description: 'Compression algorithm.', default: 'zstd', legacySafe: false },
|
|
21
|
+
{ name: 'legacy', type: 'boolean', description: 'Produce an InfoZip/PKZIP-compatible archive (forces deflate/store + PKZIP, disables NeoZip extensions).', default: false, legacySafe: true },
|
|
22
|
+
{ name: 'encrypt', type: 'boolean', description: 'Encrypt entries (prompts for password unless password is given).', default: false, legacySafe: true },
|
|
23
|
+
{ name: 'password', type: 'string', description: 'Encryption password. Use with care (visible in process list).', legacySafe: true },
|
|
24
|
+
{ name: 'encryptionMethod', type: 'string', enum: ['aes256', 'pkzip'], description: 'Encryption method. --legacy forces pkzip.', default: 'aes256', legacySafe: false },
|
|
25
|
+
{ name: 'blockchain', type: 'boolean', description: 'Enable blockchain tokenization. Incompatible with legacy.', default: false, legacySafe: false },
|
|
26
|
+
{ name: 'blockchainOts', type: 'boolean', description: 'Add an OpenTimestamps (Bitcoin) proof. Incompatible with legacy.', default: false, legacySafe: false },
|
|
27
|
+
{ name: 'blockchainTokenService', type: 'boolean', description: 'Add a Token Service timestamp (requires neozip connect). Incompatible with legacy.', default: false, legacySafe: false },
|
|
28
|
+
{ name: 'network', type: 'string', description: 'Blockchain network name (e.g. base-sepolia).', default: 'base-sepolia', legacySafe: false },
|
|
29
|
+
{ name: 'recurse', type: 'boolean', description: 'Recurse into directories.', default: false, legacySafe: true },
|
|
30
|
+
{ name: 'junkPaths', type: 'boolean', description: 'Store only file names, not directory paths.', default: false, legacySafe: true },
|
|
31
|
+
{ name: 'include', type: 'string[]', description: 'Glob patterns to include.', legacySafe: true },
|
|
32
|
+
{ name: 'exclude', type: 'string[]', description: 'Glob patterns to exclude.', legacySafe: true },
|
|
33
|
+
{ name: 'inMemory', type: 'boolean', description: 'Use in-memory compression.', default: false, legacySafe: true },
|
|
34
|
+
{ name: 'testIntegrity', type: 'boolean', description: 'Test the archive after creation.', default: false, legacySafe: true },
|
|
35
|
+
{ name: 'verbose', type: 'boolean', description: 'Verbose output.', default: false, legacySafe: true },
|
|
36
|
+
{ name: 'quiet', type: 'boolean', description: 'Suppress output.', default: false, legacySafe: true },
|
|
37
|
+
],
|
|
38
|
+
payload: [
|
|
39
|
+
{ name: 'archive', type: 'string', description: 'Output archive path (positional arg 1).' },
|
|
40
|
+
{ name: 'files', type: 'string[]', description: 'Files/directories to add (positional args 2+).' },
|
|
41
|
+
{ name: 'include', type: 'string[]', description: 'Glob patterns to include.' },
|
|
42
|
+
{ name: 'exclude', type: 'string[]', description: 'Glob patterns to exclude.' },
|
|
43
|
+
],
|
|
44
|
+
exitCodes: [
|
|
45
|
+
{ code: 0, meaning: 'Success' },
|
|
46
|
+
{ code: 10, meaning: 'Parameter error' },
|
|
47
|
+
{ code: 15, meaning: "Can't write archive" },
|
|
48
|
+
{ code: 18, meaning: "Can't find archive" },
|
|
49
|
+
{ code: 26, meaning: 'Blockchain configuration error' },
|
|
50
|
+
],
|
|
51
|
+
notes: [
|
|
52
|
+
'Output format is selected with --format json (not --json). --json supplies the payload.',
|
|
53
|
+
'Example: neozip --format json --params \'{"level":9,"legacy":true}\' --json \'{"files":["a.txt","dir/"]}\' out.zip',
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
const NEOUNZIP_SCHEMA = {
|
|
57
|
+
command: 'neounzip',
|
|
58
|
+
summary: 'Extract, test, or list an archive.',
|
|
59
|
+
output: 'Human text by default. Pass --format json for a structured result. --dry-run previews the extraction plan.',
|
|
60
|
+
params: [
|
|
61
|
+
{ name: 'exdir', type: 'string', description: 'Extraction directory (zip-slip validated).' },
|
|
62
|
+
{ name: 'overwrite', type: 'boolean', description: 'Overwrite existing files.', default: false },
|
|
63
|
+
{ name: 'never', type: 'boolean', description: 'Never overwrite existing files.', default: false },
|
|
64
|
+
{ name: 'junkPaths', type: 'boolean', description: 'Strip directory paths on extraction.', default: false },
|
|
65
|
+
{ name: 'testOnly', type: 'boolean', description: 'Test archive integrity only.', default: false },
|
|
66
|
+
{ name: 'listOnly', type: 'boolean', description: 'List archive contents only.', default: false },
|
|
67
|
+
{ name: 'preVerify', type: 'boolean', description: 'Verify integrity/blockchain before extracting.', default: false },
|
|
68
|
+
{ name: 'password', type: 'string', description: 'Decryption password.' },
|
|
69
|
+
{ name: 'include', type: 'string[]', description: 'Glob patterns to include.' },
|
|
70
|
+
{ name: 'exclude', type: 'string[]', description: 'Glob patterns to exclude.' },
|
|
71
|
+
{ name: 'skipBlockchain', type: 'boolean', description: 'Skip blockchain verification.', default: false },
|
|
72
|
+
{ name: 'inMemory', type: 'boolean', description: 'Use in-memory processing.', default: false },
|
|
73
|
+
{ name: 'verbose', type: 'boolean', description: 'Verbose output.', default: false },
|
|
74
|
+
{ name: 'quiet', type: 'boolean', description: 'Suppress output.', default: false },
|
|
75
|
+
],
|
|
76
|
+
payload: [
|
|
77
|
+
{ name: 'archive', type: 'string', description: 'Archive path (positional arg 1).' },
|
|
78
|
+
{ name: 'exdir', type: 'string', description: 'Extraction directory (positional arg 2 or exdir).' },
|
|
79
|
+
{ name: 'filePatterns', type: 'string[]', description: 'Entry name patterns to extract (positional args 3+).' },
|
|
80
|
+
],
|
|
81
|
+
exitCodes: [
|
|
82
|
+
{ code: 0, meaning: 'Success' },
|
|
83
|
+
{ code: 1, meaning: 'Warning (completed with issues)' },
|
|
84
|
+
{ code: 9, meaning: 'Files not found' },
|
|
85
|
+
{ code: 10, meaning: 'Invalid options' },
|
|
86
|
+
{ code: 82, meaning: 'Bad password' },
|
|
87
|
+
],
|
|
88
|
+
notes: [
|
|
89
|
+
'Extraction refuses entries that escape the target directory (zip-slip protection).',
|
|
90
|
+
'Example: neounzip --format json --params \'{"exdir":"out","overwrite":true}\' archive.zip',
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
const NEOLIST_SCHEMA = {
|
|
94
|
+
command: 'neolist',
|
|
95
|
+
summary: 'List the contents of an archive.',
|
|
96
|
+
output: 'Human text by default. Pass --format json (or the legacy -j/--json) for JSON.',
|
|
97
|
+
params: [
|
|
98
|
+
{ name: 'sortBy', type: 'string', enum: ['name', 'size', 'date'], description: 'Sort order.' },
|
|
99
|
+
{ name: 'include', type: 'string[]', description: 'Glob patterns to include.' },
|
|
100
|
+
{ name: 'exclude', type: 'string[]', description: 'Glob patterns to exclude.' },
|
|
101
|
+
{ name: 'noComments', type: 'boolean', description: 'Suppress comments.', default: false },
|
|
102
|
+
{ name: 'inMemory', type: 'boolean', description: 'Use in-memory processing.', default: false },
|
|
103
|
+
{ name: 'verbose', type: 'boolean', description: 'Verbose output.', default: false },
|
|
104
|
+
],
|
|
105
|
+
exitCodes: [
|
|
106
|
+
{ code: 0, meaning: 'Success' },
|
|
107
|
+
{ code: 9, meaning: 'Files not found' },
|
|
108
|
+
{ code: 10, meaning: 'Invalid options' },
|
|
109
|
+
{ code: 82, meaning: 'Password-protected (cannot list)' },
|
|
110
|
+
],
|
|
111
|
+
notes: ['Example: neolist --format json archive.zip'],
|
|
112
|
+
};
|
|
113
|
+
const CONNECT_SCHEMA = {
|
|
114
|
+
command: 'connect',
|
|
115
|
+
summary: 'Token Service account + wallet setup. Shares ~/.neozip/connection/ with NeoZip Desktop and MCP.',
|
|
116
|
+
output: 'Pass --json on any subcommand for a structured ConnectCliResult.',
|
|
117
|
+
params: [
|
|
118
|
+
{ name: 'email', type: 'string', description: 'Account email for register/verify.' },
|
|
119
|
+
{ name: 'code', type: 'string', description: 'Verification code.' },
|
|
120
|
+
{ name: 'phone', type: 'string', description: 'E.164 phone number for phone verification.' },
|
|
121
|
+
{ name: 'connectionId', type: 'string', description: 'Target a specific connection.' },
|
|
122
|
+
{ name: 'tokenServiceUrl', type: 'string', description: 'Override the Token Service URL.' },
|
|
123
|
+
{ name: 'label', type: 'string', description: 'Friendly label for the connection.' },
|
|
124
|
+
{ name: 'network', type: 'string', description: 'Network name for connect fund (e.g. base-sepolia).' },
|
|
125
|
+
{ name: 'chain', type: 'number', description: 'Chain id for connect fund (overrides --network).' },
|
|
126
|
+
],
|
|
127
|
+
exitCodes: [
|
|
128
|
+
{ code: 0, meaning: 'Success' },
|
|
129
|
+
{ code: 12, meaning: 'Command failed' },
|
|
130
|
+
],
|
|
131
|
+
notes: [
|
|
132
|
+
'Subcommands: status, register, verify, refresh, fund, phone, wallet, finish, select, rename, dump, settings, migrate, logout, delete, reset.',
|
|
133
|
+
'connect refresh renews an expired access token (silent wallet-signature login, else email OTP).',
|
|
134
|
+
'connect fund [request|status|policy] requests native gas for the Data Wallet from the Token Service funding API.',
|
|
135
|
+
'Non-interactive subcommands accept --json for structured output. The bare wizard requires a TTY.',
|
|
136
|
+
],
|
|
137
|
+
};
|
|
138
|
+
const REGISTRY = {
|
|
139
|
+
neozip: NEOZIP_SCHEMA,
|
|
140
|
+
zip: NEOZIP_SCHEMA,
|
|
141
|
+
neounzip: NEOUNZIP_SCHEMA,
|
|
142
|
+
unzip: NEOUNZIP_SCHEMA,
|
|
143
|
+
neolist: NEOLIST_SCHEMA,
|
|
144
|
+
list: NEOLIST_SCHEMA,
|
|
145
|
+
connect: CONNECT_SCHEMA,
|
|
146
|
+
};
|
|
147
|
+
/** Look up a command schema by name (case-insensitive, with aliases). */
|
|
148
|
+
function getCommandSchema(command) {
|
|
149
|
+
return REGISTRY[command.toLowerCase()];
|
|
150
|
+
}
|
|
151
|
+
/** List the canonical introspectable command names. */
|
|
152
|
+
function listSchemaCommands() {
|
|
153
|
+
return ['neozip', 'neounzip', 'neolist', 'connect'];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Handle `neozip schema [command]`. Prints JSON to stdout. Returns the process
|
|
157
|
+
* exit code (0 on success, 10 on a bad command name).
|
|
158
|
+
*/
|
|
159
|
+
function handleSchemaCommand(args) {
|
|
160
|
+
const target = args[0];
|
|
161
|
+
if (!target) {
|
|
162
|
+
// No command given: emit an index of available commands.
|
|
163
|
+
const index = {
|
|
164
|
+
ok: true,
|
|
165
|
+
commands: listSchemaCommands(),
|
|
166
|
+
usage: 'neozip schema <neozip|neounzip|neolist|connect>',
|
|
167
|
+
};
|
|
168
|
+
process.stdout.write(JSON.stringify(index, null, 2) + '\n');
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
const schema = getCommandSchema(target);
|
|
172
|
+
if (!schema) {
|
|
173
|
+
process.stdout.write(JSON.stringify({
|
|
174
|
+
ok: false,
|
|
175
|
+
error: {
|
|
176
|
+
code: 'UNKNOWN_COMMAND',
|
|
177
|
+
message: `Unknown command "${target}"`,
|
|
178
|
+
commands: listSchemaCommands(),
|
|
179
|
+
},
|
|
180
|
+
}, null, 2) + '\n');
|
|
181
|
+
return 10;
|
|
182
|
+
}
|
|
183
|
+
process.stdout.write(JSON.stringify({ ok: true, schema }, null, 2) + '\n');
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Input hardening for agent-driven (and adversarial) input.
|
|
4
|
+
*
|
|
5
|
+
* Agents hallucinate. Treat all CLI/JSON-supplied strings as untrusted:
|
|
6
|
+
* - Reject control characters and dangerous Unicode (zero-width, bidi
|
|
7
|
+
* overrides) that can hide intent in file names and paths.
|
|
8
|
+
* - Prevent zip-slip: extracted entries must resolve inside the target dir.
|
|
9
|
+
*
|
|
10
|
+
* Adapted from the patterns in the Google Workspace CLI `validate.rs`.
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.ValidationError = void 0;
|
|
47
|
+
exports.rejectControlChars = rejectControlChars;
|
|
48
|
+
exports.assertSafeArchiveName = assertSafeArchiveName;
|
|
49
|
+
exports.validateSafeOutputDir = validateSafeOutputDir;
|
|
50
|
+
exports.resolveSafeExtractPath = resolveSafeExtractPath;
|
|
51
|
+
const path = __importStar(require("path"));
|
|
52
|
+
/** Raised when input fails a hardening check. */
|
|
53
|
+
class ValidationError extends Error {
|
|
54
|
+
constructor(message) {
|
|
55
|
+
super(message);
|
|
56
|
+
this.name = 'ValidationError';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
exports.ValidationError = ValidationError;
|
|
60
|
+
// C0 controls (0x00-0x1F), DEL (0x7F), and C1 controls (0x80-0x9F).
|
|
61
|
+
const CONTROL_CHARS = /[\u0000-\u001F\u007F-\u009F]/;
|
|
62
|
+
// Zero-width and bidirectional-override characters that can disguise paths.
|
|
63
|
+
const DANGEROUS_UNICODE = /[\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF]/;
|
|
64
|
+
/**
|
|
65
|
+
* Reject control characters and dangerous Unicode in a value. Returns the value
|
|
66
|
+
* unchanged when it is safe.
|
|
67
|
+
*/
|
|
68
|
+
function rejectControlChars(value, label) {
|
|
69
|
+
if (CONTROL_CHARS.test(value)) {
|
|
70
|
+
throw new ValidationError(`${label} contains control characters`);
|
|
71
|
+
}
|
|
72
|
+
if (DANGEROUS_UNICODE.test(value)) {
|
|
73
|
+
throw new ValidationError(`${label} contains disallowed Unicode characters`);
|
|
74
|
+
}
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Validate an archive name / file path for control characters. Does not enforce
|
|
79
|
+
* a sandbox (archives may legitimately live anywhere the user can write).
|
|
80
|
+
*/
|
|
81
|
+
function assertSafeArchiveName(name) {
|
|
82
|
+
return rejectControlChars(name, 'Archive name');
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Validate an output directory string. Rejects control chars and returns the
|
|
86
|
+
* resolved absolute path.
|
|
87
|
+
*/
|
|
88
|
+
function validateSafeOutputDir(dir, label = 'Output directory') {
|
|
89
|
+
rejectControlChars(dir, label);
|
|
90
|
+
return path.resolve(dir);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Resolve an archive entry name against a target extraction directory and ensure
|
|
94
|
+
* the result stays inside that directory (zip-slip protection).
|
|
95
|
+
*
|
|
96
|
+
* - Rejects control characters / dangerous Unicode in the entry name.
|
|
97
|
+
* - Rejects absolute entry paths.
|
|
98
|
+
* - Rejects `..` traversal that escapes the target directory.
|
|
99
|
+
*
|
|
100
|
+
* Returns the safe absolute destination path for the entry.
|
|
101
|
+
*/
|
|
102
|
+
function resolveSafeExtractPath(targetDir, entryName) {
|
|
103
|
+
rejectControlChars(entryName, `Entry "${entryName}"`);
|
|
104
|
+
// Normalize Windows separators so checks are consistent cross-platform.
|
|
105
|
+
const normalizedEntry = entryName.replace(/\\/g, '/');
|
|
106
|
+
if (path.isAbsolute(normalizedEntry) || /^[A-Za-z]:[\\/]/.test(normalizedEntry)) {
|
|
107
|
+
throw new ValidationError(`Entry "${entryName}" uses an absolute path (refusing to extract)`);
|
|
108
|
+
}
|
|
109
|
+
const resolvedTarget = path.resolve(targetDir);
|
|
110
|
+
const destination = path.resolve(resolvedTarget, normalizedEntry);
|
|
111
|
+
const withSep = resolvedTarget.endsWith(path.sep)
|
|
112
|
+
? resolvedTarget
|
|
113
|
+
: resolvedTarget + path.sep;
|
|
114
|
+
if (destination !== resolvedTarget && !destination.startsWith(withSep)) {
|
|
115
|
+
throw new ValidationError(`Entry "${entryName}" escapes the extraction directory (path traversal)`);
|
|
116
|
+
}
|
|
117
|
+
return destination;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -48,9 +48,11 @@ const os = __importStar(require("os"));
|
|
|
48
48
|
const ethers_1 = require("ethers");
|
|
49
49
|
const node_1 = __importDefault(require("neozipkit/node"));
|
|
50
50
|
const node_2 = require("neozipkit/node");
|
|
51
|
-
const
|
|
51
|
+
const token_service_1 = require("neozip-blockchain/token-service");
|
|
52
52
|
const neozip_blockchain_1 = require("neozip-blockchain");
|
|
53
|
-
const
|
|
53
|
+
const cli_guidance_1 = require("../connection/cli-guidance");
|
|
54
|
+
const funding_1 = require("../connection/funding");
|
|
55
|
+
const bootstrap_1 = require("../connection/bootstrap");
|
|
54
56
|
const RPC_BY_CHAIN = {
|
|
55
57
|
84532: 'https://sepolia.base.org',
|
|
56
58
|
421614: 'https://sepolia-rollup.arbitrum.io/rpc',
|
|
@@ -78,27 +80,34 @@ function getErrorMessage(error) {
|
|
|
78
80
|
*/
|
|
79
81
|
async function runMintTimestampProof(options) {
|
|
80
82
|
const { inputPath, outputPath, walletKey: cliWalletKey, debug = false } = options;
|
|
83
|
+
const walletGuidance = (0, cli_guidance_1.requireConnectionReady)('On-chain mint', 'wallet');
|
|
84
|
+
if (walletGuidance) {
|
|
85
|
+
console.error(walletGuidance);
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
81
88
|
if (!fs.existsSync(inputPath)) {
|
|
82
89
|
console.error(`Error: ZIP file not found: ${inputPath}`);
|
|
83
90
|
return false;
|
|
84
91
|
}
|
|
85
|
-
const
|
|
92
|
+
const creds = (0, bootstrap_1.readCliConnectionCredentials)();
|
|
93
|
+
const walletKey = cliWalletKey || creds.walletKey;
|
|
86
94
|
if (!walletKey) {
|
|
95
|
+
console.error('Error: No wallet key in connection store. Run: neozip connect');
|
|
87
96
|
return false;
|
|
88
97
|
}
|
|
89
98
|
const zip = new node_1.default();
|
|
90
99
|
await zip.loadZipFile(inputPath);
|
|
91
100
|
const entries = zip.getDirectory() || [];
|
|
92
|
-
const metadataResult = (0,
|
|
101
|
+
const metadataResult = (0, token_service_1.findMetadataEntry)(entries);
|
|
93
102
|
if (!metadataResult) {
|
|
94
|
-
console.error('No
|
|
103
|
+
console.error('No Token Service timestamp found in ZIP file');
|
|
95
104
|
return false;
|
|
96
105
|
}
|
|
97
106
|
if (metadataResult.type !== 'confirmed') {
|
|
98
107
|
console.error('Archive must have confirmed timestamp (TIMESTAMP.NZIP). Run "neozip upgrade <archive>" first.');
|
|
99
108
|
return false;
|
|
100
109
|
}
|
|
101
|
-
let timestampMetadata = await (0,
|
|
110
|
+
let timestampMetadata = await (0, token_service_1.extractTimestampData)(zip, metadataResult.entry);
|
|
102
111
|
if (!timestampMetadata || !timestampMetadata.digest) {
|
|
103
112
|
// Fallback for file-based ZIPs where zip.extract() may not work
|
|
104
113
|
try {
|
|
@@ -123,14 +132,16 @@ async function runMintTimestampProof(options) {
|
|
|
123
132
|
}
|
|
124
133
|
const chainId = timestampMetadata.chainId ?? 84532;
|
|
125
134
|
const batchId = timestampMetadata.batchId ?? undefined;
|
|
135
|
+
const serverUrl = (0, bootstrap_1.resolveVerifyServerUrl)(timestampMetadata.serverUrl);
|
|
136
|
+
const tsOpts = { debug, serverUrl };
|
|
126
137
|
console.log('Checking NFT status...');
|
|
127
|
-
const nftStatus = await (0,
|
|
138
|
+
const nftStatus = await (0, token_service_1.checkNFTStatus)(timestampMetadata.digest, chainId, tsOpts);
|
|
128
139
|
if (nftStatus.isMinted && nftStatus.tokenId) {
|
|
129
140
|
console.log(`Digest already minted as token #${nftStatus.tokenId}`);
|
|
130
141
|
console.log('Proceeding to create TOKEN.NZIP with existing token data...');
|
|
131
142
|
}
|
|
132
143
|
console.log('Preparing mint data...');
|
|
133
|
-
const prepareResult = await (0,
|
|
144
|
+
const prepareResult = await (0, token_service_1.prepareMint)(timestampMetadata.digest, chainId, batchId, tsOpts);
|
|
134
145
|
if (!prepareResult.success || !prepareResult.mintData) {
|
|
135
146
|
console.error(`Error: ${prepareResult.error || 'Failed to prepare mint data'}`);
|
|
136
147
|
return false;
|
|
@@ -159,6 +170,7 @@ async function runMintTimestampProof(options) {
|
|
|
159
170
|
const mintFeeWei = BigInt(mintData.mintFeeWei);
|
|
160
171
|
if (balance < mintFeeWei) {
|
|
161
172
|
console.error(`Error: Insufficient balance. Required: ${mintData.mintFee} ETH`);
|
|
173
|
+
console.error(` ${(0, funding_1.formatLowBalanceFundingHint)()}`);
|
|
162
174
|
return false;
|
|
163
175
|
}
|
|
164
176
|
const nftContract = new ethers_1.ethers.Contract(mintData.nftContractAddress, neozip_blockchain_1.NZIP_CONTRACT_ABI_V250, wallet);
|
|
@@ -197,14 +209,14 @@ async function runMintTimestampProof(options) {
|
|
|
197
209
|
let existingTokenId;
|
|
198
210
|
let existingOwner;
|
|
199
211
|
let existingBlockNumber;
|
|
200
|
-
const existingStatus = await (0,
|
|
212
|
+
const existingStatus = await (0, token_service_1.checkNFTStatus)(timestampMetadata.digest, chainId, tsOpts);
|
|
201
213
|
if (existingStatus.isMinted && existingStatus.tokenId) {
|
|
202
214
|
existingTokenId = existingStatus.tokenId;
|
|
203
215
|
existingOwner = existingStatus.owner;
|
|
204
216
|
existingBlockNumber = existingStatus.proofData?.batchBlockNumber;
|
|
205
217
|
}
|
|
206
218
|
if (!existingTokenId) {
|
|
207
|
-
const verifyRes = await (0,
|
|
219
|
+
const verifyRes = await (0, token_service_1.verifyDigest)(timestampMetadata.digest, chainId, batchId ?? undefined, undefined, tsOpts);
|
|
208
220
|
if (verifyRes.verified && verifyRes.tokenId) {
|
|
209
221
|
existingTokenId = verifyRes.tokenId;
|
|
210
222
|
existingOwner = verifyRes.owner;
|
|
@@ -219,7 +231,7 @@ async function runMintTimestampProof(options) {
|
|
|
219
231
|
console.log(`Using existing token #${tokenId}. Creating TOKEN.NZIP...`);
|
|
220
232
|
}
|
|
221
233
|
else {
|
|
222
|
-
console.error('Mint failed: digest is already minted on-chain but token details could not be retrieved. Try the
|
|
234
|
+
console.error('Mint failed: digest is already minted on-chain but token details could not be retrieved. Try the Token Service or block explorer for this digest.');
|
|
223
235
|
console.error(`Details: ${msg}`);
|
|
224
236
|
return false;
|
|
225
237
|
}
|
|
@@ -251,10 +263,10 @@ async function runMintTimestampProof(options) {
|
|
|
251
263
|
batchTimestamp: mintData.batchTimestamp,
|
|
252
264
|
registryAddress: mintData.registryAddress,
|
|
253
265
|
nftContractAddress: mintData.nftContractAddress,
|
|
254
|
-
serverUrl:
|
|
266
|
+
serverUrl: serverUrl ?? '',
|
|
255
267
|
},
|
|
256
268
|
};
|
|
257
|
-
const metadataFiles = (0,
|
|
269
|
+
const metadataFiles = (0, token_service_1.getMetadataFileNames)();
|
|
258
270
|
const outPath = outputPath ?? inputPath.replace(/(\.nzip|\.zip)$/i, '-nft$1');
|
|
259
271
|
const tempDir = path.dirname(outPath) || '.';
|
|
260
272
|
const tempPath = path.join(tempDir, `.neozip-mint-${Date.now()}.tmp`);
|
|
@@ -286,7 +298,7 @@ async function runMintTimestampProof(options) {
|
|
|
286
298
|
zipCopy.writeCentralDirectoryAndEOCD(destPath, allEntries, { zipComment: '' });
|
|
287
299
|
fs.renameSync(destPath, outPath);
|
|
288
300
|
console.log(`Output: ${outPath}`);
|
|
289
|
-
// Summary matching
|
|
301
|
+
// Summary matching Token Service verification style
|
|
290
302
|
console.log('\nš Mint complete:');
|
|
291
303
|
console.log(' - Status: ā
MINTED');
|
|
292
304
|
console.log(' - Stamp type: NZIP-NFT (tokenized archive)');
|