@agirails/sdk 2.2.2 → 2.3.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/README.md +20 -23
- package/dist/ACTPClient.d.ts +7 -0
- package/dist/ACTPClient.d.ts.map +1 -1
- package/dist/ACTPClient.js +56 -1
- package/dist/ACTPClient.js.map +1 -1
- package/dist/abi/AgentRegistry.json +133 -0
- package/dist/adapters/X402Adapter.d.ts +34 -7
- package/dist/adapters/X402Adapter.d.ts.map +1 -1
- package/dist/adapters/X402Adapter.js +36 -8
- package/dist/adapters/X402Adapter.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/cli/commands/diff.d.ts +11 -0
- package/dist/cli/commands/diff.d.ts.map +1 -0
- package/dist/cli/commands/diff.js +115 -0
- package/dist/cli/commands/diff.js.map +1 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +51 -2
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/publish.d.ts +11 -0
- package/dist/cli/commands/publish.d.ts.map +1 -0
- package/dist/cli/commands/publish.js +170 -0
- package/dist/cli/commands/publish.js.map +1 -0
- package/dist/cli/commands/pull.d.ts +12 -0
- package/dist/cli/commands/pull.d.ts.map +1 -0
- package/dist/cli/commands/pull.js +99 -0
- package/dist/cli/commands/pull.js.map +1 -0
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/config/agirailsmd.d.ts +94 -0
- package/dist/config/agirailsmd.d.ts.map +1 -0
- package/dist/config/agirailsmd.js +209 -0
- package/dist/config/agirailsmd.js.map +1 -0
- package/dist/config/networks.d.ts +2 -0
- package/dist/config/networks.d.ts.map +1 -1
- package/dist/config/networks.js +10 -4
- package/dist/config/networks.js.map +1 -1
- package/dist/config/publishPipeline.d.ts +61 -0
- package/dist/config/publishPipeline.d.ts.map +1 -0
- package/dist/config/publishPipeline.js +192 -0
- package/dist/config/publishPipeline.js.map +1 -0
- package/dist/config/syncOperations.d.ts +67 -0
- package/dist/config/syncOperations.d.ts.map +1 -0
- package/dist/config/syncOperations.js +208 -0
- package/dist/config/syncOperations.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/level0/request.d.ts.map +1 -1
- package/dist/level0/request.js +23 -86
- package/dist/level0/request.js.map +1 -1
- package/dist/level1/Agent.d.ts +0 -11
- package/dist/level1/Agent.d.ts.map +1 -1
- package/dist/level1/Agent.js +15 -32
- package/dist/level1/Agent.js.map +1 -1
- package/dist/registry/AgentRegistryClient.d.ts +75 -0
- package/dist/registry/AgentRegistryClient.d.ts.map +1 -0
- package/dist/registry/AgentRegistryClient.js +160 -0
- package/dist/registry/AgentRegistryClient.js.map +1 -0
- package/dist/runtime/MockRuntime.d.ts.map +1 -1
- package/dist/runtime/MockRuntime.js +3 -1
- package/dist/runtime/MockRuntime.js.map +1 -1
- package/dist/types/adapter.d.ts +39 -0
- package/dist/types/adapter.d.ts.map +1 -1
- package/dist/types/adapter.js +7 -0
- package/dist/types/adapter.js.map +1 -1
- package/dist/types/x402.d.ts +23 -0
- package/dist/types/x402.d.ts.map +1 -1
- package/dist/types/x402.js.map +1 -1
- package/dist/wallet/keystore.d.ts +16 -0
- package/dist/wallet/keystore.d.ts.map +1 -0
- package/dist/wallet/keystore.js +132 -0
- package/dist/wallet/keystore.js.map +1 -0
- package/package.json +2 -1
- package/src/ACTPClient.ts +63 -1
- package/src/abi/AgentRegistry.json +133 -0
- package/src/adapters/X402Adapter.ts +94 -16
- package/src/adapters/index.ts +9 -1
- package/src/cli/commands/diff.ts +141 -0
- package/src/cli/commands/init.ts +65 -4
- package/src/cli/commands/publish.ts +209 -0
- package/src/cli/commands/pull.ts +124 -0
- package/src/cli/index.ts +8 -0
- package/src/config/agirailsmd.ts +262 -0
- package/src/config/networks.ts +12 -4
- package/src/config/publishPipeline.ts +276 -0
- package/src/config/syncOperations.ts +279 -0
- package/src/index.ts +3 -0
- package/src/level0/request.ts +27 -88
- package/src/level1/Agent.ts +16 -32
- package/src/registry/AgentRegistryClient.ts +202 -0
- package/src/runtime/MockRuntime.ts +3 -1
- package/src/types/adapter.ts +14 -0
- package/src/types/x402.ts +32 -0
- package/src/wallet/keystore.ts +119 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diff Command - Compare local AGIRAILS.md with on-chain config
|
|
3
|
+
*
|
|
4
|
+
* Shows sync status between local file and AgentRegistry state.
|
|
5
|
+
* Terraform-style: never auto-overwrites, just shows differences.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/commands/diff
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import { Output, ExitCode } from '../utils/output';
|
|
12
|
+
import { mapError } from '../utils/client';
|
|
13
|
+
import { resolve } from 'path';
|
|
14
|
+
import { ethers } from 'ethers';
|
|
15
|
+
import { diff } from '../../config/syncOperations';
|
|
16
|
+
import { getNetwork } from '../../config/networks';
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Command Definition
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
export function createDiffCommand(): Command {
|
|
23
|
+
const cmd = new Command('diff')
|
|
24
|
+
.description('Compare local AGIRAILS.md with on-chain config')
|
|
25
|
+
.argument('[path]', 'Path to AGIRAILS.md', './AGIRAILS.md')
|
|
26
|
+
.option('-n, --network <network>', 'Network (base-sepolia | base-mainnet)', 'base-sepolia')
|
|
27
|
+
.option('-a, --address <address>', 'Agent address to compare with')
|
|
28
|
+
.option('--json', 'Output as JSON')
|
|
29
|
+
.option('-q, --quiet', 'Output only sync status')
|
|
30
|
+
.action(async (path, options) => {
|
|
31
|
+
const output = new Output(
|
|
32
|
+
options.json ? 'json' : options.quiet ? 'quiet' : 'human'
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await runDiff(path, options, output);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
const structuredError = mapError(error);
|
|
39
|
+
output.errorResult({
|
|
40
|
+
code: structuredError.code,
|
|
41
|
+
message: structuredError.message,
|
|
42
|
+
details: structuredError.details,
|
|
43
|
+
});
|
|
44
|
+
process.exit(ExitCode.ERROR);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return cmd;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// Implementation
|
|
53
|
+
// ============================================================================
|
|
54
|
+
|
|
55
|
+
interface DiffCommandOptions {
|
|
56
|
+
network: string;
|
|
57
|
+
address?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function runDiff(
|
|
61
|
+
filePath: string,
|
|
62
|
+
options: DiffCommandOptions,
|
|
63
|
+
output: Output
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
const resolvedPath = resolve(filePath);
|
|
66
|
+
|
|
67
|
+
// Determine agent address
|
|
68
|
+
let agentAddress = options.address;
|
|
69
|
+
if (!agentAddress) {
|
|
70
|
+
const privateKey = process.env.ACTP_PRIVATE_KEY || process.env.PRIVATE_KEY;
|
|
71
|
+
if (!privateKey) {
|
|
72
|
+
output.error('Agent address required. Use --address or set ACTP_PRIVATE_KEY env var.');
|
|
73
|
+
process.exit(ExitCode.INVALID_INPUT);
|
|
74
|
+
}
|
|
75
|
+
agentAddress = new ethers.Wallet(privateKey).address;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const networkConfig = getNetwork(options.network);
|
|
79
|
+
if (!networkConfig.contracts.agentRegistry) {
|
|
80
|
+
output.error(`AgentRegistry not deployed on ${options.network}`);
|
|
81
|
+
process.exit(ExitCode.ERROR);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const spinner = output.spinner('Comparing local and on-chain config...');
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
const provider = new ethers.JsonRpcProvider(networkConfig.rpcUrl);
|
|
88
|
+
|
|
89
|
+
const result = await diff({
|
|
90
|
+
path: resolvedPath,
|
|
91
|
+
agentAddress,
|
|
92
|
+
registryAddress: networkConfig.contracts.agentRegistry,
|
|
93
|
+
provider,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
spinner.stop(true);
|
|
97
|
+
|
|
98
|
+
output.result(
|
|
99
|
+
{
|
|
100
|
+
status: result.status,
|
|
101
|
+
inSync: result.inSync,
|
|
102
|
+
localHash: result.localHash,
|
|
103
|
+
onChainHash: result.onChainHash,
|
|
104
|
+
onChainCID: result.onChainCID || null,
|
|
105
|
+
hasLocalFile: result.hasLocalFile,
|
|
106
|
+
hasOnChainConfig: result.hasOnChainConfig,
|
|
107
|
+
network: options.network,
|
|
108
|
+
agent: agentAddress,
|
|
109
|
+
},
|
|
110
|
+
{ quietKey: 'status' }
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
output.blank();
|
|
114
|
+
|
|
115
|
+
// Human-friendly status messages
|
|
116
|
+
switch (result.status) {
|
|
117
|
+
case 'in-sync':
|
|
118
|
+
output.success('Local and on-chain configs are in sync.');
|
|
119
|
+
break;
|
|
120
|
+
case 'local-ahead':
|
|
121
|
+
output.warning('Local changes not yet published. Run: actp publish');
|
|
122
|
+
break;
|
|
123
|
+
case 'remote-ahead':
|
|
124
|
+
output.warning('On-chain config is newer. Run: actp pull');
|
|
125
|
+
break;
|
|
126
|
+
case 'diverged':
|
|
127
|
+
output.warning('Local and on-chain configs have diverged.');
|
|
128
|
+
output.print(' Resolve by running: actp publish (to push local) or actp pull --force (to accept remote)');
|
|
129
|
+
break;
|
|
130
|
+
case 'no-local':
|
|
131
|
+
output.info('No local AGIRAILS.md found. Run: actp pull');
|
|
132
|
+
break;
|
|
133
|
+
case 'no-remote':
|
|
134
|
+
output.info('No config published on-chain. Run: actp publish');
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
} catch (error) {
|
|
138
|
+
spinner.stop(false);
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
}
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import * as crypto from 'crypto';
|
|
11
11
|
import * as fs from 'fs';
|
|
12
12
|
import * as path from 'path';
|
|
13
|
+
import * as readline from 'readline';
|
|
13
14
|
import { Command } from 'commander';
|
|
14
15
|
import {
|
|
15
16
|
saveConfig,
|
|
@@ -102,10 +103,10 @@ async function runInit(options: InitOptions, output: Output): Promise<void> {
|
|
|
102
103
|
address = '0x' + crypto.randomBytes(20).toString('hex');
|
|
103
104
|
output.info(`Generated mock address: ${address}`);
|
|
104
105
|
} else {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
);
|
|
106
|
+
// Generate a real wallet with encrypted keystore
|
|
107
|
+
const actpDir = getActpDir(projectRoot);
|
|
108
|
+
fs.mkdirSync(actpDir, { recursive: true });
|
|
109
|
+
address = await generateWallet(actpDir, output);
|
|
109
110
|
}
|
|
110
111
|
}
|
|
111
112
|
|
|
@@ -177,6 +178,66 @@ async function runInit(options: InitOptions, output: Output): Promise<void> {
|
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
|
|
181
|
+
// ============================================================================
|
|
182
|
+
// Wallet Generation
|
|
183
|
+
// ============================================================================
|
|
184
|
+
|
|
185
|
+
async function generateWallet(actpDir: string, output: Output): Promise<string> {
|
|
186
|
+
const { Wallet } = await import('ethers');
|
|
187
|
+
|
|
188
|
+
const wallet = Wallet.createRandom();
|
|
189
|
+
|
|
190
|
+
// Get password from env var or interactive prompt
|
|
191
|
+
let password = process.env.ACTP_KEY_PASSWORD;
|
|
192
|
+
if (!password) {
|
|
193
|
+
password = await promptPassword();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!password || password.length < 8) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
'Wallet password required (minimum 8 characters).\n' +
|
|
199
|
+
'Set ACTP_KEY_PASSWORD env var or enter when prompted.'
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Encrypt with Keystore V3 (scrypt + AES-128-CTR)
|
|
204
|
+
output.info('Encrypting wallet (this takes a few seconds)...');
|
|
205
|
+
const keystore = await wallet.encrypt(password);
|
|
206
|
+
|
|
207
|
+
// Save with restrictive permissions
|
|
208
|
+
const keystorePath = path.join(actpDir, 'keystore.json');
|
|
209
|
+
fs.writeFileSync(keystorePath, keystore, { mode: 0o600 });
|
|
210
|
+
|
|
211
|
+
output.success('Key securely saved and encrypted');
|
|
212
|
+
output.info(`Address: ${wallet.address}`);
|
|
213
|
+
output.warning('Back up your password — it cannot be recovered.');
|
|
214
|
+
output.info('');
|
|
215
|
+
output.info('To start your agent:');
|
|
216
|
+
output.info(' export ACTP_KEY_PASSWORD="your-password"');
|
|
217
|
+
output.info(' npx ts-node agent.ts');
|
|
218
|
+
|
|
219
|
+
return wallet.address;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function promptPassword(): Promise<string> {
|
|
223
|
+
// If not a TTY (e.g. piped or run by agent), skip prompt
|
|
224
|
+
if (!process.stdin.isTTY) {
|
|
225
|
+
return '';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const rl = readline.createInterface({
|
|
229
|
+
input: process.stdin,
|
|
230
|
+
output: process.stdout,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
return new Promise((resolve) => {
|
|
234
|
+
rl.question('Enter password for wallet encryption (min 8 chars): ', (answer) => {
|
|
235
|
+
rl.close();
|
|
236
|
+
resolve(answer.trim());
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
180
241
|
// ============================================================================
|
|
181
242
|
// Scaffold
|
|
182
243
|
// ============================================================================
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publish Command - Publish AGIRAILS.md config on-chain
|
|
3
|
+
*
|
|
4
|
+
* Reads AGIRAILS.md, computes canonical hash, uploads to IPFS,
|
|
5
|
+
* optionally to Arweave, and records on AgentRegistry.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/commands/publish
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Command } from 'commander';
|
|
11
|
+
import { Output, ExitCode } from '../utils/output';
|
|
12
|
+
import { mapError } from '../utils/client';
|
|
13
|
+
import { resolve } from 'path';
|
|
14
|
+
import { readFileSync, existsSync } from 'fs';
|
|
15
|
+
import { ethers } from 'ethers';
|
|
16
|
+
import { computeConfigHash, parseAgirailsMd, serializeAgirailsMd } from '../../config/agirailsmd';
|
|
17
|
+
import { AgentRegistryClient } from '../../registry/AgentRegistryClient';
|
|
18
|
+
import { FilebaseClient } from '../../storage/FilebaseClient';
|
|
19
|
+
import { ArweaveClient } from '../../storage/ArweaveClient';
|
|
20
|
+
import { getNetwork } from '../../config/networks';
|
|
21
|
+
import { publishAgirailsMd, PENDING_ENDPOINT } from '../../config/publishPipeline';
|
|
22
|
+
|
|
23
|
+
// ============================================================================
|
|
24
|
+
// Command Definition
|
|
25
|
+
// ============================================================================
|
|
26
|
+
|
|
27
|
+
export function createPublishCommand(): Command {
|
|
28
|
+
const cmd = new Command('publish')
|
|
29
|
+
.description('Publish AGIRAILS.md config on-chain')
|
|
30
|
+
.argument('[path]', 'Path to AGIRAILS.md', './AGIRAILS.md')
|
|
31
|
+
.option('-n, --network <network>', 'Network (base-sepolia | base-mainnet)', 'base-sepolia')
|
|
32
|
+
.option('--skip-arweave', 'Skip permanent Arweave storage (dev mode)')
|
|
33
|
+
.option('--dry-run', 'Show what would happen without executing')
|
|
34
|
+
.option('--json', 'Output as JSON')
|
|
35
|
+
.option('-q, --quiet', 'Output only the config hash')
|
|
36
|
+
.action(async (path, options) => {
|
|
37
|
+
const output = new Output(
|
|
38
|
+
options.json ? 'json' : options.quiet ? 'quiet' : 'human'
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
await runPublish(path, options, output);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
const structuredError = mapError(error);
|
|
45
|
+
output.errorResult({
|
|
46
|
+
code: structuredError.code,
|
|
47
|
+
message: structuredError.message,
|
|
48
|
+
details: structuredError.details,
|
|
49
|
+
});
|
|
50
|
+
process.exit(ExitCode.ERROR);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return cmd;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// Implementation
|
|
59
|
+
// ============================================================================
|
|
60
|
+
|
|
61
|
+
interface PublishCommandOptions {
|
|
62
|
+
network: string;
|
|
63
|
+
skipArweave?: boolean;
|
|
64
|
+
dryRun?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function runPublish(
|
|
68
|
+
filePath: string,
|
|
69
|
+
options: PublishCommandOptions,
|
|
70
|
+
output: Output
|
|
71
|
+
): Promise<void> {
|
|
72
|
+
const resolvedPath = resolve(filePath);
|
|
73
|
+
|
|
74
|
+
if (!existsSync(resolvedPath)) {
|
|
75
|
+
output.error(`File not found: ${filePath}`);
|
|
76
|
+
process.exit(ExitCode.INVALID_INPUT);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const spinner = output.spinner('Reading AGIRAILS.md...');
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Read and compute hash
|
|
83
|
+
const content = readFileSync(resolvedPath, 'utf-8');
|
|
84
|
+
const { configHash, structuredHash, bodyHash } = computeConfigHash(content);
|
|
85
|
+
|
|
86
|
+
if (options.dryRun) {
|
|
87
|
+
spinner.stop(true);
|
|
88
|
+
|
|
89
|
+
output.result(
|
|
90
|
+
{
|
|
91
|
+
configHash,
|
|
92
|
+
structuredHash,
|
|
93
|
+
bodyHash,
|
|
94
|
+
path: resolvedPath,
|
|
95
|
+
network: options.network,
|
|
96
|
+
dryRun: true,
|
|
97
|
+
},
|
|
98
|
+
{ quietKey: 'configHash' }
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
output.blank();
|
|
102
|
+
output.success('Dry run complete. No changes made.');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Validate environment
|
|
107
|
+
const privateKey = process.env.ACTP_PRIVATE_KEY || process.env.PRIVATE_KEY;
|
|
108
|
+
if (!privateKey) {
|
|
109
|
+
spinner.stop(false);
|
|
110
|
+
output.error('Private key required. Set ACTP_PRIVATE_KEY or PRIVATE_KEY env var.');
|
|
111
|
+
process.exit(ExitCode.INVALID_INPUT);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const networkConfig = getNetwork(options.network);
|
|
115
|
+
if (!networkConfig.contracts.agentRegistry) {
|
|
116
|
+
spinner.stop(false);
|
|
117
|
+
output.error(`AgentRegistry not deployed on ${options.network}`);
|
|
118
|
+
process.exit(ExitCode.ERROR);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Create provider and signer
|
|
122
|
+
const provider = new ethers.JsonRpcProvider(networkConfig.rpcUrl);
|
|
123
|
+
const signer = new ethers.Wallet(privateKey, provider);
|
|
124
|
+
|
|
125
|
+
// Create Filebase client
|
|
126
|
+
const filebaseAccessKey = process.env.FILEBASE_ACCESS_KEY;
|
|
127
|
+
const filebaseSecretKey = process.env.FILEBASE_SECRET_KEY;
|
|
128
|
+
if (!filebaseAccessKey || !filebaseSecretKey) {
|
|
129
|
+
spinner.stop(false);
|
|
130
|
+
output.error('Filebase credentials required. Set FILEBASE_ACCESS_KEY and FILEBASE_SECRET_KEY.');
|
|
131
|
+
process.exit(ExitCode.INVALID_INPUT);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const filebaseClient = new FilebaseClient({
|
|
135
|
+
accessKey: filebaseAccessKey,
|
|
136
|
+
secretKey: filebaseSecretKey,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Create Arweave client (optional)
|
|
140
|
+
let arweaveClient: ArweaveClient | undefined;
|
|
141
|
+
if (!options.skipArweave) {
|
|
142
|
+
const arweaveKey = process.env.ARCHIVE_UPLOADER_KEY;
|
|
143
|
+
if (arweaveKey) {
|
|
144
|
+
arweaveClient = await ArweaveClient.create({
|
|
145
|
+
privateKey: arweaveKey,
|
|
146
|
+
rpcUrl: networkConfig.rpcUrl,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
spinner.stop(true);
|
|
152
|
+
const publishSpinner = output.spinner('Publishing to IPFS + on-chain...');
|
|
153
|
+
|
|
154
|
+
const result = await publishAgirailsMd({
|
|
155
|
+
path: resolvedPath,
|
|
156
|
+
network: options.network,
|
|
157
|
+
registryAddress: networkConfig.contracts.agentRegistry,
|
|
158
|
+
signer,
|
|
159
|
+
filebaseClient,
|
|
160
|
+
arweaveClient,
|
|
161
|
+
skipArweave: options.skipArweave || !arweaveClient,
|
|
162
|
+
gasSettings: {
|
|
163
|
+
maxFeePerGas: networkConfig.gasSettings.maxFeePerGas,
|
|
164
|
+
maxPriorityFeePerGas: networkConfig.gasSettings.maxPriorityFeePerGas,
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
publishSpinner.stop(true);
|
|
169
|
+
|
|
170
|
+
output.result(
|
|
171
|
+
{
|
|
172
|
+
configHash: result.configHash,
|
|
173
|
+
cid: result.cid,
|
|
174
|
+
txHash: result.txHash,
|
|
175
|
+
arweaveTxId: result.arweaveTxId || null,
|
|
176
|
+
registered: result.registered || false,
|
|
177
|
+
network: options.network,
|
|
178
|
+
},
|
|
179
|
+
{ quietKey: 'configHash' }
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
output.blank();
|
|
183
|
+
if (result.registered) {
|
|
184
|
+
output.success('Agent registered and config published!');
|
|
185
|
+
} else {
|
|
186
|
+
output.success('Config published successfully!');
|
|
187
|
+
}
|
|
188
|
+
output.print('');
|
|
189
|
+
output.print('Next steps:');
|
|
190
|
+
output.print(' - Verify sync: actp diff');
|
|
191
|
+
output.print(' - View on-chain: ' + networkConfig.blockExplorer + '/tx/' + result.txHash);
|
|
192
|
+
|
|
193
|
+
// Warn if placeholder endpoint was used during auto-register
|
|
194
|
+
if (result.registered) {
|
|
195
|
+
const content = readFileSync(resolvedPath, 'utf-8');
|
|
196
|
+
const { frontmatter } = parseAgirailsMd(content);
|
|
197
|
+
if (!frontmatter.endpoint || frontmatter.endpoint === PENDING_ENDPOINT) {
|
|
198
|
+
output.print('');
|
|
199
|
+
output.warning('No endpoint in AGIRAILS.md — registered with placeholder URL.');
|
|
200
|
+
output.print(' Update when your agent is deployed:');
|
|
201
|
+
output.print(' 1. Add "endpoint: https://your-agent.com/webhook" to AGIRAILS.md');
|
|
202
|
+
output.print(' 2. Run: actp publish');
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
} catch (error) {
|
|
206
|
+
spinner.stop(false);
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pull Command - Pull on-chain config to local AGIRAILS.md
|
|
3
|
+
*
|
|
4
|
+
* Fetches the published AGIRAILS.md from IPFS (via on-chain CID),
|
|
5
|
+
* verifies integrity against on-chain hash, and writes locally.
|
|
6
|
+
* Use --force to overwrite existing file without confirmation.
|
|
7
|
+
*
|
|
8
|
+
* @module cli/commands/pull
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Command } from 'commander';
|
|
12
|
+
import { Output, ExitCode } from '../utils/output';
|
|
13
|
+
import { mapError } from '../utils/client';
|
|
14
|
+
import { resolve } from 'path';
|
|
15
|
+
import { ethers } from 'ethers';
|
|
16
|
+
import { pull } from '../../config/syncOperations';
|
|
17
|
+
import { getNetwork } from '../../config/networks';
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Command Definition
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
export function createPullCommand(): Command {
|
|
24
|
+
const cmd = new Command('pull')
|
|
25
|
+
.description('Pull on-chain AGIRAILS.md config to local file')
|
|
26
|
+
.argument('[path]', 'Path to write AGIRAILS.md', './AGIRAILS.md')
|
|
27
|
+
.option('-n, --network <network>', 'Network (base-sepolia | base-mainnet)', 'base-sepolia')
|
|
28
|
+
.option('-a, --address <address>', 'Agent address to pull config for')
|
|
29
|
+
.option('--force', 'Overwrite without confirmation (CI mode)')
|
|
30
|
+
.option('--json', 'Output as JSON')
|
|
31
|
+
.option('-q, --quiet', 'Minimal output')
|
|
32
|
+
.action(async (path, options) => {
|
|
33
|
+
const output = new Output(
|
|
34
|
+
options.json ? 'json' : options.quiet ? 'quiet' : 'human'
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
await runPull(path, options, output);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
const structuredError = mapError(error);
|
|
41
|
+
output.errorResult({
|
|
42
|
+
code: structuredError.code,
|
|
43
|
+
message: structuredError.message,
|
|
44
|
+
details: structuredError.details,
|
|
45
|
+
});
|
|
46
|
+
process.exit(ExitCode.ERROR);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return cmd;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// Implementation
|
|
55
|
+
// ============================================================================
|
|
56
|
+
|
|
57
|
+
interface PullCommandOptions {
|
|
58
|
+
network: string;
|
|
59
|
+
address?: string;
|
|
60
|
+
force?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function runPull(
|
|
64
|
+
filePath: string,
|
|
65
|
+
options: PullCommandOptions,
|
|
66
|
+
output: Output
|
|
67
|
+
): Promise<void> {
|
|
68
|
+
const resolvedPath = resolve(filePath);
|
|
69
|
+
|
|
70
|
+
// Determine agent address
|
|
71
|
+
let agentAddress = options.address;
|
|
72
|
+
if (!agentAddress) {
|
|
73
|
+
const privateKey = process.env.ACTP_PRIVATE_KEY || process.env.PRIVATE_KEY;
|
|
74
|
+
if (!privateKey) {
|
|
75
|
+
output.error('Agent address required. Use --address or set ACTP_PRIVATE_KEY env var.');
|
|
76
|
+
process.exit(ExitCode.INVALID_INPUT);
|
|
77
|
+
}
|
|
78
|
+
agentAddress = new ethers.Wallet(privateKey).address;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const networkConfig = getNetwork(options.network);
|
|
82
|
+
if (!networkConfig.contracts.agentRegistry) {
|
|
83
|
+
output.error(`AgentRegistry not deployed on ${options.network}`);
|
|
84
|
+
process.exit(ExitCode.ERROR);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const spinner = output.spinner('Pulling config from on-chain...');
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const provider = new ethers.JsonRpcProvider(networkConfig.rpcUrl);
|
|
91
|
+
|
|
92
|
+
const result = await pull({
|
|
93
|
+
path: resolvedPath,
|
|
94
|
+
agentAddress,
|
|
95
|
+
registryAddress: networkConfig.contracts.agentRegistry,
|
|
96
|
+
provider,
|
|
97
|
+
force: options.force,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
spinner.stop(result.written);
|
|
101
|
+
|
|
102
|
+
output.result(
|
|
103
|
+
{
|
|
104
|
+
written: result.written,
|
|
105
|
+
cid: result.cid || null,
|
|
106
|
+
status: result.status,
|
|
107
|
+
path: resolvedPath,
|
|
108
|
+
network: options.network,
|
|
109
|
+
},
|
|
110
|
+
{ quietKey: 'status' }
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
if (result.written) {
|
|
114
|
+
output.blank();
|
|
115
|
+
output.success(`Config pulled and written to ${filePath}`);
|
|
116
|
+
} else {
|
|
117
|
+
output.blank();
|
|
118
|
+
output.info(result.status);
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
spinner.stop(false);
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -48,6 +48,9 @@ import { createWatchCommand } from './commands/watch';
|
|
|
48
48
|
import { createSimulateCommand } from './commands/simulate';
|
|
49
49
|
import { createBatchCommand } from './commands/batch';
|
|
50
50
|
import { createTimeCommand } from './commands/time';
|
|
51
|
+
import { createPublishCommand } from './commands/publish';
|
|
52
|
+
import { createPullCommand } from './commands/pull';
|
|
53
|
+
import { createDiffCommand } from './commands/diff';
|
|
51
54
|
|
|
52
55
|
// ============================================================================
|
|
53
56
|
// Program Setup
|
|
@@ -92,6 +95,11 @@ program.addCommand(createBatchCommand());
|
|
|
92
95
|
// Mock mode utilities
|
|
93
96
|
program.addCommand(createTimeCommand());
|
|
94
97
|
|
|
98
|
+
// Config sync commands (AGIRAILS.md as source of truth)
|
|
99
|
+
program.addCommand(createPublishCommand());
|
|
100
|
+
program.addCommand(createPullCommand());
|
|
101
|
+
program.addCommand(createDiffCommand());
|
|
102
|
+
|
|
95
103
|
// ============================================================================
|
|
96
104
|
// Error Handling
|
|
97
105
|
// ============================================================================
|