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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.spkiBase64ToRawHex = spkiBase64ToRawHex;
|
|
4
|
+
exports.resolveTokenServiceBaseUrl = resolveTokenServiceBaseUrl;
|
|
5
|
+
exports.lookupRecipientByEmail = lookupRecipientByEmail;
|
|
6
|
+
exports.resolveRecipientsFromEmails = resolveRecipientsFromEmails;
|
|
7
|
+
const token_service_1 = require("neozip-blockchain/token-service");
|
|
8
|
+
const wallet_identity_1 = require("../constants/wallet-identity");
|
|
9
|
+
function spkiBase64ToRawHex(spkiBase64) {
|
|
10
|
+
const spkiDer = Buffer.from(spkiBase64, "base64");
|
|
11
|
+
if (spkiDer.length !== 44) {
|
|
12
|
+
throw new Error(`Unexpected SPKI DER length ${spkiDer.length}, expected 44 bytes for X25519`);
|
|
13
|
+
}
|
|
14
|
+
return spkiDer.subarray(12).toString("hex");
|
|
15
|
+
}
|
|
16
|
+
function resolveTokenServiceBaseUrl(config, serverUrl) {
|
|
17
|
+
return (0, token_service_1.getTokenServiceUrl)({
|
|
18
|
+
serverUrl: serverUrl || config.tokenServiceUrl || undefined,
|
|
19
|
+
}).replace(/\/+$/, "");
|
|
20
|
+
}
|
|
21
|
+
async function lookupRecipientByEmail(email, config, options) {
|
|
22
|
+
const trimmed = email.trim().toLowerCase();
|
|
23
|
+
if (!trimmed.includes("@")) {
|
|
24
|
+
throw new Error(`Invalid recipient email: ${email}`);
|
|
25
|
+
}
|
|
26
|
+
const baseUrl = resolveTokenServiceBaseUrl(config, options?.serverUrl);
|
|
27
|
+
const url = `${baseUrl}/crypto/public?email=${encodeURIComponent(trimmed)}`;
|
|
28
|
+
const response = await fetch(url, { method: "GET" });
|
|
29
|
+
const data = (await response.json());
|
|
30
|
+
if (!response.ok || !data.success || !data.crypto?.x25519PublicKey) {
|
|
31
|
+
throw new Error(data.error ||
|
|
32
|
+
`No public key found for ${trimmed} (HTTP ${response.status})`);
|
|
33
|
+
}
|
|
34
|
+
const publicKeyHex = spkiBase64ToRawHex(data.crypto.x25519PublicKey);
|
|
35
|
+
return {
|
|
36
|
+
identity: {
|
|
37
|
+
identity: trimmed,
|
|
38
|
+
identityType: wallet_identity_1.TOKEN_SERVICE_RECIPIENT_IDENTITY_TYPE,
|
|
39
|
+
address: data.crypto.evmAddress || "",
|
|
40
|
+
publicKeyHex,
|
|
41
|
+
keyFormat: "x25519",
|
|
42
|
+
},
|
|
43
|
+
evmAddress: data.crypto.evmAddress || "",
|
|
44
|
+
recipientSuites: data.crypto.recipientSuites,
|
|
45
|
+
cryptoProvisionedAt: data.crypto.cryptoProvisionedAt,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
async function resolveRecipientsFromEmails(emails, config, options) {
|
|
49
|
+
const seen = new Set();
|
|
50
|
+
const recipients = [];
|
|
51
|
+
for (const email of emails) {
|
|
52
|
+
const normalized = email.trim().toLowerCase();
|
|
53
|
+
if (seen.has(normalized))
|
|
54
|
+
continue;
|
|
55
|
+
seen.add(normalized);
|
|
56
|
+
const { identity } = await lookupRecipientByEmail(normalized, config, options);
|
|
57
|
+
recipients.push(identity);
|
|
58
|
+
}
|
|
59
|
+
return recipients;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=recipient-lookup.js.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared output layer for agent-friendly machine-readable output.
|
|
4
|
+
*
|
|
5
|
+
* Design rules (see dev_doc / AGENTS.md):
|
|
6
|
+
* - Human text is the default. JSON is opt-in via `--format json` (or, on
|
|
7
|
+
* neolist, the legacy `-j`/`--json` flag).
|
|
8
|
+
* - Structured results and structured errors are written to STDOUT as a single
|
|
9
|
+
* JSON object so a caller can parse stdout directly.
|
|
10
|
+
* - Human hints, progress, and decorative logs go to STDERR (or are suppressed)
|
|
11
|
+
* so they never corrupt JSON on stdout.
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.setOutputFormat = setOutputFormat;
|
|
15
|
+
exports.getOutputFormat = getOutputFormat;
|
|
16
|
+
exports.isJsonOutput = isJsonOutput;
|
|
17
|
+
exports.detectOutputFormat = detectOutputFormat;
|
|
18
|
+
exports.stripFormatFlag = stripFormatFlag;
|
|
19
|
+
exports.emitResult = emitResult;
|
|
20
|
+
exports.emitError = emitError;
|
|
21
|
+
let currentFormat = 'text';
|
|
22
|
+
/** Set the process-wide output format (call once, early, from each binary). */
|
|
23
|
+
function setOutputFormat(format) {
|
|
24
|
+
currentFormat = format;
|
|
25
|
+
}
|
|
26
|
+
/** Get the current process-wide output format. */
|
|
27
|
+
function getOutputFormat() {
|
|
28
|
+
return currentFormat;
|
|
29
|
+
}
|
|
30
|
+
/** Convenience predicate: are we emitting machine-readable JSON? */
|
|
31
|
+
function isJsonOutput() {
|
|
32
|
+
return currentFormat === 'json';
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Detect the desired output format from raw argv.
|
|
36
|
+
*
|
|
37
|
+
* Recognizes `--format json|text` and `--format=json|text`. The bare `--json`
|
|
38
|
+
* flag is intentionally NOT treated as an output switch here because on neozip /
|
|
39
|
+
* neounzip `--json` carries an input payload value. neolist handles its own
|
|
40
|
+
* legacy `--json`/`-j` output toggle and maps it to setOutputFormat('json').
|
|
41
|
+
*/
|
|
42
|
+
function detectOutputFormat(argv) {
|
|
43
|
+
for (let i = 0; i < argv.length; i++) {
|
|
44
|
+
const arg = argv[i];
|
|
45
|
+
if (arg === '--format') {
|
|
46
|
+
const value = argv[i + 1];
|
|
47
|
+
if (value === 'json')
|
|
48
|
+
return 'json';
|
|
49
|
+
if (value === 'text')
|
|
50
|
+
return 'text';
|
|
51
|
+
}
|
|
52
|
+
else if (arg.startsWith('--format=')) {
|
|
53
|
+
const value = arg.slice('--format='.length);
|
|
54
|
+
if (value === 'json')
|
|
55
|
+
return 'json';
|
|
56
|
+
if (value === 'text')
|
|
57
|
+
return 'text';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return 'text';
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Remove `--format <value>` / `--format=<value>` tokens from argv so the
|
|
64
|
+
* existing per-binary parsers don't have to know about them.
|
|
65
|
+
*/
|
|
66
|
+
function stripFormatFlag(argv) {
|
|
67
|
+
const result = [];
|
|
68
|
+
for (let i = 0; i < argv.length; i++) {
|
|
69
|
+
const arg = argv[i];
|
|
70
|
+
if (arg === '--format') {
|
|
71
|
+
// Skip the flag and its value.
|
|
72
|
+
i++;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (arg.startsWith('--format=')) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
result.push(arg);
|
|
79
|
+
}
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Emit a successful structured result as pretty JSON on stdout.
|
|
84
|
+
*
|
|
85
|
+
* Writes directly to process.stdout so it bypasses any `quiet` handling used to
|
|
86
|
+
* silence decorative human logs in JSON mode.
|
|
87
|
+
*/
|
|
88
|
+
function emitResult(result) {
|
|
89
|
+
const payload = { ok: true, ...result };
|
|
90
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Emit a structured error as pretty JSON on stdout. Used by the exit-code
|
|
94
|
+
* helpers when running in JSON mode.
|
|
95
|
+
*/
|
|
96
|
+
function emitError(error) {
|
|
97
|
+
const payload = { ok: false, error };
|
|
98
|
+
process.stdout.write(JSON.stringify(payload, null, 2) + '\n');
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=output.js.map
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Agent input parsing: `--params` and `--json`.
|
|
4
|
+
*
|
|
5
|
+
* - `--params '<JSON>'` control/options object (the "how": level, encrypt,
|
|
6
|
+
* blockchain, network, output dir, overwrite policy, ...). Maps onto the
|
|
7
|
+
* binary's options object.
|
|
8
|
+
* - `--json '<JSON>'` payload object (the "what": archive + inputs/targets,
|
|
9
|
+
* e.g. {archive, files, include, exclude, entries, exdir}). Maps onto
|
|
10
|
+
* positional arguments.
|
|
11
|
+
*
|
|
12
|
+
* Either flag accepts `-` as its value to read the JSON document from stdin.
|
|
13
|
+
*
|
|
14
|
+
* Precedence: values from `--params`/`--json` seed the option/positional state
|
|
15
|
+
* BEFORE the normal flag loop runs, so explicit CLI flags always win.
|
|
16
|
+
*/
|
|
17
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
+
}
|
|
23
|
+
Object.defineProperty(o, k2, desc);
|
|
24
|
+
}) : (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
o[k2] = m[k];
|
|
27
|
+
}));
|
|
28
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
+
}) : function(o, v) {
|
|
31
|
+
o["default"] = v;
|
|
32
|
+
});
|
|
33
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
34
|
+
var ownKeys = function(o) {
|
|
35
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
36
|
+
var ar = [];
|
|
37
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
38
|
+
return ar;
|
|
39
|
+
};
|
|
40
|
+
return ownKeys(o);
|
|
41
|
+
};
|
|
42
|
+
return function (mod) {
|
|
43
|
+
if (mod && mod.__esModule) return mod;
|
|
44
|
+
var result = {};
|
|
45
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
46
|
+
__setModuleDefault(result, mod);
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
49
|
+
})();
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
exports.parseJsonObjectArg = parseJsonObjectArg;
|
|
52
|
+
exports.extractAgentInput = extractAgentInput;
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const validate_1 = require("./validate");
|
|
55
|
+
/**
|
|
56
|
+
* Parse a JSON object from a flag value. A value of `-` reads from stdin.
|
|
57
|
+
* Throws ValidationError on invalid input or non-object JSON.
|
|
58
|
+
*/
|
|
59
|
+
function parseJsonObjectArg(raw, flag) {
|
|
60
|
+
return readJsonValue(raw, flag);
|
|
61
|
+
}
|
|
62
|
+
function readJsonValue(raw, flag) {
|
|
63
|
+
let text = raw;
|
|
64
|
+
if (raw === '-') {
|
|
65
|
+
try {
|
|
66
|
+
text = fs.readFileSync(0, 'utf8');
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
throw new validate_1.ValidationError(`Failed to read ${flag} from stdin: ${err instanceof Error ? err.message : String(err)}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = JSON.parse(text);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
throw new validate_1.ValidationError(`Invalid ${flag} JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
78
|
+
}
|
|
79
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
80
|
+
throw new validate_1.ValidationError(`${flag} must be a JSON object`);
|
|
81
|
+
}
|
|
82
|
+
return parsed;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Extract `--params` / `--json` from argv. Returns the parsed objects plus argv
|
|
86
|
+
* with those tokens removed so the existing per-binary parser can run unchanged.
|
|
87
|
+
*
|
|
88
|
+
* Supports `--flag value`, `--flag=value`. A `-` value reads from stdin.
|
|
89
|
+
*/
|
|
90
|
+
function extractAgentInput(argv) {
|
|
91
|
+
const out = { argv: [] };
|
|
92
|
+
for (let i = 0; i < argv.length; i++) {
|
|
93
|
+
const arg = argv[i];
|
|
94
|
+
if (arg === '--params' || arg === '--json') {
|
|
95
|
+
const value = argv[i + 1];
|
|
96
|
+
if (value === undefined) {
|
|
97
|
+
throw new validate_1.ValidationError(`${arg} requires a JSON value (or '-' for stdin)`);
|
|
98
|
+
}
|
|
99
|
+
i++;
|
|
100
|
+
const obj = readJsonValue(value, arg);
|
|
101
|
+
if (arg === '--params')
|
|
102
|
+
out.params = obj;
|
|
103
|
+
else
|
|
104
|
+
out.payload = obj;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (arg.startsWith('--params=') || arg.startsWith('--json=')) {
|
|
108
|
+
const eq = arg.indexOf('=');
|
|
109
|
+
const flag = arg.slice(0, eq);
|
|
110
|
+
const value = arg.slice(eq + 1);
|
|
111
|
+
const obj = readJsonValue(value, flag);
|
|
112
|
+
if (flag === '--params')
|
|
113
|
+
out.params = obj;
|
|
114
|
+
else
|
|
115
|
+
out.payload = obj;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
out.argv.push(arg);
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=params.js.map
|
|
@@ -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
|