Skip to content

Commit efcd6c8

Browse files
authored
Merge pull request #14 from BofAI/feat/windows-support
feat: windows compatibale
2 parents 22bd833 + e4abbe3 commit efcd6c8

11 files changed

Lines changed: 113 additions & 15 deletions

File tree

.github/workflows/ci.yml

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ on:
99

1010
jobs:
1111
python:
12-
name: Python
13-
runs-on: ubuntu-latest
12+
name: Python (${{ matrix.os }})
13+
runs-on: ${{ matrix.os }}
14+
strategy:
15+
matrix:
16+
os: [ubuntu-latest, windows-latest]
1417

1518
defaults:
1619
run:
@@ -42,9 +45,21 @@ jobs:
4245
- name: Build
4346
run: python -m build --no-isolation
4447

48+
- name: Smoke test CLI
49+
env:
50+
AGENT_WALLET_DIR: ${{ runner.temp }}/agent-wallet-python-smoke
51+
run: |
52+
agent-wallet init -p "Abc12345!"
53+
agent-wallet add local_secure -w wallet_1 -g -p "Abc12345!"
54+
agent-wallet sign msg "hello world" -n tron -p "Abc12345!"
55+
python -c "from pathlib import Path; import os; d = Path(os.environ['AGENT_WALLET_DIR']); expected = ['master.json', 'wallets_config.json', 'secret_wallet_1.json']; missing = [name for name in expected if not (d / name).exists()]; assert not missing, f'Missing files in {d}: {missing}'; print(sorted(p.name for p in d.iterdir()))"
56+
4557
typescript:
46-
name: TypeScript
47-
runs-on: ubuntu-latest
58+
name: TypeScript (${{ matrix.os }})
59+
runs-on: ${{ matrix.os }}
60+
strategy:
61+
matrix:
62+
os: [ubuntu-latest, windows-latest]
4863

4964
defaults:
5065
run:
@@ -77,3 +92,12 @@ jobs:
7792

7893
- name: Build
7994
run: pnpm build
95+
96+
- name: Smoke test CLI
97+
env:
98+
AGENT_WALLET_DIR: ${{ runner.temp }}/agent-wallet-typescript-smoke
99+
run: |
100+
node dist/delivery/bin.js init -p "Abc12345!"
101+
node dist/delivery/bin.js add local_secure -w wallet_1 -g -p "Abc12345!"
102+
node dist/delivery/bin.js sign msg "hello world" -n tron -p "Abc12345!"
103+
node -e "const fs = require('node:fs'); const path = require('node:path'); const dir = process.env.AGENT_WALLET_DIR; const expected = ['master.json', 'wallets_config.json', 'secret_wallet_1.json']; const missing = expected.filter((name) => !fs.existsSync(path.join(dir, name))); if (missing.length) { throw new Error('Missing files in ' + dir + ': ' + missing.join(', ')) } console.log(fs.readdirSync(dir).sort())"

packages/python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "bankofai-agent-wallet"
7-
version = "2.3.0"
7+
version = "2.3.1"
88
description = "Universal multi-chain secure signing SDK for AI agents"
99
readme = "README.md"
1010
license = {text = "MIT"}

packages/python/src/agent_wallet/core/providers/config_provider.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
from __future__ import annotations
44

55
import json
6-
import os
7-
import stat
86
from collections.abc import Callable
97
from pathlib import Path
108

@@ -21,6 +19,7 @@
2119
from agent_wallet.core.providers.wallet_builder import (
2220
create_adapter,
2321
)
22+
from agent_wallet.core.utils import safe_chmod
2423

2524

2625
class ConfigWalletProvider(WalletProvider):
@@ -158,7 +157,7 @@ async def get_active_wallet(self, network: str | None = None) -> Wallet:
158157
def _ensure_dir(self) -> None:
159158
path = Path(self._config_dir)
160159
path.mkdir(parents=True, exist_ok=True)
161-
os.chmod(path, stat.S_IRWXU)
160+
safe_chmod(path, 0o700)
162161

