neozip-cli 0.80.0 → 0.95.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +93 -0
- package/CHANGELOG.md +117 -12
- package/DOCUMENTATION.md +33 -38
- package/README.md +137 -63
- package/dist/src/account/account-state.js +95 -0
- package/dist/src/account/format-account-status.js +62 -0
- package/dist/src/account/identity-provision.js +79 -0
- package/dist/src/account/identity-wrap.js +106 -0
- package/dist/src/account/profile-crypto.js +85 -0
- package/dist/src/account/profile-store.js +129 -0
- package/dist/src/account/require-account.js +32 -0
- package/dist/src/account/types.js +3 -0
- package/dist/src/account/wallet-evm.js +46 -0
- package/dist/src/account/wallet-setup.js +33 -0
- package/dist/src/archive/crypto-self.js +117 -0
- package/dist/src/archive/identity-key.js +217 -0
- package/dist/src/archive/recipient-lookup.js +61 -0
- package/dist/src/cli/output.js +100 -0
- package/dist/src/cli/params.js +122 -0
- package/dist/src/cli/schema.js +186 -0
- package/dist/src/cli/validate.js +119 -0
- package/dist/src/commands/mintTimestampProof.js +26 -14
- package/dist/src/config/ConfigSetup.js +82 -423
- package/dist/src/connect/command.js +364 -0
- package/dist/src/connection/bootstrap.js +50 -0
- package/dist/src/connection/cli-guidance.js +101 -0
- package/dist/src/connection/cli-prefs.js +180 -0
- package/dist/src/connection/cli-settings.js +140 -0
- package/dist/src/connection/cli-types.js +14 -0
- package/dist/src/connection/coordinator.js +83 -0
- package/dist/src/connection/credentials.js +33 -0
- package/dist/src/connection/crypto.js +96 -0
- package/dist/src/connection/dump.js +117 -0
- package/dist/src/connection/funding.js +187 -0
- package/dist/src/connection/incomplete-setup.js +89 -0
- package/dist/src/connection/interactive.js +871 -0
- package/dist/src/connection/legacy-profile-reader.js +87 -0
- package/dist/src/connection/magic-link.js +142 -0
- package/dist/src/connection/migrate.js +115 -0
- package/dist/src/connection/onboarding.js +616 -0
- package/dist/src/connection/origin.js +69 -0
- package/dist/src/connection/phase.js +101 -0
- package/dist/src/connection/phone.js +26 -0
- package/dist/src/connection/promote-active.js +56 -0
- package/dist/src/connection/reset.js +56 -0
- package/dist/src/connection/status-report.js +52 -0
- package/dist/src/connection/store.js +406 -0
- package/dist/src/connection/token-auth.js +44 -0
- package/dist/src/connection/types.js +3 -0
- package/dist/src/connection/wallet-json-migration.js +155 -0
- package/dist/src/connection/wallet-login.js +65 -0
- package/dist/src/connection/wallet-setup.js +83 -0
- package/dist/src/constants/wallet-identity.js +14 -0
- package/dist/src/exit-codes.js +54 -10
- package/dist/src/neolist.js +100 -10
- package/dist/src/neounzip.js +236 -65
- package/dist/src/neozip/blockchain.js +18 -18
- package/dist/src/neozip/createZip.js +118 -94
- package/dist/src/neozip/upgradeZip.js +14 -11
- package/dist/src/neozip.js +269 -91
- package/dist/src/skills/command.js +256 -0
- package/dist/src/skills/locate.js +99 -0
- package/dist/src/util/mask.js +34 -0
- package/dist/src/util/token-service-fetch.js +26 -0
- package/env.example +18 -85
- package/package.json +89 -73
- package/skills/neozip-connect/SKILL.md +95 -0
- package/skills/neozip-create/SKILL.md +57 -0
- package/skills/neozip-extract/SKILL.md +58 -0
- package/skills/neozip-legacy/SKILL.md +48 -0
- package/skills/neozip-list/SKILL.md +56 -0
- package/skills/neozip-shared/SKILL.md +60 -0
- package/dist/src/commands/verifyEmail.js +0 -146
- package/dist/src/config/ConfigStore.js +0 -406
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
[](https://nodejs.org/)
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Release Notice
|
|
10
10
|
|
|
11
|
-
**
|
|
11
|
+
**Current version: v0.90.0.** This release adds full AES-256 encryption support. See [CHANGELOG.md](CHANGELOG.md) for details.
|
|
12
12
|
|
|
13
13
|
- **API Stability**: Command-line interface is becoming more stable but may still change
|
|
14
14
|
- **Testing**: Please test thoroughly and report any issues
|
|
@@ -21,7 +21,7 @@ NeoZip CLI is a modern, full-featured ZIP utility that provides:
|
|
|
21
21
|
- **Standard ZIP Compatibility**: ~99% compatible with InfoZip utilities
|
|
22
22
|
- **Blockchain Integration**: Tokenize archives and verify integrity on-chain
|
|
23
23
|
- **Advanced Compression**: Zstd, Deflate, and Store compression methods
|
|
24
|
-
- **Encryption Support**: PKZIP encryption
|
|
24
|
+
- **Encryption Support**: AES-256 (default) and PKZIP encryption
|
|
25
25
|
- **Cross-Platform**: Works on macOS, Linux, and Windows
|
|
26
26
|
|
|
27
27
|
## Features
|
|
@@ -42,14 +42,15 @@ NeoZip CLI is a modern, full-featured ZIP utility that provides:
|
|
|
42
42
|
- Compression levels: 0-9 (0=store, 9=best compression)
|
|
43
43
|
|
|
44
44
|
### Encryption
|
|
45
|
-
- **
|
|
45
|
+
- **AES-256**: Strong encryption (default, recommended)
|
|
46
|
+
- **PKZIP**: Legacy encryption (for compatibility only)
|
|
46
47
|
- Password-protected archives
|
|
47
|
-
-
|
|
48
|
+
- Auto-detection of encryption method during extraction
|
|
48
49
|
|
|
49
50
|
### Blockchain Features
|
|
50
51
|
- **Tokenization**: Create on-chain tokens for archives
|
|
51
52
|
- **OpenTimestamp**: Bitcoin blockchain timestamping
|
|
52
|
-
- **
|
|
53
|
+
- **Token Service**: Ethereum blockchain timestamping (NeoZip Token Service)
|
|
53
54
|
- **Integrity Verification**: Verify archive integrity on-chain
|
|
54
55
|
- **Network Support**: Base Sepolia, Base Mainnet, Arbitrum, and more
|
|
55
56
|
|
|
@@ -68,12 +69,13 @@ NeoZip CLI is a modern, full-featured ZIP utility that provides:
|
|
|
68
69
|
### Prerequisites
|
|
69
70
|
|
|
70
71
|
- Node.js 16.0.0 or higher
|
|
71
|
-
-
|
|
72
|
+
- [pnpm](https://pnpm.io/) 10+ (required for development from source)
|
|
73
|
+
- npm (optional — for global install of the published package only)
|
|
72
74
|
|
|
73
|
-
### Install from npm
|
|
75
|
+
### Install from npm
|
|
74
76
|
|
|
75
77
|
```bash
|
|
76
|
-
npm install -g neozip-cli
|
|
78
|
+
npm install -g neozip-cli
|
|
77
79
|
```
|
|
78
80
|
|
|
79
81
|
After installation, the following commands will be available:
|
|
@@ -138,13 +140,19 @@ neozip -0 output.nzip ./src/
|
|
|
138
140
|
### Encryption Examples
|
|
139
141
|
|
|
140
142
|
```bash
|
|
141
|
-
# Create encrypted archive (will prompt for password)
|
|
143
|
+
# Create AES-256 encrypted archive (default, will prompt for password)
|
|
142
144
|
neozip -e secure.nzip file.txt
|
|
143
145
|
|
|
144
|
-
# Create encrypted archive with password
|
|
146
|
+
# Create AES-256 encrypted archive with password
|
|
145
147
|
neozip -e -P "mypassword" secure.nzip file.txt
|
|
146
148
|
|
|
147
|
-
#
|
|
149
|
+
# Explicitly use AES-256
|
|
150
|
+
neozip --aes256 -P "mypassword" secure.nzip file.txt
|
|
151
|
+
|
|
152
|
+
# Use legacy PKZIP encryption (weak, for compatibility only)
|
|
153
|
+
neozip --pkzip -P "mypassword" legacy.nzip file.txt
|
|
154
|
+
|
|
155
|
+
# Extract encrypted archive (auto-detects AES-256 or PKZIP)
|
|
148
156
|
neounzip -P "mypassword" secure.nzip tests/extracted/
|
|
149
157
|
```
|
|
150
158
|
|
|
@@ -157,11 +165,11 @@ neozip -b tokenized.nzip file.txt
|
|
|
157
165
|
# Tokenize with OpenTimestamp proof (Bitcoin)
|
|
158
166
|
neozip -ots timestamped.nzip file.txt
|
|
159
167
|
|
|
160
|
-
# Create
|
|
161
|
-
neozip -ts
|
|
168
|
+
# Create Token Service timestamp (Ethereum)
|
|
169
|
+
neozip -ts timestamped.nzip file.txt
|
|
162
170
|
|
|
163
|
-
# Upgrade pending
|
|
164
|
-
neozip upgrade
|
|
171
|
+
# Upgrade pending Token Service timestamp to confirmed
|
|
172
|
+
neozip upgrade timestamped.nzip [output.nzip] [--wait]
|
|
165
173
|
|
|
166
174
|
# Extract and verify blockchain integrity
|
|
167
175
|
neounzip tokenized.nzip tests/extracted/
|
|
@@ -183,21 +191,22 @@ neozip [options] <archive> [files...]
|
|
|
183
191
|
- `-0` to `-9` - Compression level (0=store, 9=best)
|
|
184
192
|
- `-x, --exclude <pattern>` - Exclude files matching pattern
|
|
185
193
|
- `-i, --include <pattern>` - Include only matching files
|
|
186
|
-
- `-e, --encrypt` - Encrypt files (will prompt for password)
|
|
194
|
+
- `-e, --encrypt` - Encrypt files with AES-256 (default, will prompt for password)
|
|
187
195
|
- `-P, --password <pwd>` - Encrypt with password
|
|
196
|
+
- `--aes256` - Use AES-256 encryption (default, recommended)
|
|
197
|
+
- `--pkzip` - Use legacy PKZIP encryption (weak, for compatibility only)
|
|
188
198
|
- `-b, --blockchain` - Enable blockchain tokenization
|
|
189
199
|
- `-ots, --opentimestamp` - Enable OpenTimestamp proof (Bitcoin)
|
|
190
|
-
- `-ts, --timestamp` - Enable
|
|
191
|
-
- `--timestamp-email <email>` - Email for Zipstamp server (some servers require verified email)
|
|
200
|
+
- `-ts, --timestamp` - Enable Token Service timestamp (Ethereum; requires `neozip connect`)
|
|
192
201
|
- `-T, --test-integrity` - Test archive after creation
|
|
193
202
|
- `-v, --verbose` - Enable verbose output
|
|
194
203
|
- `-q, --quiet` - Suppress output
|
|
195
204
|
|
|
196
205
|
**Configuration Commands:**
|
|
197
206
|
```bash
|
|
198
|
-
neozip
|
|
199
|
-
neozip config #
|
|
200
|
-
neozip upgrade <archive> [output] [--wait] # Upgrade pending
|
|
207
|
+
neozip connect # Token Service account + wallet setup (alias: neozip init)
|
|
208
|
+
neozip config # Connection dashboard (status, prefs, migrate)
|
|
209
|
+
neozip upgrade <archive> [output] [--wait] # Upgrade pending Token Service timestamp to confirmed
|
|
201
210
|
```
|
|
202
211
|
|
|
203
212
|
**Examples:**
|
|
@@ -232,7 +241,7 @@ neounzip [options] <archive> [output]
|
|
|
232
241
|
- `-o, --overwrite` - Overwrite files without prompting
|
|
233
242
|
- `-t, --test` - Test archive integrity
|
|
234
243
|
- `-l, --list` - List contents without extracting
|
|
235
|
-
- `-P, --password <pwd>` - Password for encrypted archives
|
|
244
|
+
- `-P, --password <pwd>` - Password for encrypted archives (AES-256 and PKZIP auto-detected)
|
|
236
245
|
- `-x, --exclude <pattern>` - Exclude files from extraction
|
|
237
246
|
- `-i, --include <pattern>` - Include only matching files
|
|
238
247
|
- `-j, --junk-paths` - Extract files without directory structure
|
|
@@ -292,41 +301,26 @@ neolist -1 archive.nzip
|
|
|
292
301
|
|
|
293
302
|
## Configuration
|
|
294
303
|
|
|
295
|
-
###
|
|
304
|
+
### Account and wallet setup
|
|
296
305
|
|
|
297
|
-
|
|
306
|
+
All wallet identity and Token Service credentials live in the shared connection store (`~/.neozip/connection/`). Use the same profile as NeoZip Desktop and MCP.
|
|
298
307
|
|
|
299
308
|
```bash
|
|
300
|
-
# Interactive setup
|
|
301
|
-
neozip
|
|
309
|
+
neozip connect # Interactive setup (alias: neozip init)
|
|
310
|
+
neozip connect status # Show connection phase and credentials
|
|
311
|
+
neozip connect settings # Default network, RPC overrides, gas prefs
|
|
312
|
+
neozip connect migrate # Import legacy wallet.json / MCP profiles
|
|
313
|
+
neozip config # Connection dashboard (status, prefs, migrate)
|
|
302
314
|
```
|
|
303
315
|
|
|
304
|
-
|
|
305
|
-
1. Creating or importing a wallet
|
|
306
|
-
2. Selecting a blockchain network
|
|
307
|
-
3. Saving configuration to `~/.neozip/wallet.json`
|
|
316
|
+
On-chain preferences (`defaultNetwork`, custom RPC URLs, gas) are stored in each connection's `public.json`.
|
|
308
317
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
You can also configure via environment variables:
|
|
318
|
+
Optional overrides:
|
|
312
319
|
|
|
313
320
|
```bash
|
|
314
|
-
export
|
|
315
|
-
export
|
|
316
|
-
export
|
|
317
|
-
export NEOZIP_TIMESTAMP_EMAIL="your@email.com" # Email for Zipstamp (some servers require verified email)
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
### Configuration File
|
|
321
|
-
|
|
322
|
-
Configuration is stored in `~/.neozip/wallet.json`:
|
|
323
|
-
|
|
324
|
-
```json
|
|
325
|
-
{
|
|
326
|
-
"walletKey": "0x...",
|
|
327
|
-
"network": "base-sepolia",
|
|
328
|
-
"rpcUrl": "https://..."
|
|
329
|
-
}
|
|
321
|
+
export NEOZIP_CONNECTION_DIR="/path/to/connection" # Store path override
|
|
322
|
+
export NEOZIP_UNLOCK_PASSPHRASE="..." # Decrypt secrets.enc non-interactively
|
|
323
|
+
export NEOZIP_NETWORK="base-sepolia" # Override default network for this session
|
|
330
324
|
```
|
|
331
325
|
|
|
332
326
|
## Advanced Examples
|
|
@@ -376,10 +370,13 @@ neozip -r -x "node_modules" -x "*.log" project.nzip ./
|
|
|
376
370
|
### Encryption
|
|
377
371
|
|
|
378
372
|
```bash
|
|
379
|
-
# Create encrypted archive
|
|
373
|
+
# Create AES-256 encrypted archive (default)
|
|
380
374
|
neozip -e -P "secure-password" secret.nzip sensitive-files/
|
|
381
375
|
|
|
382
|
-
#
|
|
376
|
+
# Create legacy PKZIP encrypted archive
|
|
377
|
+
neozip --pkzip -P "secure-password" legacy.nzip sensitive-files/
|
|
378
|
+
|
|
379
|
+
# Extract encrypted archive (auto-detects encryption method)
|
|
383
380
|
neounzip -P "secure-password" secret.nzip decrypted/
|
|
384
381
|
```
|
|
385
382
|
|
|
@@ -415,6 +412,73 @@ neozip -X archive.nzip ./files/
|
|
|
415
412
|
neozip -y archive.nzip ./symlinks/
|
|
416
413
|
```
|
|
417
414
|
|
|
415
|
+
## For AI agents
|
|
416
|
+
|
|
417
|
+
NeoZip ships an agent-friendly surface. See [AGENTS.md](AGENTS.md) and the
|
|
418
|
+
[skills/](skills/) directory for the full guide.
|
|
419
|
+
|
|
420
|
+
### Install Agent Skills
|
|
421
|
+
|
|
422
|
+
Six bundled skills (`neozip-shared`, `neozip-create`, `neozip-extract`, `neozip-list`,
|
|
423
|
+
`neozip-connect`, `neozip-legacy`) ship inside the npm package. Install them into
|
|
424
|
+
Claude Code or Cursor after `npm install -g neozip-cli`:
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
# Symlink bundled skills into ~/.claude/skills and ~/.cursor/skills
|
|
428
|
+
neozip skills install
|
|
429
|
+
|
|
430
|
+
# Claude Code only
|
|
431
|
+
neozip skills install --target claude
|
|
432
|
+
|
|
433
|
+
# Show where skills live in your install
|
|
434
|
+
neozip skills path
|
|
435
|
+
neozip skills list
|
|
436
|
+
|
|
437
|
+
# ZIP files for Claude Cowork / claude.ai upload (Customize > Skills)
|
|
438
|
+
neozip skills pack --output-dir ./neozip-skills-zips
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Alternative (skills registry, same format as Google Workspace CLI):
|
|
442
|
+
|
|
443
|
+
```bash
|
|
444
|
+
npx skills add https://github.com/NeoWareInc/neozip-cli
|
|
445
|
+
npx skills add https://github.com/NeoWareInc/neozip-cli/tree/main/skills/neozip-create
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
### CLI conventions
|
|
449
|
+
|
|
450
|
+
- **Machine-readable output:** add `--format json` to any command to get a
|
|
451
|
+
single JSON object on stdout (`{ "ok": true, ... }` or
|
|
452
|
+
`{ "ok": false, "error": { ... } }`). Human hints go to stderr.
|
|
453
|
+
- **JSON input:** `--params '<JSON>'` (options) and `--json '<JSON>'` (payload:
|
|
454
|
+
archive + files/targets). Either accepts `-` to read from stdin. Explicit CLI
|
|
455
|
+
flags override JSON input.
|
|
456
|
+
- **Schema introspection:** `neozip schema <neozip|neounzip|neolist|connect>`
|
|
457
|
+
emits the accepted fields, types, defaults, and exit codes.
|
|
458
|
+
- **Dry-run:** `neozip`/`neounzip` accept `--dry-run` to resolve and print the
|
|
459
|
+
plan as JSON without writing anything.
|
|
460
|
+
- **Safety:** archive names and extraction dirs are validated for control
|
|
461
|
+
characters; `neounzip` refuses zip-slip (entries escaping the target dir).
|
|
462
|
+
- **Exit codes:** InfoZip-compatible and echoed in `error.exitCode`.
|
|
463
|
+
|
|
464
|
+
```bash
|
|
465
|
+
# Introspect, then create fully from JSON
|
|
466
|
+
neozip schema neozip
|
|
467
|
+
neozip --format json \
|
|
468
|
+
--params '{"level":9,"encrypt":true,"password":"s3cret"}' \
|
|
469
|
+
--json '{"archive":"out.zip","files":["src/","README.md"]}'
|
|
470
|
+
|
|
471
|
+
# Preview an extraction, then run it
|
|
472
|
+
neounzip --format json --dry-run --params '{"exdir":"out"}' in.zip
|
|
473
|
+
neounzip --format json --params '{"exdir":"out","overwrite":true}' in.zip
|
|
474
|
+
|
|
475
|
+
# List as JSON
|
|
476
|
+
neolist --format json in.zip
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
For an archive that stock InfoZip `unzip` can read, add `--legacy` (forces
|
|
480
|
+
deflate/store + PKZIP, disables NeoZip extensions).
|
|
481
|
+
|
|
418
482
|
## Requirements
|
|
419
483
|
|
|
420
484
|
- **Node.js**: 16.0.0 or higher
|
|
@@ -467,11 +531,11 @@ neozip new-archive.nzip original-files/
|
|
|
467
531
|
|
|
468
532
|
**Blockchain errors:**
|
|
469
533
|
```bash
|
|
470
|
-
# Check
|
|
471
|
-
neozip
|
|
534
|
+
# Check connection status
|
|
535
|
+
neozip connect status
|
|
472
536
|
|
|
473
|
-
#
|
|
474
|
-
neozip
|
|
537
|
+
# Re-run setup
|
|
538
|
+
neozip connect
|
|
475
539
|
```
|
|
476
540
|
|
|
477
541
|
**Encryption errors:**
|
|
@@ -479,19 +543,29 @@ neozip init
|
|
|
479
543
|
# Verify password is correct
|
|
480
544
|
neounzip -P "your-password" archive.nzip
|
|
481
545
|
|
|
482
|
-
#
|
|
483
|
-
neozip --pkzip
|
|
546
|
+
# Use legacy PKZIP if AES-256 is not supported by target tool
|
|
547
|
+
neozip --pkzip -P "password" archive.nzip files/
|
|
484
548
|
```
|
|
485
549
|
|
|
486
550
|
## Contributing
|
|
487
551
|
|
|
488
|
-
We welcome feedback and contributions
|
|
552
|
+
We welcome feedback and contributions:
|
|
489
553
|
|
|
490
554
|
1. **Report Issues**: Use [GitHub Issues](https://github.com/NeoWareInc/neozip-support/issues) or email [support@neozip.io](mailto:support@neozip.io)
|
|
491
555
|
2. **Suggest Features**: Share your ideas and use cases
|
|
492
556
|
3. **Test and Provide Feedback**: Help us improve by testing and reporting your experience
|
|
493
557
|
|
|
494
|
-
|
|
558
|
+
### Development setup
|
|
559
|
+
|
|
560
|
+
Requires [pnpm](https://pnpm.io/) (`packageManager: pnpm@10.12.1`):
|
|
561
|
+
|
|
562
|
+
```bash
|
|
563
|
+
pnpm install
|
|
564
|
+
pnpm build
|
|
565
|
+
pnpm test
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
**Important**: Please test thoroughly and report issues. See [CHANGELOG.md](CHANGELOG.md) for version history.
|
|
495
569
|
|
|
496
570
|
## License
|
|
497
571
|
|
|
@@ -511,6 +585,6 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
|
|
|
511
585
|
|
|
512
586
|
---
|
|
513
587
|
|
|
514
|
-
**Version**: 0.
|
|
515
|
-
**Status**:
|
|
516
|
-
**Last Updated**:
|
|
588
|
+
**Version**: 0.90.0
|
|
589
|
+
**Status**: Release (see [CHANGELOG.md](CHANGELOG.md) for latest changes)
|
|
590
|
+
**Last Updated**: March 18, 2026
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.computeAccountPhase = computeAccountPhase;
|
|
4
|
+
exports.buildAccountStatusReport = buildAccountStatusReport;
|
|
5
|
+
const token_service_1 = require("neozip-blockchain/token-service");
|
|
6
|
+
const mask_1 = require("../util/mask");
|
|
7
|
+
const profile_store_1 = require("./profile-store");
|
|
8
|
+
const token_auth_1 = require("../connection/token-auth");
|
|
9
|
+
function nextStepsForPhase(phase) {
|
|
10
|
+
switch (phase) {
|
|
11
|
+
case "no_profile":
|
|
12
|
+
return (0, profile_store_1.listProfiles)().length > 0
|
|
13
|
+
? ["account_select", "account_list", "account_create"]
|
|
14
|
+
: ["account_create", "account_list"];
|
|
15
|
+
case "email_unverified":
|
|
16
|
+
return ["account_register_email", "account_verify_email"];
|
|
17
|
+
case "no_wallet":
|
|
18
|
+
return ["account_create_wallet", "account_import_wallet"];
|
|
19
|
+
case "wallet_unlinked":
|
|
20
|
+
return ["account_link_wallet"];
|
|
21
|
+
case "identity_missing":
|
|
22
|
+
return ["account_provision_identity"];
|
|
23
|
+
case "ready":
|
|
24
|
+
return ["account_logout"];
|
|
25
|
+
case "degraded":
|
|
26
|
+
return ["account_status"];
|
|
27
|
+
default:
|
|
28
|
+
return ["account_status"];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function computeAccountPhase() {
|
|
32
|
+
const profileId = (0, profile_store_1.getActiveProfileId)();
|
|
33
|
+
if (!profileId) {
|
|
34
|
+
return "no_profile";
|
|
35
|
+
}
|
|
36
|
+
const profile = (0, profile_store_1.getActiveProfile)();
|
|
37
|
+
const secrets = (0, profile_store_1.readActiveSecrets)();
|
|
38
|
+
if (!profile || !secrets)
|
|
39
|
+
return "no_profile";
|
|
40
|
+
if (!secrets.accessToken || !profile.emailVerified) {
|
|
41
|
+
return "email_unverified";
|
|
42
|
+
}
|
|
43
|
+
if (!secrets.evmPrivateKey || !profile.evmAddress) {
|
|
44
|
+
return "no_wallet";
|
|
45
|
+
}
|
|
46
|
+
if (!profile.walletId || profile.linkId == null) {
|
|
47
|
+
return "wallet_unlinked";
|
|
48
|
+
}
|
|
49
|
+
if (!profile.identityKeyId) {
|
|
50
|
+
return "identity_missing";
|
|
51
|
+
}
|
|
52
|
+
return "ready";
|
|
53
|
+
}
|
|
54
|
+
async function buildAccountStatusReport(options) {
|
|
55
|
+
const profile = (0, profile_store_1.getActiveProfile)();
|
|
56
|
+
const secrets = (0, profile_store_1.readActiveSecrets)();
|
|
57
|
+
const phase = computeAccountPhase();
|
|
58
|
+
const tokenExpiresAt = secrets?.accessTokenExpiresAt ?? null;
|
|
59
|
+
const tokenExpired = Boolean(secrets?.accessToken) && (0, token_auth_1.isAccessTokenExpired)(secrets);
|
|
60
|
+
const tokenServiceUrl = (0, token_service_1.getTokenServiceUrl)({
|
|
61
|
+
serverUrl: profile?.tokenServiceUrl || undefined,
|
|
62
|
+
});
|
|
63
|
+
let tokenServiceReachable = null;
|
|
64
|
+
if (options?.probeTokenService !== false) {
|
|
65
|
+
try {
|
|
66
|
+
tokenServiceReachable = await (0, token_service_1.fetchCoordinatorConfig)(tokenServiceUrl);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
tokenServiceReachable = false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const nextSteps = nextStepsForPhase(tokenServiceReachable === false && phase !== "ready" ? "degraded" : phase);
|
|
73
|
+
const reloadRequired = phase === "ready" &&
|
|
74
|
+
Boolean(profile &&
|
|
75
|
+
(!process.env.NEOZIP_TOKEN_SERVICE_ACCESS_TOKEN ||
|
|
76
|
+
!process.env.NEOZIP_WALLET_PASSKEY));
|
|
77
|
+
return {
|
|
78
|
+
success: true,
|
|
79
|
+
phase: tokenServiceReachable === false && phase !== "ready" ? "degraded" : phase,
|
|
80
|
+
activeProfileId: (0, profile_store_1.getActiveProfileId)(),
|
|
81
|
+
profile: profile
|
|
82
|
+
? {
|
|
83
|
+
...profile,
|
|
84
|
+
email: profile.email ? (0, mask_1.maskEmail)(profile.email) : null,
|
|
85
|
+
}
|
|
86
|
+
: null,
|
|
87
|
+
tokenServiceReachable,
|
|
88
|
+
tokenServiceUrl,
|
|
89
|
+
tokenExpiresAt,
|
|
90
|
+
tokenExpired,
|
|
91
|
+
nextSteps,
|
|
92
|
+
reloadRequired,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=account-state.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatAccountStatusText = formatAccountStatusText;
|
|
4
|
+
function formatExpiry(expiresAt) {
|
|
5
|
+
const deltaMs = expiresAt - Date.now();
|
|
6
|
+
const abs = Math.abs(deltaMs);
|
|
7
|
+
const minutes = Math.round(abs / 60000);
|
|
8
|
+
const hours = Math.round(abs / 3600000);
|
|
9
|
+
const days = Math.round(abs / 86400000);
|
|
10
|
+
let rel;
|
|
11
|
+
if (abs < 3600000)
|
|
12
|
+
rel = `${minutes} min`;
|
|
13
|
+
else if (abs < 86400000)
|
|
14
|
+
rel = `${hours} hr`;
|
|
15
|
+
else
|
|
16
|
+
rel = `${days} day${days === 1 ? "" : "s"}`;
|
|
17
|
+
const when = new Date(expiresAt).toISOString();
|
|
18
|
+
return deltaMs >= 0 ? `in ${rel} (${when})` : `${rel} ago (${when})`;
|
|
19
|
+
}
|
|
20
|
+
function formatAccountStatusText(report) {
|
|
21
|
+
const lines = [];
|
|
22
|
+
lines.push("NeoZip account status");
|
|
23
|
+
lines.push(`Phase: ${report.phase}`);
|
|
24
|
+
lines.push(`Token Service: ${report.tokenServiceUrl}`);
|
|
25
|
+
if (report.tokenServiceReachable != null) {
|
|
26
|
+
lines.push(`Token Service reachable: ${report.tokenServiceReachable ? "yes" : "no"}`);
|
|
27
|
+
}
|
|
28
|
+
if (report.tokenExpired) {
|
|
29
|
+
lines.push("Access token: EXPIRED — run neozip connect refresh");
|
|
30
|
+
}
|
|
31
|
+
else if (report.tokenExpiresAt != null) {
|
|
32
|
+
lines.push(`Access token: valid (expires ${formatExpiry(report.tokenExpiresAt)})`);
|
|
33
|
+
}
|
|
34
|
+
if (report.activeProfileId) {
|
|
35
|
+
lines.push(`Active profile: ${report.activeProfileId}`);
|
|
36
|
+
}
|
|
37
|
+
if (report.profile) {
|
|
38
|
+
lines.push(`Label: ${report.profile.label}`);
|
|
39
|
+
if (report.profile.email)
|
|
40
|
+
lines.push(`Email: ${report.profile.email}`);
|
|
41
|
+
if (report.profile.evmAddress) {
|
|
42
|
+
lines.push(`EVM address: ${report.profile.evmAddress}`);
|
|
43
|
+
}
|
|
44
|
+
if (report.profile.walletId != null) {
|
|
45
|
+
lines.push(`Wallet ID: ${report.profile.walletId}`);
|
|
46
|
+
}
|
|
47
|
+
if (report.profile.identityKeyId != null) {
|
|
48
|
+
lines.push(`Identity key ID: ${report.profile.identityKeyId}`);
|
|
49
|
+
}
|
|
50
|
+
if (report.profile.x25519Fingerprint) {
|
|
51
|
+
lines.push(`X25519 fingerprint: ${report.profile.x25519Fingerprint}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (report.nextSteps.length) {
|
|
55
|
+
lines.push(`Next steps: ${report.nextSteps.join(", ")}`);
|
|
56
|
+
}
|
|
57
|
+
if (report.reloadRequired) {
|
|
58
|
+
lines.push("Reload MCP in Cursor so the active connection credentials are loaded into this session.");
|
|
59
|
+
}
|
|
60
|
+
return lines.join("\n");
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=format-account-status.js.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.provisionWalletIdentityKey = provisionWalletIdentityKey;
|
|
4
|
+
exports.refreshIdentityFromCryptoSelf = refreshIdentityFromCryptoSelf;
|
|
5
|
+
const identity_key_1 = require("../archive/identity-key");
|
|
6
|
+
const crypto_self_1 = require("../archive/crypto-self");
|
|
7
|
+
const identity_wrap_1 = require("./identity-wrap");
|
|
8
|
+
const token_service_1 = require("neozip-blockchain/token-service");
|
|
9
|
+
const wallet_evm_1 = require("./wallet-evm");
|
|
10
|
+
async function provisionWalletIdentityKey(params) {
|
|
11
|
+
const existing = await (0, token_service_1.getIdentityKeyBundle)(params.baseUrl, params.accessToken, params.walletId);
|
|
12
|
+
if (existing.found) {
|
|
13
|
+
return {
|
|
14
|
+
identityKeyId: existing.identityKeyId,
|
|
15
|
+
x25519PublicKey: existing.x25519PublicKey,
|
|
16
|
+
provisioned: false,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const eoaPriv = (0, wallet_evm_1.rawEoaBytes)(params.evmPrivateKey);
|
|
20
|
+
try {
|
|
21
|
+
const wrap = (0, identity_wrap_1.wrapNewX25519Keypair)({
|
|
22
|
+
rawEoaPrivBytes: eoaPriv,
|
|
23
|
+
evmAddress: params.evmAddress,
|
|
24
|
+
});
|
|
25
|
+
const bundleBody = {
|
|
26
|
+
x25519PublicKeyB64: wrap.x25519PublicKeyB64,
|
|
27
|
+
...wrap.bundle,
|
|
28
|
+
};
|
|
29
|
+
const challenge = await (0, token_service_1.requestIdentityKeyInitChallenge)(params.baseUrl, params.accessToken, params.walletId, bundleBody);
|
|
30
|
+
const expectedMessage = (0, identity_wrap_1.buildIdentityKeyInitMessage)({
|
|
31
|
+
walletId: params.walletId,
|
|
32
|
+
challengeId: challenge.challengeId,
|
|
33
|
+
evmAddress: params.evmAddress,
|
|
34
|
+
formatVersion: wrap.bundle.wrapFormatVersion,
|
|
35
|
+
x25519PublicKeyB64: wrap.x25519PublicKeyB64,
|
|
36
|
+
wrappedSha256Hex: wrap.wrappedSha256Hex,
|
|
37
|
+
});
|
|
38
|
+
if (challenge.message !== expectedMessage) {
|
|
39
|
+
throw new identity_key_1.IdentityKeyTamperError("Server init message does not match canonical form expected by this client.");
|
|
40
|
+
}
|
|
41
|
+
const evmSignature = await (0, wallet_evm_1.signEvmMessage)(params.evmPrivateKey, challenge.message);
|
|
42
|
+
try {
|
|
43
|
+
const completed = await (0, token_service_1.completeIdentityKeyInit)(params.baseUrl, params.accessToken, params.walletId, bundleBody, challenge.challengeId, evmSignature);
|
|
44
|
+
return {
|
|
45
|
+
identityKeyId: completed.identityKeyId,
|
|
46
|
+
x25519PublicKey: completed.x25519PublicKey,
|
|
47
|
+
provisioned: true,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
if (err instanceof token_service_1.IdentityKeyConflictError) {
|
|
52
|
+
const reloaded = await (0, token_service_1.getIdentityKeyBundle)(params.baseUrl, params.accessToken, params.walletId);
|
|
53
|
+
if (reloaded.found) {
|
|
54
|
+
return {
|
|
55
|
+
identityKeyId: reloaded.identityKeyId,
|
|
56
|
+
x25519PublicKey: reloaded.x25519PublicKey,
|
|
57
|
+
provisioned: false,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
eoaPriv.fill(0);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/** Sync profile metadata with live /crypto/self when token is available. */
|
|
69
|
+
async function refreshIdentityFromCryptoSelf(config) {
|
|
70
|
+
const self = await (0, crypto_self_1.fetchCryptoSelf)(config);
|
|
71
|
+
if (!self.ok) {
|
|
72
|
+
return { identityKeyId: null, walletId: null };
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
identityKeyId: self.data.identityKeyId,
|
|
76
|
+
walletId: self.data.walletId,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=identity-provision.js.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.buildIdentityKeyInitMessage = buildIdentityKeyInitMessage;
|
|
37
|
+
exports.wrapNewX25519Keypair = wrapNewX25519Keypair;
|
|
38
|
+
const crypto = __importStar(require("node:crypto"));
|
|
39
|
+
const wallet_identity_1 = require("../constants/wallet-identity");
|
|
40
|
+
function buildCanonicalAad(params) {
|
|
41
|
+
const lowerAddr = params.evmAddress.toLowerCase();
|
|
42
|
+
const addrBytes = Buffer.from(lowerAddr, "ascii");
|
|
43
|
+
return Buffer.concat([
|
|
44
|
+
addrBytes,
|
|
45
|
+
Buffer.from([wallet_identity_1.WRAP_FORMAT_VERSION]),
|
|
46
|
+
params.x25519SpkiDer,
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
function deriveKek(rawEoaPrivBytes, saltB64) {
|
|
50
|
+
const salt = Buffer.from(saltB64, "base64");
|
|
51
|
+
if (salt.length !== wallet_identity_1.WRAP_KDF_SALT_LEN) {
|
|
52
|
+
throw new Error(`Wrap KDF salt must be ${wallet_identity_1.WRAP_KDF_SALT_LEN} bytes`);
|
|
53
|
+
}
|
|
54
|
+
return Buffer.from(crypto.hkdfSync("sha256", rawEoaPrivBytes, salt, wallet_identity_1.WRAP_KDF_INFO, 32));
|
|
55
|
+
}
|
|
56
|
+
function sha256Hex(buf) {
|
|
57
|
+
return crypto.createHash("sha256").update(buf).digest("hex");
|
|
58
|
+
}
|
|
59
|
+
function buildIdentityKeyInitMessage(params) {
|
|
60
|
+
return [
|
|
61
|
+
"NeoZip Token Service identity key init",
|
|
62
|
+
`walletId:${params.walletId}`,
|
|
63
|
+
`challenge:${params.challengeId}`,
|
|
64
|
+
`address:${params.evmAddress.toLowerCase()}`,
|
|
65
|
+
`formatVersion:${params.formatVersion}`,
|
|
66
|
+
`x25519:${params.x25519PublicKeyB64}`,
|
|
67
|
+
`wrappedSha256:${params.wrappedSha256Hex}`,
|
|
68
|
+
].join("\n");
|
|
69
|
+
}
|
|
70
|
+
function wrapNewX25519Keypair(params) {
|
|
71
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync("x25519");
|
|
72
|
+
const spkiDer = publicKey.export({ type: "spki", format: "der" });
|
|
73
|
+
const pkcs8Der = privateKey.export({ type: "pkcs8", format: "der" });
|
|
74
|
+
const rawPriv = pkcs8Der.subarray(pkcs8Der.length - 32);
|
|
75
|
+
const saltB64 = crypto.randomBytes(wallet_identity_1.WRAP_KDF_SALT_LEN).toString("base64");
|
|
76
|
+
const ivB64 = crypto.randomBytes(wallet_identity_1.WRAP_IV_LEN).toString("base64");
|
|
77
|
+
const aad = buildCanonicalAad({
|
|
78
|
+
evmAddress: params.evmAddress,
|
|
79
|
+
x25519SpkiDer: spkiDer,
|
|
80
|
+
});
|
|
81
|
+
const kek = deriveKek(params.rawEoaPrivBytes, saltB64);
|
|
82
|
+
const iv = Buffer.from(ivB64, "base64");
|
|
83
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", kek, iv);
|
|
84
|
+
cipher.setAAD(aad);
|
|
85
|
+
const ct = Buffer.concat([cipher.update(rawPriv), cipher.final()]);
|
|
86
|
+
const tag = cipher.getAuthTag();
|
|
87
|
+
const wrapped = Buffer.concat([ct, tag]);
|
|
88
|
+
kek.fill(0);
|
|
89
|
+
rawPriv.fill(0);
|
|
90
|
+
pkcs8Der.fill(0);
|
|
91
|
+
const bundle = {
|
|
92
|
+
wrappedPrivateKeyB64: wrapped.toString("base64"),
|
|
93
|
+
wrapFormatVersion: wallet_identity_1.WRAP_FORMAT_VERSION,
|
|
94
|
+
wrapKdfInfo: wallet_identity_1.WRAP_KDF_INFO,
|
|
95
|
+
wrapKdfSaltB64: saltB64,
|
|
96
|
+
wrapAead: wallet_identity_1.WRAP_AEAD,
|
|
97
|
+
wrapIvB64: ivB64,
|
|
98
|
+
wrapAadB64: aad.toString("base64"),
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
bundle,
|
|
102
|
+
x25519PublicKeyB64: spkiDer.toString("base64"),
|
|
103
|
+
wrappedSha256Hex: sha256Hex(wrapped),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=identity-wrap.js.map
|