@ens-node-metadata/agent 0.2.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/LICENSE +661 -0
- package/README.md +126 -0
- package/SKILL.md +104 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +29 -0
- package/dist/commands/metadata/set.d.ts +21 -0
- package/dist/commands/metadata/set.d.ts.map +1 -0
- package/dist/commands/metadata/set.js +75 -0
- package/dist/commands/metadata/template.d.ts +4 -0
- package/dist/commands/metadata/template.d.ts.map +1 -0
- package/dist/commands/metadata/template.js +7 -0
- package/dist/commands/metadata/validate.d.ts +10 -0
- package/dist/commands/metadata/validate.d.ts.map +1 -0
- package/dist/commands/metadata/validate.js +44 -0
- package/dist/commands/register.d.ts +21 -0
- package/dist/commands/register.d.ts.map +1 -0
- package/dist/commands/register.js +76 -0
- package/dist/commands/registration-file/publish.d.ts +10 -0
- package/dist/commands/registration-file/publish.d.ts.map +1 -0
- package/dist/commands/registration-file/publish.js +79 -0
- package/dist/commands/registration-file/template.d.ts +3 -0
- package/dist/commands/registration-file/template.d.ts.map +1 -0
- package/dist/commands/registration-file/template.js +48 -0
- package/dist/commands/registration-file/validate.d.ts +10 -0
- package/dist/commands/registration-file/validate.d.ts.map +1 -0
- package/dist/commands/registration-file/validate.js +35 -0
- package/dist/commands/registry/identity.d.ts +18 -0
- package/dist/commands/registry/identity.d.ts.map +1 -0
- package/dist/commands/registry/identity.js +115 -0
- package/dist/commands/skill.d.ts +16 -0
- package/dist/commands/skill.d.ts.map +1 -0
- package/dist/commands/skill.js +53 -0
- package/dist/commands/update.d.ts +21 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +76 -0
- package/dist/index.d.ts +82 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/ens-write.d.ts +6 -0
- package/dist/lib/ens-write.d.ts.map +1 -0
- package/dist/lib/ens-write.js +23 -0
- package/dist/lib/ui.d.ts +6 -0
- package/dist/lib/ui.d.ts.map +1 -0
- package/dist/lib/ui.js +14 -0
- package/dist/types.d.ts +181 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +84 -0
- package/package.json +44 -0
- package/src/index.ts +8 -0
package/README.md
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# @ens-node-metadata/agent
|
|
2
|
+
|
|
3
|
+
CLI for registering AI agents on ENS using [ERC-8004](https://best-practices.8004scan.io/docs/01-agent-metadata-standard.html) (v2.0).
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm dlx @ens-node-metadata/agent --help
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Commands
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
agent skill [--install]
|
|
15
|
+
Print SKILL.md. With --install, copy it to the current directory.
|
|
16
|
+
|
|
17
|
+
agent registration-file template
|
|
18
|
+
Print an empty ERC-8004 v2.0 registration JSON to stdout.
|
|
19
|
+
|
|
20
|
+
agent registration-file validate <registration-file.json>
|
|
21
|
+
Validate against Zod schema. Prints ✅ or errors ❌.
|
|
22
|
+
Emits WA031 deprecation warning if the legacy `endpoints` field is used.
|
|
23
|
+
|
|
24
|
+
agent registration-file publish <registration-file.json>
|
|
25
|
+
Publish to IPFS via @web3-storage/w3up-client. Prints the ipfs:// URI.
|
|
26
|
+
Requires: W3_PRINCIPAL, W3_PROOF env vars (see below).
|
|
27
|
+
|
|
28
|
+
agent registry identity --chain-name <chain> <agent-uri>
|
|
29
|
+
Read the ERC-8004 Identity Registry for the given chain.
|
|
30
|
+
Supported chains: base, mainnet.
|
|
31
|
+
Requires: ERC8004_REGISTRY_<CHAIN> env var.
|
|
32
|
+
|
|
33
|
+
agent metadata template
|
|
34
|
+
Print a starter ENS metadata payload JSON (the text records to set).
|
|
35
|
+
|
|
36
|
+
agent metadata validate <payload.json>
|
|
37
|
+
Validate ENS metadata payload against the agent schema.
|
|
38
|
+
|
|
39
|
+
agent register <ENS> <payload.json> --private-key <key> [--broadcast]
|
|
40
|
+
Build ENS setTextRecords transaction. Dry run by default; --broadcast submits it.
|
|
41
|
+
Uses viem + @ensdomains/ensjs.
|
|
42
|
+
|
|
43
|
+
agent update <ENS> <payload.json> --private-key <key> [--broadcast]
|
|
44
|
+
Same as register — for updating existing records.
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Environment Variables
|
|
48
|
+
|
|
49
|
+
Optional
|
|
50
|
+
|
|
51
|
+
* PINATA_API_KEY
|
|
52
|
+
* PINATA_API_SECRET
|
|
53
|
+
* PINATA_JWT
|
|
54
|
+
|
|
55
|
+
## ERC-8004 v2.0 Registration File
|
|
56
|
+
|
|
57
|
+
The registration file is a JSON document published to IPFS or HTTPS. Its `ipfs://` URI is stored as the `agent-uri` ENS text record.
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"type": "Agent",
|
|
62
|
+
"name": "My Agent",
|
|
63
|
+
"description": "Does useful things on-chain.",
|
|
64
|
+
"services": [
|
|
65
|
+
{ "name": "MCP", "endpoint": "https://myagent.example.com/mcp", "version": "1.0" },
|
|
66
|
+
{ "name": "A2A", "endpoint": "https://myagent.example.com/a2a", "version": "0.3" }
|
|
67
|
+
],
|
|
68
|
+
"x402Support": false,
|
|
69
|
+
"active": true,
|
|
70
|
+
"registrations": [],
|
|
71
|
+
"supportedTrust": ["reputation"]
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
> ⚠️ **v2.0 migration:** The field name changed from `endpoints` → `services` (Jan 2026).
|
|
76
|
+
> The CLI accepts `endpoints` for backward compatibility with a WA031 deprecation warning.
|
|
77
|
+
|
|
78
|
+
## Registration Flow
|
|
79
|
+
|
|
80
|
+
1. `agent registration-file template > registration.json` — create the file
|
|
81
|
+
2. Edit `registration.json`
|
|
82
|
+
3. `agent registration-file validate registration.json` — validate
|
|
83
|
+
4. `agent registration-file publish registration.json` — publish to IPFS → get `ipfs://` URI
|
|
84
|
+
5. `agent metadata template > payload.json` — create the ENS record payload
|
|
85
|
+
6. Set `agent-uri` to the `ipfs://` URI in `payload.json`
|
|
86
|
+
7. `agent metadata validate payload.json` — validate
|
|
87
|
+
8. `agent register myagent.eth payload.json --private-key $PK --broadcast` — register
|
|
88
|
+
|
|
89
|
+
See [SKILL.md](./SKILL.md) for the full step-by-step guide.
|
|
90
|
+
|
|
91
|
+
## TypeScript API
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import {
|
|
95
|
+
buildRegistrationFile,
|
|
96
|
+
validateRegistrationFile,
|
|
97
|
+
AgentRegistrationFileSchema,
|
|
98
|
+
} from "@ens-node-metadata/agent";
|
|
99
|
+
|
|
100
|
+
// Build a registration file
|
|
101
|
+
const file = buildRegistrationFile({
|
|
102
|
+
name: "My AI Agent",
|
|
103
|
+
description: "Does useful things on-chain.",
|
|
104
|
+
services: [
|
|
105
|
+
{ name: "MCP", endpoint: "https://myagent.example.com/mcp", version: "1.0" },
|
|
106
|
+
],
|
|
107
|
+
active: true,
|
|
108
|
+
supportedTrust: ["reputation"],
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Validate an untrusted payload
|
|
112
|
+
const raw = JSON.parse(maybeInvalidJson);
|
|
113
|
+
const result = validateRegistrationFile(raw);
|
|
114
|
+
if (result.success) {
|
|
115
|
+
console.log(result.data.name);
|
|
116
|
+
// Check for legacy `endpoints` field usage
|
|
117
|
+
if (result.data._legacyEndpoints) {
|
|
118
|
+
console.warn("WA031: migrate from `endpoints` to `services`");
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Related Packages
|
|
124
|
+
|
|
125
|
+
* [`@ens-node-metadata/schemas`](../schemas) — JSON schemas for all ENS node types
|
|
126
|
+
* [`@ens-node-metadata/sdk`](../sdk) — ENS metadata read SDK
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ens-agent-registration
|
|
3
|
+
description: Register an AI agent on ENS using ERC-8004.
|
|
4
|
+
allowed-tools: Bash(node:*), Bash(ipfs:*), Bash(cast:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Agents
|
|
8
|
+
|
|
9
|
+
A skill to help manage your agents metadata on ENS along with ERC-8004 management.
|
|
10
|
+
|
|
11
|
+
## Bootstrap
|
|
12
|
+
|
|
13
|
+
* Ask your human, what is their agents ens name is?
|
|
14
|
+
* Hereby referred to as AGENT_ENS_NAME
|
|
15
|
+
|
|
16
|
+
## Command overview
|
|
17
|
+
|
|
18
|
+
Run `agent --help` or `agent <command> --help` for full usage.
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
agent registration-file template # prints example JSON
|
|
22
|
+
agent registration-file validate <file> # validates SCHEMA_8004_V2
|
|
23
|
+
agent registration-file publish <file> # upload to IPFS via Pinata
|
|
24
|
+
# => Returns <agent-uri>
|
|
25
|
+
|
|
26
|
+
# Register agent with canonical 8004 registries
|
|
27
|
+
agent registry identity --chain-name <chain> <agent-uri> --private-key <0x...> [--broadcast]
|
|
28
|
+
|
|
29
|
+
agent metadata template # starter ENS text-record payload
|
|
30
|
+
agent metadata validate <payload> # validates metadata JSON schema
|
|
31
|
+
agent metadata set <AGENT_ENS_NAME> <payload> --private-key <0x...> [--broadcast]
|
|
32
|
+
|
|
33
|
+
agent skill [--install] # Installs
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Workflows
|
|
37
|
+
|
|
38
|
+
### ERC-8004 operations
|
|
39
|
+
|
|
40
|
+
#### Building a registration file
|
|
41
|
+
|
|
42
|
+
A registration file is required to register your Agent's identity.
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
# edit with your details
|
|
46
|
+
agent registration-file template > registration.json`
|
|
47
|
+
|
|
48
|
+
# validate
|
|
49
|
+
agent registration-file validate registration.json`
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### Publishing a registration file
|
|
53
|
+
|
|
54
|
+
We publish to IPFS using <https://pinata.cloud>.
|
|
55
|
+
|
|
56
|
+
The following variables should be in your environment to use this command.
|
|
57
|
+
|
|
58
|
+
* `PINATA_JWT`, `PINATA_API_KEY`, `PINATA_API_SECRET`
|
|
59
|
+
|
|
60
|
+
```sh
|
|
61
|
+
# Publish to IPFS
|
|
62
|
+
agent registration-file publish registration.json`
|
|
63
|
+
# Returns => ipfs://CID henceforth referred to as <agent-uri>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### Register your Agent
|
|
67
|
+
|
|
68
|
+
We publish to the cannonical registries <https://github.com/erc-8004/erc-8004-contracts>
|
|
69
|
+
|
|
70
|
+
```sh
|
|
71
|
+
agent registry identity --chain-name <chain> <agent-uri> --private-key <0x...> [--broadcast]
|
|
72
|
+
# Returns => <agent-id>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### ENS operations
|
|
76
|
+
|
|
77
|
+
#### Preparing a metadata payload
|
|
78
|
+
|
|
79
|
+
A metadata payload descrbes which text records to set.
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
# Agent metadata expressed as JSON schema
|
|
83
|
+
agent metadata template > payload.json
|
|
84
|
+
|
|
85
|
+
# Validator
|
|
86
|
+
agent metadata validate payload.json
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
#### Updating metadata on ENS
|
|
90
|
+
|
|
91
|
+
* Use this command to set Agent metadata against your ENS name.
|
|
92
|
+
* Consider updating your metadata when key information about your skills, identity or capabilities change.
|
|
93
|
+
* Remember to also update your `<agent-uri>`.
|
|
94
|
+
|
|
95
|
+
```sh
|
|
96
|
+
agent metadata set <AGENT_ENS_NAME> payload.json --private-key 0x<KEY> --broadcast`
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## References
|
|
100
|
+
|
|
101
|
+
* Agent Metadata (ERC-8004) used for registration-file command
|
|
102
|
+
* <https://best-practices.8004scan.io/docs/01-agent-metadata-standard.html>
|
|
103
|
+
* ENS Metadata - Agent schema, used for metadata command
|
|
104
|
+
* <https://ens-metadata-docs.vercel.app/schemas/agent>
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import Pastel from 'pastel';
|
|
3
|
+
const args = process.argv.slice(2);
|
|
4
|
+
const isRootHelp = args.length === 0 || (args.length === 1 && (args[0] === '--help' || args[0] === '-h'));
|
|
5
|
+
if (isRootHelp) {
|
|
6
|
+
console.log(`
|
|
7
|
+
CLI for registering AI agents on ENS using ERC-8004 (v2.0).
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
agent skill [--install]
|
|
11
|
+
|
|
12
|
+
agent registration-file template
|
|
13
|
+
agent registration-file validate <file.json>
|
|
14
|
+
agent registration-file publish <file.json>
|
|
15
|
+
|
|
16
|
+
agent registry identity --chain-name <chain> <agent-uri>
|
|
17
|
+
|
|
18
|
+
agent metadata template
|
|
19
|
+
agent metadata validate <payload.json>
|
|
20
|
+
agent metadata set <ens-node> <payload.json> --private-key <key> [--broadcast]
|
|
21
|
+
|
|
22
|
+
Run \`agent <command> --help\` for details on a specific command.
|
|
23
|
+
`);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
const app = new Pastel({
|
|
27
|
+
importMeta: import.meta,
|
|
28
|
+
});
|
|
29
|
+
await app.run();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const description = "Set ENS metadata text records from a payload file";
|
|
4
|
+
export declare const args: z.ZodTuple<[z.ZodString, z.ZodString], null>;
|
|
5
|
+
export declare const options: z.ZodObject<{
|
|
6
|
+
privateKey: z.ZodString;
|
|
7
|
+
broadcast: z.ZodDefault<z.ZodBoolean>;
|
|
8
|
+
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
privateKey: string;
|
|
10
|
+
broadcast: boolean;
|
|
11
|
+
}, {
|
|
12
|
+
privateKey: string;
|
|
13
|
+
broadcast?: boolean | undefined;
|
|
14
|
+
}>;
|
|
15
|
+
type Props = {
|
|
16
|
+
args: z.infer<typeof args>;
|
|
17
|
+
options: z.infer<typeof options>;
|
|
18
|
+
};
|
|
19
|
+
export default function Set({ args: [ensName, payloadFile], options }: Props): React.JSX.Element;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=set.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"set.d.ts","sourceRoot":"","sources":["../../../src/commands/metadata/set.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAKvB,eAAO,MAAM,WAAW,sDAAsD,CAAA;AAE9E,eAAO,MAAM,IAAI,8CAGf,CAAA;AAEF,eAAO,MAAM,OAAO;;;;;;;;;EAMlB,CAAA;AAEF,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAA;IAC1B,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,CAAA;CACjC,CAAA;AAQD,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,OAAO,EAAE,EAAE,KAAK,qBA4D3E"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { Box, Text, useApp } from 'ink';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { SCHEMA_MAP } from '@ens-node-metadata/schemas';
|
|
6
|
+
import { validateMetadataSchema } from '@ens-node-metadata/sdk';
|
|
7
|
+
import { setEnsTextRecords } from '../../lib/ens-write.js';
|
|
8
|
+
export const description = 'Set ENS metadata text records from a payload file';
|
|
9
|
+
export const args = z.tuple([
|
|
10
|
+
z.string().describe('ENS name (e.g. myagent.eth)'),
|
|
11
|
+
z.string().describe('payload.json'),
|
|
12
|
+
]);
|
|
13
|
+
export const options = z.object({
|
|
14
|
+
privateKey: z.string().describe('Private key for signing (hex, prefixed with 0x)'),
|
|
15
|
+
broadcast: z
|
|
16
|
+
.boolean()
|
|
17
|
+
.default(false)
|
|
18
|
+
.describe('Broadcast the transaction on-chain (default: dry run)'),
|
|
19
|
+
});
|
|
20
|
+
export default function Set({ args: [ensName, payloadFile], options }) {
|
|
21
|
+
const { exit } = useApp();
|
|
22
|
+
const [state, setState] = React.useState({ status: 'idle' });
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
async function run() {
|
|
25
|
+
let payload;
|
|
26
|
+
try {
|
|
27
|
+
const raw = JSON.parse(readFileSync(payloadFile, 'utf8'));
|
|
28
|
+
const result = validateMetadataSchema(raw, SCHEMA_MAP.Agent);
|
|
29
|
+
if (!result.success) {
|
|
30
|
+
const issues = result.errors.map((e) => `[${e.key}] ${e.message}`).join('\n');
|
|
31
|
+
setState({ status: 'error', message: `Invalid payload:\n${issues}` });
|
|
32
|
+
exit(new Error('validation failed'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
payload = result.data;
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
setState({ status: 'error', message: `Error reading payload: ${err.message}` });
|
|
39
|
+
exit(new Error('read error'));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const texts = Object.entries(payload).map(([key, value]) => ({ key, value }));
|
|
43
|
+
if (!options.broadcast) {
|
|
44
|
+
const lines = [
|
|
45
|
+
`Dry run — would set ${texts.length} text records on ${ensName}:`,
|
|
46
|
+
'',
|
|
47
|
+
...texts.map((t) => ` setText("${t.key}", "${t.value}")`),
|
|
48
|
+
'',
|
|
49
|
+
'Run with --broadcast to submit on-chain.',
|
|
50
|
+
];
|
|
51
|
+
setState({ status: 'done', message: lines.join('\n') });
|
|
52
|
+
exit();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
setState({ status: 'working', message: `Setting ${texts.length} text records on ${ensName}…` });
|
|
56
|
+
try {
|
|
57
|
+
const hash = await setEnsTextRecords(ensName, texts, options.privateKey);
|
|
58
|
+
setState({ status: 'done', message: `✅ Transaction submitted: ${hash}` });
|
|
59
|
+
exit();
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
setState({ status: 'error', message: `Transaction failed: ${err.message}` });
|
|
63
|
+
exit(new Error('tx failed'));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
run();
|
|
67
|
+
}, [exit, ensName, payloadFile, options]);
|
|
68
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
69
|
+
state.status === 'idle' && React.createElement(Text, { color: "gray" }, "Preparing\u2026"),
|
|
70
|
+
state.status === 'working' && React.createElement(Text, { color: "cyan" }, state.message),
|
|
71
|
+
state.status === 'done' && React.createElement(Text, { color: "green" }, state.message),
|
|
72
|
+
state.status === 'error' && React.createElement(Text, { color: "red" },
|
|
73
|
+
"\u274C ",
|
|
74
|
+
state.message)));
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../../src/commands/metadata/template.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,OAAO,CAAA;AAIzB,eAAO,MAAM,WAAW,mDAAmD,CAAA;AAE3E,MAAM,CAAC,OAAO,UAAU,gBAAgB,sBAMvC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Text } from 'ink';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { SCHEMA_MAP } from '@ens-node-metadata/schemas';
|
|
4
|
+
export const description = 'Generate starter ENS metadata payload template';
|
|
5
|
+
export default function MetadataTemplate() {
|
|
6
|
+
return (React.createElement(Text, null, JSON.stringify(SCHEMA_MAP.Agent, null, 2)));
|
|
7
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const description = "Validate ENS metadata payload against agent schema";
|
|
4
|
+
export declare const args: z.ZodTuple<[z.ZodString], null>;
|
|
5
|
+
type Props = {
|
|
6
|
+
args: z.infer<typeof args>;
|
|
7
|
+
};
|
|
8
|
+
export default function MetadataValidate({ args: [file] }: Props): React.JSX.Element;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/commands/metadata/validate.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,WAAW,uDAAuD,CAAA;AAE/E,eAAO,MAAM,IAAI,iCAAiD,CAAA;AAElE,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAA;CAC3B,CAAA;AAED,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,qBAwC/D"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { Box, Text, useApp } from 'ink';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { SCHEMA_MAP } from '@ens-node-metadata/schemas';
|
|
6
|
+
import { validateMetadataSchema } from '@ens-node-metadata/sdk';
|
|
7
|
+
export const description = 'Validate ENS metadata payload against agent schema';
|
|
8
|
+
export const args = z.tuple([z.string().describe('payload.json')]);
|
|
9
|
+
export default function MetadataValidate({ args: [file] }) {
|
|
10
|
+
const { exit } = useApp();
|
|
11
|
+
let fileError = null;
|
|
12
|
+
let result = null;
|
|
13
|
+
try {
|
|
14
|
+
const raw = JSON.parse(readFileSync(file, 'utf8'));
|
|
15
|
+
result = validateMetadataSchema(raw, SCHEMA_MAP.Agent);
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
fileError = err.message;
|
|
19
|
+
}
|
|
20
|
+
React.useEffect(() => {
|
|
21
|
+
exit(fileError || (result && !result.success) ? new Error('validation failed') : undefined);
|
|
22
|
+
}, [exit, fileError, result]);
|
|
23
|
+
if (fileError) {
|
|
24
|
+
return React.createElement(Text, { color: "red" },
|
|
25
|
+
"\u274C Error reading file: ",
|
|
26
|
+
fileError);
|
|
27
|
+
}
|
|
28
|
+
if (result.success) {
|
|
29
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
30
|
+
React.createElement(Text, { color: "green" }, "\u2705 Valid ENS agent metadata payload"),
|
|
31
|
+
React.createElement(Text, { color: "gray" },
|
|
32
|
+
" ",
|
|
33
|
+
Object.keys(result.data).length,
|
|
34
|
+
" text records")));
|
|
35
|
+
}
|
|
36
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
37
|
+
React.createElement(Text, { color: "red" }, "\u274C Invalid agent metadata payload"),
|
|
38
|
+
result.errors.map(({ key, message }) => (React.createElement(Text, { key: key, color: "red" },
|
|
39
|
+
' ',
|
|
40
|
+
"[",
|
|
41
|
+
key,
|
|
42
|
+
"] ",
|
|
43
|
+
message)))));
|
|
44
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const description = "Register agent on ENS with metadata payload";
|
|
4
|
+
export declare const args: z.ZodTuple<[z.ZodString, z.ZodString], null>;
|
|
5
|
+
export declare const options: z.ZodObject<{
|
|
6
|
+
privateKey: z.ZodString;
|
|
7
|
+
broadcast: z.ZodDefault<z.ZodBoolean>;
|
|
8
|
+
}, "strip", z.ZodTypeAny, {
|
|
9
|
+
privateKey: string;
|
|
10
|
+
broadcast: boolean;
|
|
11
|
+
}, {
|
|
12
|
+
privateKey: string;
|
|
13
|
+
broadcast?: boolean | undefined;
|
|
14
|
+
}>;
|
|
15
|
+
type Props = {
|
|
16
|
+
args: z.infer<typeof args>;
|
|
17
|
+
options: z.infer<typeof options>;
|
|
18
|
+
};
|
|
19
|
+
export default function Register({ args: [ensName, payloadFile], options }: Props): React.JSX.Element;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=register.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/commands/register.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,WAAW,gDAAgD,CAAA;AAExE,eAAO,MAAM,IAAI,8CAGf,CAAA;AAEF,eAAO,MAAM,OAAO;;;;;;;;;EAMlB,CAAA;AAEF,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAA;IAC1B,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,CAAA;CACjC,CAAA;AAQD,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC,EAAE,OAAO,EAAE,EAAE,KAAK,qBA8DhF"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { Box, Text, useApp } from 'ink';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { AgentMetadataPayloadSchema } from '../index.js';
|
|
6
|
+
import { setEnsTextRecords } from '../lib/ens-write.js';
|
|
7
|
+
export const description = 'Register agent on ENS with metadata payload';
|
|
8
|
+
export const args = z.tuple([
|
|
9
|
+
z.string().describe('EnsNode'),
|
|
10
|
+
z.string().describe('payload.json'),
|
|
11
|
+
]);
|
|
12
|
+
export const options = z.object({
|
|
13
|
+
privateKey: z.string().describe('Private key for signing (hex, prefixed with 0x)'),
|
|
14
|
+
broadcast: z
|
|
15
|
+
.boolean()
|
|
16
|
+
.default(false)
|
|
17
|
+
.describe('Broadcast the transaction on-chain (default: dry run)'),
|
|
18
|
+
});
|
|
19
|
+
export default function Register({ args: [ensName, payloadFile], options }) {
|
|
20
|
+
const { exit } = useApp();
|
|
21
|
+
const [state, setState] = React.useState({ status: 'idle' });
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
async function run() {
|
|
24
|
+
let payload;
|
|
25
|
+
try {
|
|
26
|
+
const raw = JSON.parse(readFileSync(payloadFile, 'utf8'));
|
|
27
|
+
const result = AgentMetadataPayloadSchema.safeParse(raw);
|
|
28
|
+
if (!result.success) {
|
|
29
|
+
const issues = result.error.issues
|
|
30
|
+
.map((i) => `[${i.path.join('.') || 'root'}] ${i.message}`)
|
|
31
|
+
.join('\n');
|
|
32
|
+
setState({ status: 'error', message: `Invalid payload:\n${issues}` });
|
|
33
|
+
exit(new Error('validation failed'));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
payload = result.data;
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
setState({ status: 'error', message: `Error reading payload: ${err.message}` });
|
|
40
|
+
exit(new Error('read error'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const texts = Object.entries(payload).map(([key, value]) => ({ key, value }));
|
|
44
|
+
if (!options.broadcast) {
|
|
45
|
+
const lines = [
|
|
46
|
+
`Dry run — would set ${texts.length} text records on ${ensName}:`,
|
|
47
|
+
'',
|
|
48
|
+
...texts.map((t) => ` setText("${t.key}", "${t.value}")`),
|
|
49
|
+
'',
|
|
50
|
+
'Run with --broadcast to submit on-chain.',
|
|
51
|
+
];
|
|
52
|
+
setState({ status: 'done', message: lines.join('\n') });
|
|
53
|
+
exit();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
setState({ status: 'working', message: `Setting ${texts.length} text records on ${ensName}…` });
|
|
57
|
+
try {
|
|
58
|
+
const hash = await setEnsTextRecords(ensName, texts, options.privateKey);
|
|
59
|
+
setState({ status: 'done', message: `✅ Transaction submitted: ${hash}` });
|
|
60
|
+
exit();
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
setState({ status: 'error', message: `Transaction failed: ${err.message}` });
|
|
64
|
+
exit(new Error('tx failed'));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
run();
|
|
68
|
+
}, [exit, ensName, payloadFile, options]);
|
|
69
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
70
|
+
state.status === 'idle' && React.createElement(Text, { color: "gray" }, "Preparing\u2026"),
|
|
71
|
+
state.status === 'working' && React.createElement(Text, { color: "cyan" }, state.message),
|
|
72
|
+
state.status === 'done' && React.createElement(Text, { color: "green" }, state.message),
|
|
73
|
+
state.status === 'error' && React.createElement(Text, { color: "red" },
|
|
74
|
+
"\u274C ",
|
|
75
|
+
state.message)));
|
|
76
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
export declare const description = "Publish registration file to IPFS via Pinata";
|
|
4
|
+
export declare const args: z.ZodTuple<[z.ZodString], null>;
|
|
5
|
+
type Props = {
|
|
6
|
+
args: z.infer<typeof args>;
|
|
7
|
+
};
|
|
8
|
+
export default function RegistrationFilePublish({ args: [file] }: Props): React.JSX.Element;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=publish.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../../src/commands/registration-file/publish.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,eAAO,MAAM,WAAW,iDAAiD,CAAA;AAEzE,eAAO,MAAM,IAAI,iCAA2D,CAAA;AAE5E,KAAK,KAAK,GAAG;IACX,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,CAAC,CAAA;CAC3B,CAAA;AASD,MAAM,CAAC,OAAO,UAAU,uBAAuB,CAAC,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,qBAkFtE"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { Box, Text, useApp } from 'ink';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
import { validateRegistrationFile } from '../../index.js';
|
|
6
|
+
import { publishFile } from '@ens-node-metadata/shared';
|
|
7
|
+
export const description = 'Publish registration file to IPFS via Pinata';
|
|
8
|
+
export const args = z.tuple([z.string().describe('registration-file.json')]);
|
|
9
|
+
export default function RegistrationFilePublish({ args: [file] }) {
|
|
10
|
+
const { exit } = useApp();
|
|
11
|
+
const [state, setState] = React.useState({ status: 'idle' });
|
|
12
|
+
React.useEffect(() => {
|
|
13
|
+
async function run() {
|
|
14
|
+
// 1. Check env vars
|
|
15
|
+
const pinataJwt = process.env.PINATA_JWT;
|
|
16
|
+
const pinataKey = process.env.PINATA_API_KEY;
|
|
17
|
+
const pinataSecret = process.env.PINATA_API_SECRET;
|
|
18
|
+
if (!pinataJwt && !(pinataKey && pinataSecret)) {
|
|
19
|
+
setState({
|
|
20
|
+
status: 'error',
|
|
21
|
+
message: 'Missing Pinata credentials. Set PINATA_JWT or both PINATA_API_KEY and PINATA_API_SECRET.',
|
|
22
|
+
});
|
|
23
|
+
exit(new Error('missing env vars'));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// 2. Validate registration file
|
|
27
|
+
setState({ status: 'validating' });
|
|
28
|
+
let raw;
|
|
29
|
+
try {
|
|
30
|
+
raw = JSON.parse(readFileSync(file, 'utf8'));
|
|
31
|
+
}
|
|
32
|
+
catch (err) {
|
|
33
|
+
setState({ status: 'error', message: `Error reading file: ${err.message}` });
|
|
34
|
+
exit(new Error('read error'));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const result = validateRegistrationFile(raw);
|
|
38
|
+
if (!result.success) {
|
|
39
|
+
const issues = result.error.issues
|
|
40
|
+
.map((i) => `[${i.path.join('.') || 'root'}] ${i.message}`)
|
|
41
|
+
.join('\n');
|
|
42
|
+
setState({ status: 'error', message: `Invalid registration file:\n${issues}` });
|
|
43
|
+
exit(new Error('validation failed'));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
// 3. Upload to IPFS via Pinata
|
|
47
|
+
setState({ status: 'uploading' });
|
|
48
|
+
try {
|
|
49
|
+
const { cid } = await publishFile({
|
|
50
|
+
provider: 'pinata',
|
|
51
|
+
filePath: file,
|
|
52
|
+
pinataJwt,
|
|
53
|
+
pinataKey,
|
|
54
|
+
pinataSecret,
|
|
55
|
+
schemaId: result.data.name,
|
|
56
|
+
version: '1.0.0',
|
|
57
|
+
});
|
|
58
|
+
setState({ status: 'done', uri: `ipfs://${cid}` });
|
|
59
|
+
exit();
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
setState({ status: 'error', message: `Upload failed: ${err.message}` });
|
|
63
|
+
exit(new Error('upload failed'));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
run();
|
|
67
|
+
}, [exit, file]);
|
|
68
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
69
|
+
state.status === 'idle' && React.createElement(Text, { color: "gray" }, "Preparing\u2026"),
|
|
70
|
+
state.status === 'validating' && React.createElement(Text, { color: "cyan" }, "Validating registration file\u2026"),
|
|
71
|
+
state.status === 'uploading' && React.createElement(Text, { color: "cyan" }, "Uploading to IPFS via Pinata\u2026"),
|
|
72
|
+
state.status === 'done' && (React.createElement(Box, { flexDirection: "column" },
|
|
73
|
+
React.createElement(Text, { color: "green" }, "\u2705 Published to IPFS"),
|
|
74
|
+
React.createElement(Text, null, state.uri))),
|
|
75
|
+
state.status === 'error' && (React.createElement(Box, { flexDirection: "column" },
|
|
76
|
+
React.createElement(Text, { color: "red" },
|
|
77
|
+
"\u274C ",
|
|
78
|
+
state.message)))));
|
|
79
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../../src/commands/registration-file/template.tsx"],"names":[],"mappings":"AAGA,eAAO,MAAM,WAAW,4DAA4D,CAAA;AAwCpF,MAAM,CAAC,OAAO,UAAU,wBAAwB,SAQ/C"}
|