163162
def _persist(self) -> None:
164163
self._ensure_dir()
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""Cross-platform utilities."""
2+
3+
from __future__ import annotations
4+
5+
import os
6+
import warnings
7+
from pathlib import Path
8+
9+
10+
def safe_chmod(path: str | Path, mode: int) -> None:
11+
"""Attempt ``os.chmod`` and warn instead of crashing on unsupported platforms."""
12+
try:
13+
os.chmod(path, mode)
14+
except (OSError, NotImplementedError):
15+
warnings.warn(
16+
f"Could not set file permissions on {path}. "
17+
"On Windows this is expected — secrets are still protected by encryption.",
18+
stacklevel=2,
19+
)

packages/python/src/agent_wallet/delivery/cli.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import json
77
import os
88
import secrets
9-
import stat
109
import string
1110
import sys
1211
from pathlib import Path
@@ -37,6 +36,7 @@
3736
derive_key_from_mnemonic,
3837
parse_network_family,
3938
)
39+
from agent_wallet.core.utils import safe_chmod
4040
from agent_wallet.local.kv_store import SecureKVStore
4141
from agent_wallet.local.secret_loader import load_local_secret
4242

@@ -659,7 +659,7 @@ def start(
659659
auto_generated = True
660660

661661
secrets_path.mkdir(parents=True, exist_ok=True)
662-
os.chmod(secrets_path, stat.S_IRWXU)
662+
safe_chmod(secrets_path, 0o700)
663663
kv_store = SecureKVStore(dir, pw)
664664
kv_store.init_master()
665665
provider.ensure_storage()
@@ -756,7 +756,7 @@ def init(
756756
raise typer.Exit(1)
757757

758758
secrets_path.mkdir(parents=True, exist_ok=True)
759-
os.chmod(secrets_path, stat.S_IRWXU) # 700
759+
safe_chmod(secrets_path, 0o700)
760760

761761
provider = _get_provider(dir)
762762
console.print(PASSWORD_REQUIREMENTS_HINT)

packages/python/src/agent_wallet/local/kv_store.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from eth_hash.auto import keccak
1212

1313
from agent_wallet.core.errors import DecryptionError
14+
from agent_wallet.core.utils import safe_chmod
1415

1516
# Keystore V3 scrypt parameters
1617
SCRYPT_N = 262144
@@ -167,3 +168,4 @@ def _write_json(self, filename: str, data: dict) -> None:
167168
json.dumps(data, indent=2, ensure_ascii=False) + "\n",
168169
encoding="utf-8",
169170
)
171+
safe_chmod(path, 0o600)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Tests for cross-platform utilities."""
2+
3+
from __future__ import annotations
4+
5+
import os
6+
import stat
7+
import warnings
8+
from pathlib import Path
9+
from unittest.mock import patch
10+
11+
from agent_wallet.core.utils import safe_chmod
12+
13+
14+
def test_safe_chmod_sets_permissions(tmp_path: Path) -> None:
15+
f = tmp_path / "secret.json"
16+
f.write_text("{}")
17+
safe_chmod(f, stat.S_IRUSR | stat.S_IWUSR)
18+
if os.name == "nt":
19+
assert f.exists()
20+
return
21+
assert f.stat().st_mode & 0o777 == 0o600
22+
23+
24+
def test_safe_chmod_warns_on_failure(tmp_path: Path) -> None:
25+
f = tmp_path / "secret.json"
26+
f.write_text("{}")
27+
with patch("agent_wallet.core.utils.os.chmod", side_effect=OSError("not supported")):
28+
with warnings.catch_warnings(record=True) as w:
29+
warnings.simplefilter("always")
30+
safe_chmod(f, stat.S_IRUSR | stat.S_IWUSR)
31+
assert len(w) == 1
32+
assert "Could not set file permissions" in str(w[0].message)

packages/typescript/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@bankofai/agent-wallet",
3-
"version": "2.3.0",
3+
"version": "2.3.1",
44
"description": "Universal multi-chain secure signing SDK for AI agents",
55
"type": "module",
66
"main": "./dist/index.cjs",

packages/typescript/src/core/resolver.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ export async function resolveWallet(options?: {
8383
// ---------------------------------------------------------------------------
8484

8585
function expandTilde(p: string): string {
86-
if (p === '~' || p.startsWith('~/')) return join(homedir(), p.slice(1))
86+
if (p === '~' || p.startsWith('~/') || p.startsWith('~\\')) return join(homedir(), p.slice(2))
8787
return p
8888
}
8989

packages/typescript/src/delivery/cli.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ export function deriveKeyFromMnemonic(
6363
return privateKey
6464
}
6565

66-
function expandTilde(p: string): string {
67-
if (p === '~' || p.startsWith('~/')) return join(homedir(), p.slice(1))
66+
export function expandTilde(p: string): string {
67+
if (p === '~' || p.startsWith('~/') || p.startsWith('~\\')) return join(homedir(), p.slice(2))
6868
return p
6969
}
7070

0 commit comments

Comments
 (0)