aipou-mcp-server 0.3.1 → 0.5.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/NOTICE +8 -0
- package/README.md +25 -0
- package/dist/collector.js +1 -20
- package/dist/doctor.d.ts +32 -0
- package/dist/doctor.js +112 -0
- package/dist/doctor.test.d.ts +1 -0
- package/dist/doctor.test.js +162 -0
- package/dist/identity.js +16 -2
- package/dist/index.js +13807 -78
- package/dist/init.d.ts +32 -0
- package/dist/init.js +69 -0
- package/dist/init.test.d.ts +1 -0
- package/dist/init.test.js +113 -0
- package/dist/receipt-metadata.d.ts +5 -0
- package/dist/receipt-metadata.js +16 -0
- package/dist/receipt-metadata.test.d.ts +1 -0
- package/dist/receipt-metadata.test.js +22 -0
- package/dist/secure-file.d.ts +1 -0
- package/dist/secure-file.js +21 -0
- package/package.json +7 -5
- package/server.json +11 -4
package/NOTICE
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
AI Proof of Us (AIPOU) MCP Server
|
|
2
|
+
Copyright (c) 2026 0xddneto and AI Proof of Us contributors
|
|
3
|
+
|
|
4
|
+
This package bundles the stdio runtime from the Model Context Protocol
|
|
5
|
+
TypeScript SDK, Copyright (c) 2024 Anthropic, PBC, under the MIT License.
|
|
6
|
+
Source: https://github.com/modelcontextprotocol/typescript-sdk
|
|
7
|
+
|
|
8
|
+
The software and bundled dependency are distributed under the MIT License.
|
package/README.md
CHANGED
|
@@ -16,6 +16,27 @@ The command needs no wallet setup, funds, network, or claims. It prints a
|
|
|
16
16
|
framework-ready `workReceiptId` object and deletes its ephemeral wallet,
|
|
17
17
|
collector key, and receipt state before exiting.
|
|
18
18
|
|
|
19
|
+
To create a persistent dedicated wallet without printing its private key:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx -y aipou-mcp-server --init
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The command writes a protected `agent-wallet.key` and prints an MCP
|
|
26
|
+
configuration using `AIPOU_AGENT_KEY_FILE`. Re-running it reports the existing
|
|
27
|
+
identity instead of overwriting the key.
|
|
28
|
+
|
|
29
|
+
Verify the persistent setup without writing files, contacting an RPC, moving
|
|
30
|
+
funds, or submitting a claim:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx -y aipou-mcp-server --doctor
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The diagnostic returns structured checks for Node.js, storage, identity,
|
|
37
|
+
contract addresses, and chain. It prints only the public wallet address, never
|
|
38
|
+
the private key.
|
|
39
|
+
|
|
19
40
|
From a source checkout, the same check runs with:
|
|
20
41
|
|
|
21
42
|
```bash
|
|
@@ -69,6 +90,10 @@ AIPOU_CONTRACT_ADDRESS=0x55f0Cc5e51A1284D20337d6cbb18938C8A1ABCbB
|
|
|
69
90
|
AIPOU_CLAIMS_ADDRESS=0x4ca4C98fB784D20EdC8E2A7F531dAab4c6e53058
|
|
70
91
|
```
|
|
71
92
|
|
|
93
|
+
`AIPOU_AGENT_KEY_FILE=/protected/path/agent-wallet.key` is the recommended
|
|
94
|
+
alternative to the inline private-key variable. One of the two identity options
|
|
95
|
+
is required for persistent receipt collection.
|
|
96
|
+
|
|
72
97
|
Use a new dedicated farming wallet, never a primary wallet. Do not commit the private key. Status checks can show already claimed and pending AIPOU without moving funds. Optional claims and settlement occur only after an explicit user request.
|
|
73
98
|
|
|
74
99
|
Do not configure `AIPOU_VALIDATOR_PRIVATE_KEY` on a user installation. That key is only for the separate protocol validator service. The local Ed25519 collector key and receipt metadata are stored unencrypted under `AIPOU_DATA_DIR`; restrict that directory with operating-system permissions and use encrypted backups.
|
package/dist/collector.js
CHANGED
|
@@ -1,27 +1,8 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
1
|
import { createHash, createPublicKey, generateKeyPairSync, sign, verify } from "node:crypto";
|
|
3
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
3
|
import path from "node:path";
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
4
|
import { canonicalJson } from "./canonical.js";
|
|
7
|
-
|
|
8
|
-
// mode 0o600 is a no-op on NTFS, so on Windows the private key would keep the
|
|
9
|
-
// permissive inherited ACL. Strip inheritance and grant only the current user.
|
|
10
|
-
// Best-effort: an ACL failure must not block receipt collection.
|
|
11
|
-
async function restrictToCurrentUser(filePath) {
|
|
12
|
-
if (process.platform !== "win32")
|
|
13
|
-
return;
|
|
14
|
-
const user = process.env.USERNAME;
|
|
15
|
-
if (!user)
|
|
16
|
-
return;
|
|
17
|
-
const principal = process.env.USERDOMAIN ? `${process.env.USERDOMAIN}\\${user}` : user;
|
|
18
|
-
try {
|
|
19
|
-
await execFileAsync("icacls", [filePath, "/inheritance:r", "/grant:r", `${principal}:F`]);
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
// keep the inherited ACL rather than failing key creation
|
|
23
|
-
}
|
|
24
|
-
}
|
|
5
|
+
import { restrictToCurrentUser } from "./secure-file.js";
|
|
25
6
|
function dataDir() {
|
|
26
7
|
return path.resolve(process.env.AIPOU_DATA_DIR || ".aipou");
|
|
27
8
|
}
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
type CheckStatus = "pass" | "fail" | "warning";
|
|
2
|
+
export interface DoctorCheck {
|
|
3
|
+
status: CheckStatus;
|
|
4
|
+
detail: string;
|
|
5
|
+
}
|
|
6
|
+
export interface InstallationDoctorResult {
|
|
7
|
+
mode: "installation_doctor";
|
|
8
|
+
ready: boolean;
|
|
9
|
+
version: string;
|
|
10
|
+
wallet: string | null;
|
|
11
|
+
dataDir: string;
|
|
12
|
+
keyFile: string | null;
|
|
13
|
+
keySource: "key_file" | "inline_private_key" | "missing";
|
|
14
|
+
checks: {
|
|
15
|
+
node: DoctorCheck;
|
|
16
|
+
dataDirectory: DoctorCheck;
|
|
17
|
+
identity: DoctorCheck;
|
|
18
|
+
keyPermissions: DoctorCheck;
|
|
19
|
+
tokenAddress: DoctorCheck;
|
|
20
|
+
claimsAddress: DoctorCheck;
|
|
21
|
+
chain: DoctorCheck;
|
|
22
|
+
};
|
|
23
|
+
safety: {
|
|
24
|
+
privateKeyPrinted: false;
|
|
25
|
+
filesWritten: false;
|
|
26
|
+
networkRequestsMade: false;
|
|
27
|
+
fundsMoved: false;
|
|
28
|
+
claimSubmitted: false;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export declare function runInstallationDoctor(dataDirInput?: string): Promise<InstallationDoctorResult>;
|
|
32
|
+
export {};
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { access, readFile, stat } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { Wallet, isAddress } from "ethers";
|
|
6
|
+
import { AIPOU_CLAIMS_ADDRESS, AIPOU_TOKEN_ADDRESS } from "./init.js";
|
|
7
|
+
import { getPackageVersion } from "./version.js";
|
|
8
|
+
function pass(detail) {
|
|
9
|
+
return { status: "pass", detail };
|
|
10
|
+
}
|
|
11
|
+
function fail(detail) {
|
|
12
|
+
return { status: "fail", detail };
|
|
13
|
+
}
|
|
14
|
+
function warning(detail) {
|
|
15
|
+
return { status: "warning", detail };
|
|
16
|
+
}
|
|
17
|
+
export async function runInstallationDoctor(dataDirInput) {
|
|
18
|
+
const dataDir = path.resolve(dataDirInput || process.env.AIPOU_DATA_DIR || path.join(os.homedir(), ".aipou"));
|
|
19
|
+
// An explicit directory is an operator choice and must not be shadowed by
|
|
20
|
+
// inherited client or dotenv identity variables.
|
|
21
|
+
const configuredKeyFile = dataDirInput ? undefined : process.env.AIPOU_AGENT_KEY_FILE?.trim();
|
|
22
|
+
const inlinePrivateKey = dataDirInput ? undefined : process.env.AIPOU_AGENT_PRIVATE_KEY?.trim();
|
|
23
|
+
const keyFile = inlinePrivateKey ? null : path.resolve(configuredKeyFile || path.join(dataDir, "agent-wallet.key"));
|
|
24
|
+
const nodeMajor = Number(process.versions.node.split(".")[0]);
|
|
25
|
+
const tokenAddress = process.env.AIPOU_CONTRACT_ADDRESS?.trim() || AIPOU_TOKEN_ADDRESS;
|
|
26
|
+
const claimsAddress = process.env.AIPOU_CLAIMS_ADDRESS?.trim() || AIPOU_CLAIMS_ADDRESS;
|
|
27
|
+
const chainId = Number(process.env.AIPOU_CHAIN_ID || 8453);
|
|
28
|
+
let dataDirectory = pass("Receipt directory is readable and writable.");
|
|
29
|
+
try {
|
|
30
|
+
await access(dataDir, constants.R_OK | constants.W_OK);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
dataDirectory = fail(`Receipt directory is unavailable (${dataDir}); run aipou-mcp --init first.`);
|
|
34
|
+
}
|
|
35
|
+
let wallet = null;
|
|
36
|
+
let identity;
|
|
37
|
+
let keySource;
|
|
38
|
+
try {
|
|
39
|
+
const privateKey = inlinePrivateKey || (keyFile ? (await readFile(keyFile, "utf8")).trim() : "");
|
|
40
|
+
if (!privateKey)
|
|
41
|
+
throw new Error("missing");
|
|
42
|
+
wallet = new Wallet(privateKey).address;
|
|
43
|
+
keySource = inlinePrivateKey ? "inline_private_key" : "key_file";
|
|
44
|
+
identity = inlinePrivateKey
|
|
45
|
+
? warning("Dedicated wallet key is valid, but a protected key file is safer than an inline environment variable.")
|
|
46
|
+
: pass("Dedicated-wallet key file is readable and valid; its secret was not returned.");
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
keySource = "missing";
|
|
50
|
+
identity = fail(`Dedicated wallet key is missing or invalid${keyFile ? ` (${keyFile})` : ""}; run aipou-mcp --init.`);
|
|
51
|
+
}
|
|
52
|
+
let keyPermissions;
|
|
53
|
+
if (inlinePrivateKey) {
|
|
54
|
+
keyPermissions = warning("Inline private-key permissions cannot be checked; use the key file created by aipou-mcp --init.");
|
|
55
|
+
}
|
|
56
|
+
else if (!keyFile) {
|
|
57
|
+
keyPermissions = fail("No dedicated-wallet key file is configured.");
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
try {
|
|
61
|
+
const keyStat = await stat(keyFile);
|
|
62
|
+
if (process.platform === "win32") {
|
|
63
|
+
keyPermissions = warning("Key file exists; Windows ACLs were not changed or re-applied by this read-only diagnostic.");
|
|
64
|
+
}
|
|
65
|
+
else if ((keyStat.mode & 0o077) === 0) {
|
|
66
|
+
keyPermissions = pass("Key file has no group or other permission bits.");
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
keyPermissions = fail("Key file is accessible by group or other users; restrict it to mode 0600.");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
keyPermissions = fail(`Key file permissions could not be inspected (${keyFile}).`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const checks = {
|
|
77
|
+
node: Number.isInteger(nodeMajor) && nodeMajor >= 20
|
|
78
|
+
? pass(`Node.js ${process.versions.node} satisfies the required major version (20+).`)
|
|
79
|
+
: fail(`Node.js ${process.versions.node} is unsupported; install Node.js 20 or newer.`),
|
|
80
|
+
dataDirectory,
|
|
81
|
+
identity,
|
|
82
|
+
keyPermissions,
|
|
83
|
+
tokenAddress: isAddress(tokenAddress)
|
|
84
|
+
? pass(`AIPOU token address is valid (${tokenAddress}).`)
|
|
85
|
+
: fail("AIPOU_CONTRACT_ADDRESS is not a valid EVM address."),
|
|
86
|
+
claimsAddress: isAddress(claimsAddress)
|
|
87
|
+
? pass(`AIPOU claims address is valid (${claimsAddress}).`)
|
|
88
|
+
: fail("AIPOU_CLAIMS_ADDRESS is not a valid EVM address."),
|
|
89
|
+
chain: Number.isSafeInteger(chainId) && chainId > 0
|
|
90
|
+
? (chainId === 8453
|
|
91
|
+
? pass("Chain ID is Base Mainnet (8453).")
|
|
92
|
+
: warning(`Chain ID ${chainId} is configured instead of Base Mainnet (8453).`))
|
|
93
|
+
: fail("AIPOU_CHAIN_ID must be a positive integer.")
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
mode: "installation_doctor",
|
|
97
|
+
ready: Object.values(checks).every((check) => check.status !== "fail"),
|
|
98
|
+
version: await getPackageVersion(),
|
|
99
|
+
wallet,
|
|
100
|
+
dataDir,
|
|
101
|
+
keyFile,
|
|
102
|
+
keySource,
|
|
103
|
+
checks,
|
|
104
|
+
safety: {
|
|
105
|
+
privateKeyPrinted: false,
|
|
106
|
+
filesWritten: false,
|
|
107
|
+
networkRequestsMade: false,
|
|
108
|
+
fundsMoved: false,
|
|
109
|
+
claimSubmitted: false
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { access, mkdtemp, readFile, rm } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { promisify } from "node:util";
|
|
9
|
+
import { Wallet } from "ethers";
|
|
10
|
+
import { runInstallationDoctor } from "./doctor.js";
|
|
11
|
+
import { initializePersistentIdentity } from "./init.js";
|
|
12
|
+
const execFileAsync = promisify(execFile);
|
|
13
|
+
test("installation doctor validates init output without exposing or changing the key", async () => {
|
|
14
|
+
const dataDir = await mkdtemp(path.join(os.tmpdir(), "aipou-doctor-test-"));
|
|
15
|
+
const previousInlineKey = process.env.AIPOU_AGENT_PRIVATE_KEY;
|
|
16
|
+
const previousKeyFile = process.env.AIPOU_AGENT_KEY_FILE;
|
|
17
|
+
try {
|
|
18
|
+
delete process.env.AIPOU_AGENT_PRIVATE_KEY;
|
|
19
|
+
delete process.env.AIPOU_AGENT_KEY_FILE;
|
|
20
|
+
const initialized = await initializePersistentIdentity(dataDir);
|
|
21
|
+
const privateKeyBefore = await readFile(initialized.keyFile, "utf8");
|
|
22
|
+
const result = await runInstallationDoctor(dataDir);
|
|
23
|
+
const privateKeyAfter = await readFile(initialized.keyFile, "utf8");
|
|
24
|
+
assert.equal(result.ready, true);
|
|
25
|
+
assert.equal(result.wallet, initialized.wallet);
|
|
26
|
+
assert.equal(result.keySource, "key_file");
|
|
27
|
+
assert.equal(result.version.length > 0, true);
|
|
28
|
+
assert.equal(Object.values(result.checks).every((check) => check.status !== "fail"), true);
|
|
29
|
+
assert.equal(privateKeyAfter, privateKeyBefore);
|
|
30
|
+
assert.equal(JSON.stringify(result).includes(privateKeyBefore.trim()), false);
|
|
31
|
+
assert.deepEqual(result.safety, {
|
|
32
|
+
privateKeyPrinted: false,
|
|
33
|
+
filesWritten: false,
|
|
34
|
+
networkRequestsMade: false,
|
|
35
|
+
fundsMoved: false,
|
|
36
|
+
claimSubmitted: false
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
if (previousInlineKey === undefined)
|
|
41
|
+
delete process.env.AIPOU_AGENT_PRIVATE_KEY;
|
|
42
|
+
else
|
|
43
|
+
process.env.AIPOU_AGENT_PRIVATE_KEY = previousInlineKey;
|
|
44
|
+
if (previousKeyFile === undefined)
|
|
45
|
+
delete process.env.AIPOU_AGENT_KEY_FILE;
|
|
46
|
+
else
|
|
47
|
+
process.env.AIPOU_AGENT_KEY_FILE = previousKeyFile;
|
|
48
|
+
await rm(dataDir, { recursive: true, force: true });
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
test("installation doctor fails closed without creating a missing directory", async () => {
|
|
52
|
+
const parent = await mkdtemp(path.join(os.tmpdir(), "aipou-doctor-missing-test-"));
|
|
53
|
+
const dataDir = path.join(parent, "missing");
|
|
54
|
+
const previousInlineKey = process.env.AIPOU_AGENT_PRIVATE_KEY;
|
|
55
|
+
const previousKeyFile = process.env.AIPOU_AGENT_KEY_FILE;
|
|
56
|
+
try {
|
|
57
|
+
delete process.env.AIPOU_AGENT_PRIVATE_KEY;
|
|
58
|
+
delete process.env.AIPOU_AGENT_KEY_FILE;
|
|
59
|
+
const result = await runInstallationDoctor(dataDir);
|
|
60
|
+
assert.equal(result.ready, false);
|
|
61
|
+
assert.equal(result.wallet, null);
|
|
62
|
+
assert.equal(result.keySource, "missing");
|
|
63
|
+
assert.equal(result.checks.dataDirectory.status, "fail");
|
|
64
|
+
assert.equal(result.checks.identity.status, "fail");
|
|
65
|
+
await assert.rejects(access(dataDir));
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
if (previousInlineKey === undefined)
|
|
69
|
+
delete process.env.AIPOU_AGENT_PRIVATE_KEY;
|
|
70
|
+
else
|
|
71
|
+
process.env.AIPOU_AGENT_PRIVATE_KEY = previousInlineKey;
|
|
72
|
+
if (previousKeyFile === undefined)
|
|
73
|
+
delete process.env.AIPOU_AGENT_KEY_FILE;
|
|
74
|
+
else
|
|
75
|
+
process.env.AIPOU_AGENT_KEY_FILE = previousKeyFile;
|
|
76
|
+
await rm(parent, { recursive: true, force: true });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
test("installation doctor supports the legacy inline key without exposing it", async () => {
|
|
80
|
+
const dataDir = await mkdtemp(path.join(os.tmpdir(), "aipou-doctor-inline-test-"));
|
|
81
|
+
const previousInlineKey = process.env.AIPOU_AGENT_PRIVATE_KEY;
|
|
82
|
+
const previousKeyFile = process.env.AIPOU_AGENT_KEY_FILE;
|
|
83
|
+
const previousDataDir = process.env.AIPOU_DATA_DIR;
|
|
84
|
+
const wallet = Wallet.createRandom();
|
|
85
|
+
try {
|
|
86
|
+
process.env.AIPOU_AGENT_PRIVATE_KEY = wallet.privateKey;
|
|
87
|
+
process.env.AIPOU_DATA_DIR = dataDir;
|
|
88
|
+
delete process.env.AIPOU_AGENT_KEY_FILE;
|
|
89
|
+
const result = await runInstallationDoctor();
|
|
90
|
+
assert.equal(result.ready, true);
|
|
91
|
+
assert.equal(result.wallet, wallet.address);
|
|
92
|
+
assert.equal(result.keySource, "inline_private_key");
|
|
93
|
+
assert.equal(result.keyFile, null);
|
|
94
|
+
assert.equal(result.checks.identity.status, "warning");
|
|
95
|
+
assert.equal(result.checks.keyPermissions.status, "warning");
|
|
96
|
+
assert.equal(JSON.stringify(result).includes(wallet.privateKey), false);
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
if (previousInlineKey === undefined)
|
|
100
|
+
delete process.env.AIPOU_AGENT_PRIVATE_KEY;
|
|
101
|
+
else
|
|
102
|
+
process.env.AIPOU_AGENT_PRIVATE_KEY = previousInlineKey;
|
|
103
|
+
if (previousKeyFile === undefined)
|
|
104
|
+
delete process.env.AIPOU_AGENT_KEY_FILE;
|
|
105
|
+
else
|
|
106
|
+
process.env.AIPOU_AGENT_KEY_FILE = previousKeyFile;
|
|
107
|
+
if (previousDataDir === undefined)
|
|
108
|
+
delete process.env.AIPOU_DATA_DIR;
|
|
109
|
+
else
|
|
110
|
+
process.env.AIPOU_DATA_DIR = previousDataDir;
|
|
111
|
+
await rm(dataDir, { recursive: true, force: true });
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
test("installation doctor rejects invalid addresses and chain IDs without throwing", async () => {
|
|
115
|
+
const dataDir = await mkdtemp(path.join(os.tmpdir(), "aipou-doctor-config-test-"));
|
|
116
|
+
const previousContract = process.env.AIPOU_CONTRACT_ADDRESS;
|
|
117
|
+
const previousChainId = process.env.AIPOU_CHAIN_ID;
|
|
118
|
+
try {
|
|
119
|
+
await initializePersistentIdentity(dataDir);
|
|
120
|
+
process.env.AIPOU_CONTRACT_ADDRESS = "not-an-address";
|
|
121
|
+
process.env.AIPOU_CHAIN_ID = "not-a-chain";
|
|
122
|
+
const result = await runInstallationDoctor(dataDir);
|
|
123
|
+
assert.equal(result.ready, false);
|
|
124
|
+
assert.equal(result.checks.tokenAddress.status, "fail");
|
|
125
|
+
assert.equal(result.checks.chain.status, "fail");
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
if (previousContract === undefined)
|
|
129
|
+
delete process.env.AIPOU_CONTRACT_ADDRESS;
|
|
130
|
+
else
|
|
131
|
+
process.env.AIPOU_CONTRACT_ADDRESS = previousContract;
|
|
132
|
+
if (previousChainId === undefined)
|
|
133
|
+
delete process.env.AIPOU_CHAIN_ID;
|
|
134
|
+
else
|
|
135
|
+
process.env.AIPOU_CHAIN_ID = previousChainId;
|
|
136
|
+
await rm(dataDir, { recursive: true, force: true });
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
test("CLI doctor reports a ready persistent identity without printing its secret", async () => {
|
|
140
|
+
const dataDir = await mkdtemp(path.join(os.tmpdir(), "aipou-doctor-cli-test-"));
|
|
141
|
+
const indexPath = fileURLToPath(new URL("./index.js", import.meta.url));
|
|
142
|
+
try {
|
|
143
|
+
const initialized = await initializePersistentIdentity(dataDir);
|
|
144
|
+
const privateKey = (await readFile(initialized.keyFile, "utf8")).trim();
|
|
145
|
+
const inheritedWallet = Wallet.createRandom();
|
|
146
|
+
const { stdout, stderr } = await execFileAsync(process.execPath, [indexPath, "--doctor", "--data-dir", dataDir], {
|
|
147
|
+
env: {
|
|
148
|
+
...process.env,
|
|
149
|
+
AIPOU_AGENT_PRIVATE_KEY: inheritedWallet.privateKey,
|
|
150
|
+
AIPOU_AGENT_KEY_FILE: path.join(dataDir, "wrong-key-file")
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
const result = JSON.parse(stdout);
|
|
154
|
+
assert.equal(result.ready, true);
|
|
155
|
+
assert.equal(result.wallet, initialized.wallet);
|
|
156
|
+
assert.equal(stdout.includes(privateKey), false);
|
|
157
|
+
assert.equal(stderr, "");
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
await rm(dataDir, { recursive: true, force: true });
|
|
161
|
+
}
|
|
162
|
+
});
|
package/dist/identity.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
import { Wallet, getAddress, hexlify, isAddress, verifyTypedData } from "ethers";
|
|
3
5
|
export const authorizationTypes = {
|
|
4
6
|
TaskAuthorization: [
|
|
@@ -10,10 +12,22 @@ export const authorizationTypes = {
|
|
|
10
12
|
{ name: "issuedAt", type: "uint256" }
|
|
11
13
|
]
|
|
12
14
|
};
|
|
15
|
+
function readKeyFile(keyFile) {
|
|
16
|
+
try {
|
|
17
|
+
return readFileSync(path.resolve(keyFile), "utf8").trim();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
throw new Error(`AIPOU_AGENT_KEY_FILE could not be read (${keyFile}); ` +
|
|
21
|
+
"run aipou-mcp --init to create a dedicated wallet key");
|
|
22
|
+
}
|
|
23
|
+
}
|
|
13
24
|
export function agentWallet() {
|
|
14
|
-
const
|
|
25
|
+
const inlinePrivateKey = process.env.AIPOU_AGENT_PRIVATE_KEY?.trim();
|
|
26
|
+
const keyFile = process.env.AIPOU_AGENT_KEY_FILE;
|
|
27
|
+
const privateKey = inlinePrivateKey || (keyFile ? readKeyFile(keyFile) : undefined);
|
|
15
28
|
if (!privateKey) {
|
|
16
|
-
throw new Error("AIPOU_AGENT_PRIVATE_KEY is required;
|
|
29
|
+
throw new Error("AIPOU_AGENT_PRIVATE_KEY or AIPOU_AGENT_KEY_FILE is required; " +
|
|
30
|
+
"run aipou-mcp --init to create a dedicated wallet, never use a primary wallet");
|
|
17
31
|
}
|
|
18
32
|
return new Wallet(privateKey);
|
|
19
33
|
}
|