@ametyst/cli 0.2.1 → 0.2.34
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/ametyst.node +0 -0
- package/dist/index.js +4078 -4084
- package/native/ametyst-vault.node +0 -0
- package/native/index.d.ts +7 -0
- package/native/src/lib.rs +65 -16
- package/package.json +26 -7
|
Binary file
|
package/native/index.d.ts
CHANGED
|
@@ -5,5 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
export declare function hardenProcess(): void
|
|
7
7
|
export declare function createWallet(passphrase: string): string
|
|
8
|
+
/**
|
|
9
|
+
* Import an externally-provided raw private key (e.g. an Option-A session key
|
|
10
|
+
* generated + passkey-approved in the web app and pasted into the CLI) into
|
|
11
|
+
* the SAME passphrase-encrypted keystore format `create_wallet` emits. The key
|
|
12
|
+
* never leaves the machine — Ametyst never receives it (ADR-001 non-custodial).
|
|
13
|
+
*/
|
|
14
|
+
export declare function importWallet(privateKey: string, passphrase: string): string
|
|
8
15
|
export declare function decryptWallet(keystoreJson: string, passphrase: string): string
|
|
9
16
|
export declare function getWalletAddress(keystoreJson: string): string
|
package/native/src/lib.rs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
use aes_gcm::aead::{Aead, KeyInit};
|
|
2
2
|
use aes_gcm::{Aes256Gcm, Nonce};
|
|
3
3
|
use k256::ecdsa::SigningKey;
|
|
4
|
+
#[cfg(unix)]
|
|
4
5
|
use libc::{c_void, mlock, munlock, rlimit, setrlimit, RLIMIT_CORE};
|
|
5
6
|
use napi_derive::napi;
|
|
6
7
|
use rand::rngs::OsRng;
|
|
@@ -24,9 +25,10 @@ struct SecretBytes {
|
|
|
24
25
|
impl SecretBytes {
|
|
25
26
|
fn new(bytes: Vec<u8>) -> Self {
|
|
26
27
|
let secret = Self { bytes };
|
|
27
|
-
|
|
28
|
-
let len = secret.bytes.len();
|
|
28
|
+
#[cfg(unix)]
|
|
29
29
|
unsafe {
|
|
30
|
+
let ptr = secret.bytes.as_ptr() as *const c_void;
|
|
31
|
+
let len = secret.bytes.len();
|
|
30
32
|
let _ = mlock(ptr, len);
|
|
31
33
|
}
|
|
32
34
|
secret
|
|
@@ -43,10 +45,11 @@ impl SecretBytes {
|
|
|
43
45
|
|
|
44
46
|
impl Drop for SecretBytes {
|
|
45
47
|
fn drop(&mut self) {
|
|
46
|
-
let ptr = self.bytes.as_ptr() as *const c_void;
|
|
47
|
-
let len = self.bytes.len();
|
|
48
48
|
self.bytes.zeroize();
|
|
49
|
+
#[cfg(unix)]
|
|
49
50
|
unsafe {
|
|
51
|
+
let ptr = self.bytes.as_ptr() as *const c_void;
|
|
52
|
+
let len = self.bytes.len();
|
|
50
53
|
let _ = munlock(ptr, len);
|
|
51
54
|
}
|
|
52
55
|
}
|
|
@@ -110,24 +113,49 @@ pub fn harden_process() -> napi::Result<()> {
|
|
|
110
113
|
libc::prctl(libc::PR_SET_DUMPABLE, 0, 0, 0, 0);
|
|
111
114
|
}
|
|
112
115
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
116
|
+
// Windows writes no core dump to cwd; setrlimit is Unix-only.
|
|
117
|
+
// Memory-lock (VirtualLock) parity on Windows is a backlog item.
|
|
118
|
+
#[cfg(unix)]
|
|
119
|
+
{
|
|
120
|
+
let lim = rlimit {
|
|
121
|
+
rlim_cur: 0,
|
|
122
|
+
rlim_max: 0,
|
|
123
|
+
};
|
|
124
|
+
unsafe {
|
|
125
|
+
setrlimit(RLIMIT_CORE, &lim);
|
|
126
|
+
}
|
|
119
127
|
}
|
|
120
128
|
|
|
121
129
|
Ok(())
|
|
122
130
|
}
|
|
123
131
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
private_key.
|
|
132
|
+
/// Parse a raw secp256k1 private key from a hex string (with or without a
|
|
133
|
+
/// leading `0x`) into exactly 32 bytes. Rejects anything that is not a valid
|
|
134
|
+
/// 32-byte key so a bad paste fails loudly instead of producing a keystore for
|
|
135
|
+
/// the wrong address.
|
|
136
|
+
fn parse_private_key_hex(private_key: &str) -> napi::Result<Vec<u8>> {
|
|
137
|
+
let trimmed = private_key.trim();
|
|
138
|
+
let stripped = trimmed.strip_prefix("0x").unwrap_or(trimmed);
|
|
139
|
+
let bytes = hex::decode(stripped)
|
|
140
|
+
.map_err(|e| napi::Error::from_reason(format!("invalid private key hex: {e}")))?;
|
|
141
|
+
if bytes.len() != 32 {
|
|
142
|
+
return Err(napi::Error::from_reason(format!(
|
|
143
|
+
"invalid private key length: expected 32 bytes, got {}",
|
|
144
|
+
bytes.len()
|
|
145
|
+
)));
|
|
146
|
+
}
|
|
147
|
+
Ok(bytes)
|
|
148
|
+
}
|
|
130
149
|
|
|
150
|
+
/// Encrypt an in-memory private key into the version-3 keystore JSON that
|
|
151
|
+
/// `createWallet` produces (scrypt KDF + AES-256-GCM). Shared by `create_wallet`
|
|
152
|
+
/// (fresh random key) and `import_wallet` (externally-provided key) so both
|
|
153
|
+
/// emit byte-for-byte the same keystore shape that `decrypt_wallet` /
|
|
154
|
+
/// `get_wallet_address` read back.
|
|
155
|
+
fn encrypt_private_key_to_keystore(
|
|
156
|
+
secret_private_key: &SecretBytes,
|
|
157
|
+
passphrase: &str,
|
|
158
|
+
) -> napi::Result<String> {
|
|
131
159
|
let address = derive_address_from_private_key(secret_private_key.as_slice())?;
|
|
132
160
|
|
|
133
161
|
let mut salt = [0u8; 32];
|
|
@@ -181,6 +209,27 @@ pub fn create_wallet(passphrase: String) -> napi::Result<String> {
|
|
|
181
209
|
.map_err(|e| napi::Error::from_reason(format!("keystore serialization failed: {e}")))
|
|
182
210
|
}
|
|
183
211
|
|
|
212
|
+
#[napi(js_name = "createWallet")]
|
|
213
|
+
pub fn create_wallet(passphrase: String) -> napi::Result<String> {
|
|
214
|
+
let mut private_key = [0u8; 32];
|
|
215
|
+
OsRng.fill_bytes(&mut private_key);
|
|
216
|
+
let secret_private_key = SecretBytes::new(private_key.to_vec());
|
|
217
|
+
private_key.zeroize();
|
|
218
|
+
|
|
219
|
+
encrypt_private_key_to_keystore(&secret_private_key, &passphrase)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/// Import an externally-provided raw private key (e.g. an Option-A session key
|
|
223
|
+
/// generated + passkey-approved in the web app and pasted into the CLI) into
|
|
224
|
+
/// the SAME passphrase-encrypted keystore format `create_wallet` emits. The key
|
|
225
|
+
/// never leaves the machine — Ametyst never receives it (ADR-001 non-custodial).
|
|
226
|
+
#[napi(js_name = "importWallet")]
|
|
227
|
+
pub fn import_wallet(private_key: String, passphrase: String) -> napi::Result<String> {
|
|
228
|
+
let key_bytes = parse_private_key_hex(&private_key)?;
|
|
229
|
+
let secret_private_key = SecretBytes::new(key_bytes);
|
|
230
|
+
encrypt_private_key_to_keystore(&secret_private_key, &passphrase)
|
|
231
|
+
}
|
|
232
|
+
|
|
184
233
|
#[napi(js_name = "decryptWallet")]
|
|
185
234
|
pub fn decrypt_wallet(keystore_json: String, passphrase: String) -> napi::Result<String> {
|
|
186
235
|
let keystore: Keystore = serde_json::from_str(&keystore_json)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ametyst/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.34",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Ametyst CLI — embedded MCP server, wallet ops, agent payments",
|
|
6
6
|
"type": "module",
|
|
@@ -31,18 +31,20 @@
|
|
|
31
31
|
"additional": [
|
|
32
32
|
"aarch64-apple-darwin",
|
|
33
33
|
"x86_64-apple-darwin",
|
|
34
|
-
"x86_64-unknown-linux-gnu"
|
|
34
|
+
"x86_64-unknown-linux-gnu",
|
|
35
|
+
"x86_64-pc-windows-msvc"
|
|
35
36
|
]
|
|
36
37
|
}
|
|
37
38
|
},
|
|
38
39
|
"dependencies": {
|
|
40
|
+
"@ametyst-dev/sdk-prod": "^0.8.29",
|
|
39
41
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
40
42
|
"@napi-rs/keyring": "1.1.7",
|
|
41
43
|
"mcp-use": "1.24.2"
|
|
42
44
|
},
|
|
43
45
|
"_comment_optionalDependencies": "Injected at publish-time by publish-staging.yml / publish-prod.yml via `npm pkg set` with the version being published. Not committed here so pnpm install (locally + CI) doesn't try to resolve sub-package versions that may not exist on the registry between releases.",
|
|
44
46
|
"devDependencies": {
|
|
45
|
-
"@ametyst-dev/sdk-prod": "^0.
|
|
47
|
+
"@ametyst-dev/sdk-prod": "^0.8.29",
|
|
46
48
|
"@napi-rs/cli": "2.18.4",
|
|
47
49
|
"@types/node": "20.19.39",
|
|
48
50
|
"@types/prompts": "2.4.9",
|
|
@@ -54,15 +56,32 @@
|
|
|
54
56
|
"tsx": "4.19.2",
|
|
55
57
|
"typescript": "5.2.2",
|
|
56
58
|
"viem": "2.38.6",
|
|
57
|
-
"vitest": "2.
|
|
59
|
+
"vitest": "3.2.6"
|
|
58
60
|
},
|
|
59
61
|
"publishConfig": {
|
|
60
62
|
"registry": "https://registry.npmjs.org"
|
|
61
63
|
},
|
|
64
|
+
"_comment_overrides": "Security overrides: pin transitive deps to their first patched version to clear npm/pnpm audit advisories (bn.js infinite-loop, ws memory-disclosure/DoS, plus the rest of the flagged transitive tree). All within-major to avoid breaking changes. The npm `overrides` block below mirrors `pnpm.overrides` so a GLOBAL npm install of the published package (where the cli is the install root) inherits the same patched resolution; note a project that installs the cli as a *dependency* will NOT inherit these — the consuming project must add its own overrides.",
|
|
65
|
+
"overrides": {
|
|
66
|
+
"bn.js@<4.12.3": "4.12.4",
|
|
67
|
+
"ws@>=8.0.0 <8.21.0": "8.21.0",
|
|
68
|
+
"protobufjs@<7.6.3": "7.6.4",
|
|
69
|
+
"form-data@<4.0.6": "4.0.6",
|
|
70
|
+
"tar@<7.5.16": "7.5.19",
|
|
71
|
+
"dompurify@<3.4.11": "3.4.11",
|
|
72
|
+
"@opentelemetry/core@<2.8.0": "2.8.0",
|
|
73
|
+
"hono@<4.12.25": "4.12.27",
|
|
74
|
+
"vite@>=8.0.0 <8.0.16": "8.1.2",
|
|
75
|
+
"vite@>=5.0.0 <6.4.3": "6.4.3",
|
|
76
|
+
"vitest@<3.2.6": "3.2.6",
|
|
77
|
+
"esbuild@<0.25.0": "0.25.12",
|
|
78
|
+
"esbuild@>=0.27.3 <0.28.1": "0.28.1"
|
|
79
|
+
},
|
|
62
80
|
"optionalDependencies": {
|
|
63
|
-
"@ametyst/cli-darwin-arm64": "0.2.
|
|
64
|
-
"@ametyst/cli-darwin-x64": "0.2.
|
|
65
|
-
"@ametyst/cli-linux-x64-gnu": "0.2.
|
|
81
|
+
"@ametyst/cli-darwin-arm64": "0.2.34",
|
|
82
|
+
"@ametyst/cli-darwin-x64": "0.2.34",
|
|
83
|
+
"@ametyst/cli-linux-x64-gnu": "0.2.34",
|
|
84
|
+
"@ametyst/cli-win32-x64-msvc": "0.2.34"
|
|
66
85
|
},
|
|
67
86
|
"scripts": {
|
|
68
87
|
"build:native": "cd native && cargo build --release && napi build --release",
|