@lawreneliang/atel-sdk 0.4.2 → 0.4.4
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/bin/atel.mjs +205 -356
- package/dist/anchor/base.js +1 -1
- package/dist/anchor/bsc.js +1 -1
- package/dist/anchor/evm.js +1 -1
- package/dist/anchor/index.js +1 -1
- package/dist/anchor/mock.js +1 -1
- package/dist/anchor/solana.js +1 -1
- package/dist/collaboration/index.js +1 -1
- package/dist/crypto/index.js +1 -1
- package/dist/endpoint/index.js +1 -1
- package/dist/envelope/index.js +1 -1
- package/dist/gateway/index.js +1 -1
- package/dist/graph/index.js +1 -1
- package/dist/handshake/index.js +1 -1
- package/dist/identity/index.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/negotiation/index.js +1 -1
- package/dist/network/index.d.ts +75 -0
- package/dist/network/index.js +1 -0
- package/dist/orchestrator/index.js +1 -1
- package/dist/policy/index.js +1 -1
- package/dist/proof/index.js +1 -1
- package/dist/registry/index.js +1 -1
- package/dist/rollback/index.js +1 -1
- package/dist/score/index.js +1 -1
- package/dist/service/index.js +1 -1
- package/dist/service/server.js +1 -1
- package/dist/trace/index.js +1 -1
- package/dist/trust/index.js +1 -1
- package/dist/trust-sync/index.js +1 -1
- package/package.json +2 -1
- package/skill/SKILL.md +113 -44
package/bin/atel.mjs
CHANGED
|
@@ -3,17 +3,12 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* ATEL CLI — Command-line interface for ATEL SDK
|
|
5
5
|
*
|
|
6
|
-
* Async execution model:
|
|
7
|
-
* 1. Receive task → security check → return accepted + taskId
|
|
8
|
-
* 2. Forward to local executor (ATEL_EXECUTOR_URL)
|
|
9
|
-
* 3. Executor completes → callback to atel
|
|
10
|
-
* 4. Trace → Proof → on-chain anchor
|
|
11
|
-
* 5. Push result back to sender (encrypted)
|
|
12
|
-
*
|
|
13
6
|
* Commands:
|
|
14
7
|
* atel init [name] Create agent identity + default policy
|
|
15
|
-
* atel info Show identity, capabilities, policy
|
|
16
|
-
* atel start [port] Start endpoint (
|
|
8
|
+
* atel info Show identity, capabilities, policy, network
|
|
9
|
+
* atel start [port] Start endpoint (auto network + auto register)
|
|
10
|
+
* atel setup [port] Network setup only (detect IP, UPnP, verify)
|
|
11
|
+
* atel verify Verify port reachability
|
|
17
12
|
* atel inbox [count] Show received messages
|
|
18
13
|
* atel register [name] [caps] [url] Register on public registry
|
|
19
14
|
* atel search <capability> Search registry for agents
|
|
@@ -25,15 +20,10 @@
|
|
|
25
20
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, appendFileSync } from 'node:fs';
|
|
26
21
|
import { resolve } from 'node:path';
|
|
27
22
|
import {
|
|
28
|
-
AgentIdentity,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
createMessage,
|
|
33
|
-
RegistryClient,
|
|
34
|
-
ExecutionTrace,
|
|
35
|
-
ProofGenerator,
|
|
36
|
-
SolanaAnchorProvider,
|
|
23
|
+
AgentIdentity, AgentEndpoint, AgentClient, HandshakeManager,
|
|
24
|
+
createMessage, RegistryClient, ExecutionTrace, ProofGenerator,
|
|
25
|
+
SolanaAnchorProvider, autoNetworkSetup, collectCandidates, connectToAgent,
|
|
26
|
+
discoverPublicIP, checkReachable,
|
|
37
27
|
} from '@lawreneliang/atel-sdk';
|
|
38
28
|
|
|
39
29
|
const ATEL_DIR = resolve(process.env.ATEL_DIR || '.atel');
|
|
@@ -43,134 +33,43 @@ const EXECUTOR_URL = process.env.ATEL_EXECUTOR_URL || '';
|
|
|
43
33
|
const INBOX_FILE = resolve(ATEL_DIR, 'inbox.jsonl');
|
|
44
34
|
const POLICY_FILE = resolve(ATEL_DIR, 'policy.json');
|
|
45
35
|
const TASKS_FILE = resolve(ATEL_DIR, 'tasks.json');
|
|
36
|
+
const NETWORK_FILE = resolve(ATEL_DIR, 'network.json');
|
|
46
37
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const DEFAULT_POLICY = {
|
|
50
|
-
rateLimit: 60, // max tasks per minute
|
|
51
|
-
maxPayloadBytes: 1048576, // 1MB
|
|
52
|
-
maxConcurrent: 10,
|
|
53
|
-
allowedDIDs: [], // empty = allow all
|
|
54
|
-
blockedDIDs: [],
|
|
55
|
-
};
|
|
38
|
+
const DEFAULT_POLICY = { rateLimit: 60, maxPayloadBytes: 1048576, maxConcurrent: 10, allowedDIDs: [], blockedDIDs: [] };
|
|
56
39
|
|
|
57
40
|
// ─── Helpers ─────────────────────────────────────────────────────
|
|
58
41
|
|
|
59
|
-
function ensureDir() {
|
|
60
|
-
|
|
61
|
-
}
|
|
42
|
+
function ensureDir() { if (!existsSync(ATEL_DIR)) mkdirSync(ATEL_DIR, { recursive: true }); }
|
|
43
|
+
function log(event) { ensureDir(); appendFileSync(INBOX_FILE, JSON.stringify(event) + '\n'); console.log(JSON.stringify(event)); }
|
|
62
44
|
|
|
63
|
-
function
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
console.log(JSON.stringify(event));
|
|
67
|
-
}
|
|
45
|
+
function saveIdentity(id) { ensureDir(); writeFileSync(IDENTITY_FILE, JSON.stringify({ agent_id: id.agent_id, did: id.did, publicKey: Buffer.from(id.publicKey).toString('hex'), secretKey: Buffer.from(id.secretKey).toString('hex') }, null, 2)); }
|
|
46
|
+
function loadIdentity() { if (!existsSync(IDENTITY_FILE)) return null; const d = JSON.parse(readFileSync(IDENTITY_FILE, 'utf-8')); return new AgentIdentity({ agent_id: d.agent_id, publicKey: Uint8Array.from(Buffer.from(d.publicKey, 'hex')), secretKey: Uint8Array.from(Buffer.from(d.secretKey, 'hex')) }); }
|
|
47
|
+
function requireIdentity() { const id = loadIdentity(); if (!id) { console.error('No identity. Run: atel init'); process.exit(1); } return id; }
|
|
68
48
|
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
49
|
+
function loadCapabilities() { const f = resolve(ATEL_DIR, 'capabilities.json'); if (!existsSync(f)) return []; try { return JSON.parse(readFileSync(f, 'utf-8')); } catch { return []; } }
|
|
50
|
+
function saveCapabilities(c) { ensureDir(); writeFileSync(resolve(ATEL_DIR, 'capabilities.json'), JSON.stringify(c, null, 2)); }
|
|
51
|
+
function loadPolicy() { if (!existsSync(POLICY_FILE)) return { ...DEFAULT_POLICY }; try { return { ...DEFAULT_POLICY, ...JSON.parse(readFileSync(POLICY_FILE, 'utf-8')) }; } catch { return { ...DEFAULT_POLICY }; } }
|
|
52
|
+
function savePolicy(p) { ensureDir(); writeFileSync(POLICY_FILE, JSON.stringify(p, null, 2)); }
|
|
53
|
+
function loadTasks() { if (!existsSync(TASKS_FILE)) return {}; try { return JSON.parse(readFileSync(TASKS_FILE, 'utf-8')); } catch { return {}; } }
|
|
54
|
+
function saveTasks(t) { ensureDir(); writeFileSync(TASKS_FILE, JSON.stringify(t, null, 2)); }
|
|
55
|
+
function loadNetwork() { if (!existsSync(NETWORK_FILE)) return null; try { return JSON.parse(readFileSync(NETWORK_FILE, 'utf-8')); } catch { return null; } }
|
|
56
|
+
function saveNetwork(n) { ensureDir(); writeFileSync(NETWORK_FILE, JSON.stringify(n, null, 2)); }
|
|
78
57
|
|
|
79
|
-
|
|
80
|
-
if (!existsSync(IDENTITY_FILE)) return null;
|
|
81
|
-
const data = JSON.parse(readFileSync(IDENTITY_FILE, 'utf-8'));
|
|
82
|
-
return new AgentIdentity({
|
|
83
|
-
agent_id: data.agent_id,
|
|
84
|
-
publicKey: Uint8Array.from(Buffer.from(data.publicKey, 'hex')),
|
|
85
|
-
secretKey: Uint8Array.from(Buffer.from(data.secretKey, 'hex')),
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function requireIdentity() {
|
|
90
|
-
const id = loadIdentity();
|
|
91
|
-
if (!id) { console.error('No identity found. Run: atel init'); process.exit(1); }
|
|
92
|
-
return id;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function loadCapabilities() {
|
|
96
|
-
const f = resolve(ATEL_DIR, 'capabilities.json');
|
|
97
|
-
if (!existsSync(f)) return [];
|
|
98
|
-
try { return JSON.parse(readFileSync(f, 'utf-8')); } catch { return []; }
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function saveCapabilities(caps) {
|
|
102
|
-
ensureDir();
|
|
103
|
-
writeFileSync(resolve(ATEL_DIR, 'capabilities.json'), JSON.stringify(caps, null, 2));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function loadPolicy() {
|
|
107
|
-
if (!existsSync(POLICY_FILE)) return { ...DEFAULT_POLICY };
|
|
108
|
-
try { return { ...DEFAULT_POLICY, ...JSON.parse(readFileSync(POLICY_FILE, 'utf-8')) }; } catch { return { ...DEFAULT_POLICY }; }
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function savePolicy(policy) {
|
|
112
|
-
ensureDir();
|
|
113
|
-
writeFileSync(POLICY_FILE, JSON.stringify(policy, null, 2));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function loadTasks() {
|
|
117
|
-
if (!existsSync(TASKS_FILE)) return {};
|
|
118
|
-
try { return JSON.parse(readFileSync(TASKS_FILE, 'utf-8')); } catch { return {}; }
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function saveTasks(tasks) {
|
|
122
|
-
ensureDir();
|
|
123
|
-
writeFileSync(TASKS_FILE, JSON.stringify(tasks, null, 2));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// ─── Policy Enforcement ──────────────────────────────────────────
|
|
58
|
+
// ─── Policy Enforcer ─────────────────────────────────────────────
|
|
127
59
|
|
|
128
60
|
class PolicyEnforcer {
|
|
129
|
-
constructor(policy) {
|
|
130
|
-
this.policy = policy;
|
|
131
|
-
this.requestLog = []; // timestamps of recent requests per DID
|
|
132
|
-
this.activeTasks = 0;
|
|
133
|
-
}
|
|
134
|
-
|
|
61
|
+
constructor(policy) { this.policy = policy; this.requestLog = []; this.activeTasks = 0; }
|
|
135
62
|
check(message) {
|
|
136
|
-
const p = this.policy;
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (p.
|
|
143
|
-
return { allowed: false, reason: `DID ${fromDid} is blocked` };
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// 2. Allowed DID whitelist (empty = allow all)
|
|
147
|
-
if (p.allowedDIDs.length > 0 && !p.allowedDIDs.includes(fromDid)) {
|
|
148
|
-
return { allowed: false, reason: `DID ${fromDid} is not in allowlist` };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// 3. Rate limit
|
|
152
|
-
const now = Date.now();
|
|
153
|
-
const windowMs = 60000;
|
|
154
|
-
this.requestLog = this.requestLog.filter(t => now - t < windowMs);
|
|
155
|
-
if (this.requestLog.length >= p.rateLimit) {
|
|
156
|
-
return { allowed: false, reason: `Rate limit exceeded (${p.rateLimit}/min)` };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// 4. Payload size
|
|
160
|
-
if (payloadSize > p.maxPayloadBytes) {
|
|
161
|
-
return { allowed: false, reason: `Payload too large (${payloadSize} > ${p.maxPayloadBytes} bytes)` };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// 5. Concurrent tasks
|
|
165
|
-
if (this.activeTasks >= p.maxConcurrent) {
|
|
166
|
-
return { allowed: false, reason: `Max concurrent tasks reached (${p.maxConcurrent})` };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Record this request
|
|
63
|
+
const p = this.policy, from = message.from, size = JSON.stringify(message.payload || {}).length, now = Date.now();
|
|
64
|
+
if (p.blockedDIDs.length > 0 && p.blockedDIDs.includes(from)) return { allowed: false, reason: `DID blocked` };
|
|
65
|
+
if (p.allowedDIDs.length > 0 && !p.allowedDIDs.includes(from)) return { allowed: false, reason: `DID not in allowlist` };
|
|
66
|
+
this.requestLog = this.requestLog.filter(t => now - t < 60000);
|
|
67
|
+
if (this.requestLog.length >= p.rateLimit) return { allowed: false, reason: `Rate limit (${p.rateLimit}/min)` };
|
|
68
|
+
if (size > p.maxPayloadBytes) return { allowed: false, reason: `Payload too large (${size} > ${p.maxPayloadBytes})` };
|
|
69
|
+
if (this.activeTasks >= p.maxConcurrent) return { allowed: false, reason: `Max concurrent (${p.maxConcurrent})` };
|
|
170
70
|
this.requestLog.push(now);
|
|
171
71
|
return { allowed: true };
|
|
172
72
|
}
|
|
173
|
-
|
|
174
73
|
taskStarted() { this.activeTasks++; }
|
|
175
74
|
taskFinished() { this.activeTasks = Math.max(0, this.activeTasks - 1); }
|
|
176
75
|
}
|
|
@@ -178,20 +77,14 @@ class PolicyEnforcer {
|
|
|
178
77
|
// ─── On-chain Anchoring ──────────────────────────────────────────
|
|
179
78
|
|
|
180
79
|
async function anchorOnChain(traceRoot, metadata) {
|
|
181
|
-
const
|
|
182
|
-
if (!
|
|
80
|
+
const key = process.env.ATEL_SOLANA_PRIVATE_KEY;
|
|
81
|
+
if (!key) return null;
|
|
183
82
|
try {
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
log({ event: 'proof_anchored', chain: 'solana', txHash: record.txHash, block: record.blockNumber, trace_root: traceRoot });
|
|
190
|
-
return record;
|
|
191
|
-
} catch (e) {
|
|
192
|
-
log({ event: 'anchor_failed', chain: 'solana', error: e.message });
|
|
193
|
-
return null;
|
|
194
|
-
}
|
|
83
|
+
const s = new SolanaAnchorProvider({ rpcUrl: process.env.ATEL_SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com', privateKey: key });
|
|
84
|
+
const r = await s.anchor(traceRoot, metadata);
|
|
85
|
+
log({ event: 'proof_anchored', chain: 'solana', txHash: r.txHash, block: r.blockNumber, trace_root: traceRoot });
|
|
86
|
+
return r;
|
|
87
|
+
} catch (e) { log({ event: 'anchor_failed', chain: 'solana', error: e.message }); return null; }
|
|
195
88
|
}
|
|
196
89
|
|
|
197
90
|
// ─── Commands ────────────────────────────────────────────────────
|
|
@@ -201,26 +94,38 @@ async function cmdInit(agentId) {
|
|
|
201
94
|
const identity = new AgentIdentity({ agent_id: name });
|
|
202
95
|
saveIdentity(identity);
|
|
203
96
|
savePolicy(DEFAULT_POLICY);
|
|
204
|
-
console.log(JSON.stringify({
|
|
205
|
-
status: 'created',
|
|
206
|
-
agent_id: identity.agent_id,
|
|
207
|
-
did: identity.did,
|
|
208
|
-
policy: POLICY_FILE,
|
|
209
|
-
next: 'Run: atel register [name] [capabilities] [endpoint]',
|
|
210
|
-
}, null, 2));
|
|
97
|
+
console.log(JSON.stringify({ status: 'created', agent_id: identity.agent_id, did: identity.did, policy: POLICY_FILE, next: 'Run: atel start [port] — auto-configures network and registers' }, null, 2));
|
|
211
98
|
}
|
|
212
99
|
|
|
213
100
|
async function cmdInfo() {
|
|
214
101
|
const id = requireIdentity();
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
102
|
+
console.log(JSON.stringify({ agent_id: id.agent_id, did: id.did, capabilities: loadCapabilities(), policy: loadPolicy(), network: loadNetwork(), executor: EXECUTOR_URL || 'not configured' }, null, 2));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function cmdSetup(port) {
|
|
106
|
+
const p = parseInt(port || '3100');
|
|
107
|
+
console.log(JSON.stringify({ event: 'network_setup', port: p }));
|
|
108
|
+
const net = await autoNetworkSetup(p);
|
|
109
|
+
for (const step of net.steps) console.log(JSON.stringify({ event: 'step', message: step }));
|
|
110
|
+
if (net.endpoint) {
|
|
111
|
+
saveNetwork({ publicIP: net.publicIP, port: p, endpoint: net.endpoint, upnp: net.upnpSuccess, reachable: net.reachable, configuredAt: new Date().toISOString() });
|
|
112
|
+
console.log(JSON.stringify({ status: 'ready', endpoint: net.endpoint }));
|
|
113
|
+
} else if (net.publicIP) {
|
|
114
|
+
const ep = `http://${net.publicIP}:${p}`;
|
|
115
|
+
saveNetwork({ publicIP: net.publicIP, port: p, endpoint: ep, upnp: false, reachable: false, needsManualPortForward: true, configuredAt: new Date().toISOString() });
|
|
116
|
+
console.log(JSON.stringify({ status: 'needs_port_forward', publicIP: net.publicIP, port: p, instruction: `Forward external TCP port ${p} to this machine's port ${p} on your router. Then run: atel verify` }));
|
|
117
|
+
} else {
|
|
118
|
+
console.log(JSON.stringify({ status: 'failed', error: 'Could not determine public IP' }));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function cmdVerify() {
|
|
123
|
+
const net = loadNetwork();
|
|
124
|
+
if (!net) { console.error('No network config. Run: atel setup'); process.exit(1); }
|
|
125
|
+
console.log(JSON.stringify({ event: 'verifying', ip: net.publicIP, port: net.port }));
|
|
126
|
+
const result = await verifyPortReachable(net.publicIP, net.port);
|
|
127
|
+
console.log(JSON.stringify({ status: result.reachable ? 'reachable' : 'not_reachable', detail: result.detail }));
|
|
128
|
+
if (result.reachable) { net.reachable = true; net.needsManualPortForward = false; saveNetwork(net); }
|
|
224
129
|
}
|
|
225
130
|
|
|
226
131
|
async function cmdStart(port) {
|
|
@@ -231,193 +136,124 @@ async function cmdStart(port) {
|
|
|
231
136
|
const policy = loadPolicy();
|
|
232
137
|
const enforcer = new PolicyEnforcer(policy);
|
|
233
138
|
const pendingTasks = loadTasks();
|
|
139
|
+
|
|
140
|
+
// ── Network: collect candidates ──
|
|
141
|
+
let networkConfig = loadNetwork();
|
|
142
|
+
if (!networkConfig) {
|
|
143
|
+
log({ event: 'network_setup', status: 'auto-detecting' });
|
|
144
|
+
networkConfig = await autoNetworkSetup(p);
|
|
145
|
+
for (const step of networkConfig.steps) log({ event: 'network_step', message: step });
|
|
146
|
+
delete networkConfig.steps;
|
|
147
|
+
saveNetwork(networkConfig);
|
|
148
|
+
} else {
|
|
149
|
+
log({ event: 'network_loaded', candidates: networkConfig.candidates.length });
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ── Start endpoint ──
|
|
234
153
|
const endpoint = new AgentEndpoint(id, { port: p, host: '0.0.0.0' });
|
|
235
154
|
|
|
236
|
-
// Result callback
|
|
237
|
-
// Executor calls this when done
|
|
155
|
+
// Result callback: POST /atel/v1/result (executor calls this when done)
|
|
238
156
|
endpoint.app?.post?.('/atel/v1/result', async (req, res) => {
|
|
239
157
|
const { taskId, result, success } = req.body || {};
|
|
240
|
-
if (!taskId || !pendingTasks[taskId]) {
|
|
241
|
-
res.status(404).json({ error: 'Unknown taskId' });
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
|
|
158
|
+
if (!taskId || !pendingTasks[taskId]) { res.status(404).json({ error: 'Unknown taskId' }); return; }
|
|
245
159
|
const task = pendingTasks[taskId];
|
|
246
160
|
enforcer.taskFinished();
|
|
247
161
|
|
|
248
|
-
// Complete the trace
|
|
249
162
|
const trace = new ExecutionTrace(taskId, id);
|
|
250
|
-
trace.append('TASK_ACCEPTED', { from: task.from, action: task.action
|
|
163
|
+
trace.append('TASK_ACCEPTED', { from: task.from, action: task.action });
|
|
251
164
|
trace.append('TASK_FORWARDED', { executor_url: EXECUTOR_URL });
|
|
252
165
|
trace.append('TASK_RESULT', { success: success !== false, result });
|
|
166
|
+
if (success !== false) trace.finalize(typeof result === 'object' ? result : { result });
|
|
167
|
+
else trace.fail(new Error(result?.error || 'Execution failed'));
|
|
253
168
|
|
|
254
|
-
if (success !== false) {
|
|
255
|
-
trace.finalize(typeof result === 'object' ? result : { result });
|
|
256
|
-
} else {
|
|
257
|
-
trace.fail(new Error(result?.error || 'Execution failed'));
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// Generate proof
|
|
261
169
|
const proofGen = new ProofGenerator(trace, id);
|
|
262
|
-
const proof = proofGen.generate(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
// Anchor on-chain
|
|
269
|
-
const anchor = await anchorOnChain(proof.trace_root, {
|
|
270
|
-
proof_id: proof.proof_id,
|
|
271
|
-
task_from: task.from,
|
|
272
|
-
action: task.action,
|
|
273
|
-
taskId,
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
log({
|
|
277
|
-
event: 'task_completed',
|
|
278
|
-
taskId,
|
|
279
|
-
from: task.from,
|
|
280
|
-
action: task.action,
|
|
281
|
-
success: success !== false,
|
|
282
|
-
proof_id: proof.proof_id,
|
|
283
|
-
trace_root: proof.trace_root,
|
|
284
|
-
anchor_tx: anchor?.txHash || null,
|
|
285
|
-
timestamp: new Date().toISOString(),
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
// Push result back to sender (encrypted, via their endpoint)
|
|
170
|
+
const proof = proofGen.generate(capTypes.join(',') || 'no-policy', `task-from-${task.from}`, JSON.stringify(result));
|
|
171
|
+
const anchor = await anchorOnChain(proof.trace_root, { proof_id: proof.proof_id, task_from: task.from, action: task.action, taskId });
|
|
172
|
+
|
|
173
|
+
log({ event: 'task_completed', taskId, from: task.from, action: task.action, success: success !== false, proof_id: proof.proof_id, anchor_tx: anchor?.txHash || null, timestamp: new Date().toISOString() });
|
|
174
|
+
|
|
175
|
+
// Push result back to sender
|
|
289
176
|
if (task.senderEndpoint) {
|
|
290
177
|
try {
|
|
291
178
|
const client = new AgentClient(id);
|
|
292
179
|
const hsManager = new HandshakeManager(id);
|
|
293
180
|
await client.handshake(task.senderEndpoint, hsManager, task.from);
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
payload: {
|
|
299
|
-
taskId,
|
|
300
|
-
status: success !== false ? 'completed' : 'failed',
|
|
301
|
-
result,
|
|
302
|
-
proof: { proof_id: proof.proof_id, trace_root: proof.trace_root },
|
|
303
|
-
anchor: anchor ? { chain: 'solana', txHash: anchor.txHash } : null,
|
|
304
|
-
},
|
|
305
|
-
secretKey: id.secretKey,
|
|
306
|
-
});
|
|
307
|
-
await client.sendTask(task.senderEndpoint, resultMsg, hsManager);
|
|
308
|
-
log({ event: 'result_pushed', taskId, to: task.from, endpoint: task.senderEndpoint });
|
|
309
|
-
} catch (e) {
|
|
310
|
-
log({ event: 'result_push_failed', taskId, to: task.from, error: e.message });
|
|
311
|
-
}
|
|
181
|
+
const msg = createMessage({ type: 'task-result', from: id.did, to: task.from, payload: { taskId, status: success !== false ? 'completed' : 'failed', result, proof: { proof_id: proof.proof_id, trace_root: proof.trace_root }, anchor: anchor ? { chain: 'solana', txHash: anchor.txHash } : null }, secretKey: id.secretKey });
|
|
182
|
+
await client.sendTask(task.senderEndpoint, msg, hsManager);
|
|
183
|
+
log({ event: 'result_pushed', taskId, to: task.from });
|
|
184
|
+
} catch (e) { log({ event: 'result_push_failed', taskId, error: e.message }); }
|
|
312
185
|
}
|
|
313
186
|
|
|
314
|
-
|
|
315
|
-
delete pendingTasks[taskId];
|
|
316
|
-
saveTasks(pendingTasks);
|
|
317
|
-
|
|
187
|
+
delete pendingTasks[taskId]; saveTasks(pendingTasks);
|
|
318
188
|
res.json({ status: 'ok', proof_id: proof.proof_id, anchor_tx: anchor?.txHash || null });
|
|
319
189
|
});
|
|
320
190
|
|
|
191
|
+
// Task handler
|
|
321
192
|
endpoint.onTask(async (message, session) => {
|
|
322
193
|
const payload = message.payload || {};
|
|
323
194
|
const action = payload.action || payload.type || 'unknown';
|
|
324
195
|
|
|
325
|
-
//
|
|
326
|
-
const
|
|
327
|
-
if (!
|
|
328
|
-
log({ event: 'task_rejected', from: message.from, action, reason: policyCheck.reason, timestamp: new Date().toISOString() });
|
|
329
|
-
return { status: 'rejected', error: policyCheck.reason };
|
|
330
|
-
}
|
|
196
|
+
// Policy check
|
|
197
|
+
const pc = enforcer.check(message);
|
|
198
|
+
if (!pc.allowed) { log({ event: 'task_rejected', from: message.from, action, reason: pc.reason, timestamp: new Date().toISOString() }); return { status: 'rejected', error: pc.reason }; }
|
|
331
199
|
|
|
332
|
-
//
|
|
200
|
+
// Capability check
|
|
333
201
|
if (capTypes.length > 0 && !capTypes.includes(action) && !capTypes.includes('general')) {
|
|
334
|
-
log({ event: 'task_rejected', from: message.from, action, reason: `Outside capability
|
|
202
|
+
log({ event: 'task_rejected', from: message.from, action, reason: `Outside capability: [${capTypes.join(',')}]`, timestamp: new Date().toISOString() });
|
|
335
203
|
return { status: 'rejected', error: `Action "${action}" outside capability boundary`, capabilities: capTypes };
|
|
336
204
|
}
|
|
337
205
|
|
|
338
|
-
// ── Accept task (async) ──
|
|
339
206
|
const taskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
340
207
|
enforcer.taskStarted();
|
|
341
208
|
|
|
342
|
-
//
|
|
209
|
+
// Lookup sender endpoint for result push-back
|
|
343
210
|
let senderEndpoint = null;
|
|
344
|
-
try {
|
|
345
|
-
const regClient = new RegistryClient({ registryUrl: REGISTRY_URL });
|
|
346
|
-
const resp = await fetch(`${REGISTRY_URL}/registry/v1/agent/${encodeURIComponent(message.from)}`);
|
|
347
|
-
if (resp.ok) {
|
|
348
|
-
const entry = await resp.json();
|
|
349
|
-
senderEndpoint = entry.endpoint;
|
|
350
|
-
}
|
|
351
|
-
} catch { /* best effort */ }
|
|
352
|
-
|
|
353
|
-
pendingTasks[taskId] = {
|
|
354
|
-
from: message.from,
|
|
355
|
-
action,
|
|
356
|
-
payload,
|
|
357
|
-
senderEndpoint,
|
|
358
|
-
encrypted: !!session?.encrypted,
|
|
359
|
-
acceptedAt: new Date().toISOString(),
|
|
360
|
-
};
|
|
361
|
-
saveTasks(pendingTasks);
|
|
211
|
+
try { const r = await fetch(`${REGISTRY_URL}/registry/v1/agent/${encodeURIComponent(message.from)}`); if (r.ok) senderEndpoint = (await r.json()).endpoint; } catch {}
|
|
362
212
|
|
|
213
|
+
pendingTasks[taskId] = { from: message.from, action, payload, senderEndpoint, encrypted: !!session?.encrypted, acceptedAt: new Date().toISOString() };
|
|
214
|
+
saveTasks(pendingTasks);
|
|
363
215
|
log({ event: 'task_accepted', taskId, from: message.from, action, encrypted: !!session?.encrypted, timestamp: new Date().toISOString() });
|
|
364
216
|
|
|
365
|
-
//
|
|
217
|
+
// Forward to executor or echo
|
|
366
218
|
if (EXECUTOR_URL) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
fetch(EXECUTOR_URL, {
|
|
370
|
-
method: 'POST',
|
|
371
|
-
headers: { 'Content-Type': 'application/json' },
|
|
372
|
-
body: JSON.stringify(execPayload),
|
|
373
|
-
}).catch(e => {
|
|
374
|
-
log({ event: 'executor_forward_failed', taskId, error: e.message });
|
|
375
|
-
});
|
|
376
|
-
// Don't await — async execution
|
|
377
|
-
} catch (e) {
|
|
378
|
-
log({ event: 'executor_forward_failed', taskId, error: e.message });
|
|
379
|
-
}
|
|
219
|
+
fetch(EXECUTOR_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ taskId, from: message.from, action, payload, encrypted: !!session?.encrypted }) }).catch(e => log({ event: 'executor_forward_failed', taskId, error: e.message }));
|
|
220
|
+
return { status: 'accepted', taskId, message: 'Task accepted. Result will be pushed when ready.' };
|
|
380
221
|
} else {
|
|
381
|
-
//
|
|
222
|
+
// Echo mode
|
|
382
223
|
enforcer.taskFinished();
|
|
383
224
|
const trace = new ExecutionTrace(taskId, id);
|
|
384
225
|
trace.append('TASK_ACCEPTED', { from: message.from, action, payload });
|
|
385
226
|
const result = { status: 'no_executor', agent: id.agent_id, action, received_payload: payload };
|
|
386
|
-
trace.append('TASK_ECHO', { result });
|
|
387
|
-
trace.finalize(result);
|
|
227
|
+
trace.append('TASK_ECHO', { result }); trace.finalize(result);
|
|
388
228
|
const proofGen = new ProofGenerator(trace, id);
|
|
389
229
|
const proof = proofGen.generate(capTypes.join(',') || 'no-policy', `task-from-${message.from}`, JSON.stringify(result));
|
|
390
230
|
const anchor = await anchorOnChain(proof.trace_root, { proof_id: proof.proof_id, task_from: message.from, action, taskId });
|
|
391
|
-
delete pendingTasks[taskId];
|
|
392
|
-
saveTasks(pendingTasks);
|
|
231
|
+
delete pendingTasks[taskId]; saveTasks(pendingTasks);
|
|
393
232
|
log({ event: 'task_completed', taskId, from: message.from, action, mode: 'echo', proof_id: proof.proof_id, anchor_tx: anchor?.txHash || null, timestamp: new Date().toISOString() });
|
|
394
233
|
return { status: 'completed', taskId, result, proof, anchor };
|
|
395
234
|
}
|
|
396
|
-
|
|
397
|
-
// Return accepted immediately — result comes later via push-back
|
|
398
|
-
return { status: 'accepted', taskId, message: 'Task accepted. Result will be pushed to your endpoint when ready.' };
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
endpoint.onProof(async (message, session) => {
|
|
402
|
-
log({ event: 'proof_received', from: message.from, payload: message.payload, timestamp: new Date().toISOString() });
|
|
403
235
|
});
|
|
404
236
|
|
|
405
|
-
|
|
406
|
-
endpoint.onMessage?.('task-result', async (message, session) => {
|
|
407
|
-
log({ event: 'task_result_received', from: message.from, payload: message.payload, timestamp: new Date().toISOString() });
|
|
408
|
-
});
|
|
237
|
+
endpoint.onProof(async (message) => { log({ event: 'proof_received', from: message.from, payload: message.payload, timestamp: new Date().toISOString() }); });
|
|
409
238
|
|
|
410
239
|
await endpoint.start();
|
|
240
|
+
|
|
241
|
+
// Auto-register to Registry with candidates
|
|
242
|
+
if (capTypes.length > 0 && networkConfig.candidates.length > 0) {
|
|
243
|
+
try {
|
|
244
|
+
const regClient = new RegistryClient({ registryUrl: REGISTRY_URL });
|
|
245
|
+
// Pick best non-relay candidate as primary endpoint for backward compat
|
|
246
|
+
const bestDirect = networkConfig.candidates.find(c => c.type !== 'relay') || networkConfig.candidates[0];
|
|
247
|
+
await regClient.register({ name: id.agent_id, capabilities: caps, endpoint: bestDirect.url, candidates: networkConfig.candidates }, id);
|
|
248
|
+
log({ event: 'auto_registered', registry: REGISTRY_URL, candidates: networkConfig.candidates.length });
|
|
249
|
+
} catch (e) { log({ event: 'auto_register_failed', error: e.message }); }
|
|
250
|
+
}
|
|
251
|
+
|
|
411
252
|
console.log(JSON.stringify({
|
|
412
|
-
status: 'listening',
|
|
413
|
-
|
|
414
|
-
did: id.did,
|
|
415
|
-
endpoint: endpoint.getEndpointUrl(),
|
|
416
|
-
port: p,
|
|
417
|
-
capabilities: capTypes,
|
|
253
|
+
status: 'listening', agent_id: id.agent_id, did: id.did,
|
|
254
|
+
port: p, candidates: networkConfig.candidates, capabilities: capTypes,
|
|
418
255
|
policy: { rateLimit: policy.rateLimit, maxPayloadBytes: policy.maxPayloadBytes, maxConcurrent: policy.maxConcurrent, allowedDIDs: policy.allowedDIDs.length, blockedDIDs: policy.blockedDIDs.length },
|
|
419
|
-
executor: EXECUTOR_URL || '
|
|
420
|
-
inbox: INBOX_FILE,
|
|
256
|
+
executor: EXECUTOR_URL || 'echo mode', inbox: INBOX_FILE,
|
|
421
257
|
}, null, 2));
|
|
422
258
|
|
|
423
259
|
process.on('SIGINT', async () => { await endpoint.stop(); process.exit(0); });
|
|
@@ -426,10 +262,7 @@ async function cmdStart(port) {
|
|
|
426
262
|
|
|
427
263
|
async function cmdInbox(count) {
|
|
428
264
|
const n = parseInt(count || '20');
|
|
429
|
-
if (!existsSync(INBOX_FILE)) {
|
|
430
|
-
console.log(JSON.stringify({ messages: [], count: 0 }));
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
265
|
+
if (!existsSync(INBOX_FILE)) { console.log(JSON.stringify({ messages: [], count: 0 })); return; }
|
|
433
266
|
const lines = readFileSync(INBOX_FILE, 'utf-8').trim().split('\n').filter(Boolean);
|
|
434
267
|
const messages = lines.slice(-n).map(l => JSON.parse(l));
|
|
435
268
|
console.log(JSON.stringify({ messages, count: messages.length, total: lines.length }, null, 2));
|
|
@@ -437,15 +270,14 @@ async function cmdInbox(count) {
|
|
|
437
270
|
|
|
438
271
|
async function cmdRegister(name, capabilities, endpointUrl) {
|
|
439
272
|
const id = requireIdentity();
|
|
440
|
-
const client = new RegistryClient({ registryUrl: REGISTRY_URL });
|
|
441
273
|
const caps = (capabilities || 'general').split(',').map(c => ({ type: c.trim(), description: c.trim() }));
|
|
442
274
|
saveCapabilities(caps);
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
}, id);
|
|
448
|
-
console.log(JSON.stringify({ status: 'registered', did: entry.did, name: entry.name, capabilities: caps.map(c => c.type), registry: REGISTRY_URL }, null, 2));
|
|
275
|
+
// If no endpoint provided, use saved network config
|
|
276
|
+
let ep = endpointUrl;
|
|
277
|
+
if (!ep) { const net = loadNetwork(); ep = net?.endpoint || 'http://localhost:3100'; }
|
|
278
|
+
const client = new RegistryClient({ registryUrl: REGISTRY_URL });
|
|
279
|
+
const entry = await client.register({ name: name || id.agent_id, capabilities: caps, endpoint: ep }, id);
|
|
280
|
+
console.log(JSON.stringify({ status: 'registered', did: entry.did, name: entry.name, capabilities: caps.map(c => c.type), endpoint: ep, registry: REGISTRY_URL }, null, 2));
|
|
449
281
|
}
|
|
450
282
|
|
|
451
283
|
async function cmdSearch(capability) {
|
|
@@ -459,66 +291,87 @@ async function cmdHandshake(remoteEndpoint, remoteDid) {
|
|
|
459
291
|
const client = new AgentClient(id);
|
|
460
292
|
const hsManager = new HandshakeManager(id);
|
|
461
293
|
let did = remoteDid;
|
|
462
|
-
if (!did) {
|
|
463
|
-
const health = await client.health(remoteEndpoint);
|
|
464
|
-
did = health.did;
|
|
465
|
-
}
|
|
294
|
+
if (!did) { const h = await client.health(remoteEndpoint); did = h.did; }
|
|
466
295
|
const session = await client.handshake(remoteEndpoint, hsManager, did);
|
|
467
296
|
console.log(JSON.stringify({ status: 'handshake_complete', sessionId: session.sessionId, remoteDid: did, encrypted: session.encrypted }, null, 2));
|
|
468
|
-
const
|
|
469
|
-
let sessions = {};
|
|
470
|
-
if (existsSync(sessFile)) sessions = JSON.parse(readFileSync(sessFile, 'utf-8'));
|
|
297
|
+
const sf = resolve(ATEL_DIR, 'sessions.json');
|
|
298
|
+
let sessions = {}; if (existsSync(sf)) sessions = JSON.parse(readFileSync(sf, 'utf-8'));
|
|
471
299
|
sessions[remoteEndpoint] = { did, sessionId: session.sessionId, encrypted: session.encrypted };
|
|
472
|
-
writeFileSync(
|
|
300
|
+
writeFileSync(sf, JSON.stringify(sessions, null, 2));
|
|
473
301
|
}
|
|
474
302
|
|
|
475
|
-
async function cmdTask(
|
|
303
|
+
async function cmdTask(target, taskJson) {
|
|
476
304
|
const id = requireIdentity();
|
|
477
305
|
const client = new AgentClient(id);
|
|
478
306
|
const hsManager = new HandshakeManager(id);
|
|
479
|
-
|
|
307
|
+
|
|
308
|
+
let remoteEndpoint = target;
|
|
480
309
|
let remoteDid;
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
310
|
+
|
|
311
|
+
// If target looks like a DID or name, search Registry and try candidates
|
|
312
|
+
if (!target.startsWith('http')) {
|
|
313
|
+
const regClient = new RegistryClient({ registryUrl: REGISTRY_URL });
|
|
314
|
+
let entry;
|
|
315
|
+
// Try as DID first
|
|
316
|
+
try {
|
|
317
|
+
const resp = await fetch(`${REGISTRY_URL}/registry/v1/agent/${encodeURIComponent(target)}`);
|
|
318
|
+
if (resp.ok) entry = await resp.json();
|
|
319
|
+
} catch {}
|
|
320
|
+
// Try as capability search
|
|
321
|
+
if (!entry) {
|
|
322
|
+
const results = await regClient.search({ type: target, limit: 5 });
|
|
323
|
+
if (results.length > 0) entry = results[0];
|
|
324
|
+
}
|
|
325
|
+
if (!entry) { console.error(`Agent not found: ${target}`); process.exit(1); }
|
|
326
|
+
|
|
327
|
+
remoteDid = entry.did;
|
|
328
|
+
|
|
329
|
+
// Try candidates if available
|
|
330
|
+
if (entry.candidates && entry.candidates.length > 0) {
|
|
331
|
+
console.log(JSON.stringify({ event: 'connecting', did: remoteDid, candidates: entry.candidates.length }));
|
|
332
|
+
const conn = await connectToAgent(entry.candidates);
|
|
333
|
+
if (conn) {
|
|
334
|
+
remoteEndpoint = conn.url;
|
|
335
|
+
console.log(JSON.stringify({ event: 'connected', type: conn.candidateType, url: conn.url, latencyMs: conn.latencyMs }));
|
|
336
|
+
} else {
|
|
337
|
+
console.error('All candidates unreachable'); process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
} else {
|
|
340
|
+
remoteEndpoint = entry.endpoint;
|
|
341
|
+
}
|
|
484
342
|
}
|
|
343
|
+
|
|
344
|
+
// Handshake
|
|
345
|
+
const sf = resolve(ATEL_DIR, 'sessions.json');
|
|
485
346
|
if (!remoteDid) {
|
|
486
|
-
const
|
|
487
|
-
remoteDid = health.did;
|
|
488
|
-
const session = await client.handshake(remoteEndpoint, hsManager, remoteDid);
|
|
489
|
-
let sessions = {};
|
|
490
|
-
if (existsSync(sessFile)) sessions = JSON.parse(readFileSync(sessFile, 'utf-8'));
|
|
491
|
-
sessions[remoteEndpoint] = { did: remoteDid, sessionId: session.sessionId, encrypted: session.encrypted };
|
|
492
|
-
writeFileSync(sessFile, JSON.stringify(sessions, null, 2));
|
|
493
|
-
} else {
|
|
494
|
-
await client.handshake(remoteEndpoint, hsManager, remoteDid);
|
|
347
|
+
const h = await client.health(remoteEndpoint); remoteDid = h.did;
|
|
495
348
|
}
|
|
349
|
+
await client.handshake(remoteEndpoint, hsManager, remoteDid);
|
|
350
|
+
let sessions = {}; if (existsSync(sf)) sessions = JSON.parse(readFileSync(sf, 'utf-8'));
|
|
351
|
+
sessions[remoteEndpoint] = { did: remoteDid };
|
|
352
|
+
writeFileSync(sf, JSON.stringify(sessions, null, 2));
|
|
353
|
+
|
|
354
|
+
// Send task
|
|
496
355
|
const payload = typeof taskJson === 'string' ? JSON.parse(taskJson) : taskJson;
|
|
497
356
|
const msg = createMessage({ type: 'task', from: id.did, to: remoteDid, payload, secretKey: id.secretKey });
|
|
498
357
|
const result = await client.sendTask(remoteEndpoint, msg, hsManager);
|
|
499
|
-
console.log(JSON.stringify({ status: 'task_sent', remoteDid, result }, null, 2));
|
|
358
|
+
console.log(JSON.stringify({ status: 'task_sent', remoteDid, via: remoteEndpoint, result }, null, 2));
|
|
500
359
|
}
|
|
501
360
|
|
|
502
361
|
async function cmdResult(taskId, resultJson) {
|
|
503
|
-
// Submit execution result back to local atel endpoint
|
|
504
362
|
const result = typeof resultJson === 'string' ? JSON.parse(resultJson) : resultJson;
|
|
505
|
-
const
|
|
506
|
-
|
|
507
|
-
method: 'POST',
|
|
508
|
-
headers: { 'Content-Type': 'application/json' },
|
|
509
|
-
body: JSON.stringify({ taskId, result, success: true }),
|
|
510
|
-
});
|
|
511
|
-
const data = await resp.json();
|
|
512
|
-
console.log(JSON.stringify(data, null, 2));
|
|
363
|
+
const resp = await fetch(`http://localhost:${process.env.ATEL_PORT || '3100'}/atel/v1/result`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ taskId, result, success: true }) });
|
|
364
|
+
console.log(JSON.stringify(await resp.json(), null, 2));
|
|
513
365
|
}
|
|
514
366
|
|
|
515
367
|
// ─── Main ────────────────────────────────────────────────────────
|
|
516
368
|
|
|
517
369
|
const [,, cmd, ...args] = process.argv;
|
|
518
|
-
|
|
519
370
|
const commands = {
|
|
520
371
|
init: () => cmdInit(args[0]),
|
|
521
372
|
info: () => cmdInfo(),
|
|
373
|
+
setup: () => cmdSetup(args[0]),
|
|
374
|
+
verify: () => cmdVerify(),
|
|
522
375
|
start: () => cmdStart(args[0]),
|
|
523
376
|
inbox: () => cmdInbox(args[0]),
|
|
524
377
|
register: () => cmdRegister(args[0], args[1], args[2]),
|
|
@@ -534,9 +387,11 @@ if (!cmd || !commands[cmd]) {
|
|
|
534
387
|
Usage: atel <command> [args]
|
|
535
388
|
|
|
536
389
|
Commands:
|
|
537
|
-
init [name] Create agent identity +
|
|
538
|
-
info Show identity, capabilities, policy
|
|
539
|
-
|
|
390
|
+
init [name] Create agent identity + security policy
|
|
391
|
+
info Show identity, capabilities, network, policy
|
|
392
|
+
setup [port] Configure network (detect IP, UPnP, verify)
|
|
393
|
+
verify Verify port reachability
|
|
394
|
+
start [port] Start endpoint (auto network + auto register)
|
|
540
395
|
inbox [count] Show received messages (default: 20)
|
|
541
396
|
register [name] [caps] [endpoint] Register on public registry
|
|
542
397
|
search <capability> Search registry for agents
|
|
@@ -545,22 +400,16 @@ Commands:
|
|
|
545
400
|
result <taskId> <json> Submit execution result (from executor)
|
|
546
401
|
|
|
547
402
|
Environment:
|
|
548
|
-
ATEL_DIR
|
|
549
|
-
ATEL_REGISTRY
|
|
550
|
-
ATEL_EXECUTOR_URL
|
|
551
|
-
ATEL_SOLANA_PRIVATE_KEY
|
|
552
|
-
ATEL_SOLANA_RPC_URL
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
maxConcurrent Max concurrent tasks (default: 10)
|
|
558
|
-
allowedDIDs DID whitelist (empty = allow all)
|
|
559
|
-
blockedDIDs DID blacklist`);
|
|
403
|
+
ATEL_DIR Identity directory (default: .atel)
|
|
404
|
+
ATEL_REGISTRY Registry URL (default: http://47.251.8.19:8100)
|
|
405
|
+
ATEL_EXECUTOR_URL Local executor HTTP endpoint
|
|
406
|
+
ATEL_SOLANA_PRIVATE_KEY Solana key for on-chain anchoring
|
|
407
|
+
ATEL_SOLANA_RPC_URL Solana RPC (default: mainnet-beta)
|
|
408
|
+
|
|
409
|
+
Network: atel start auto-detects public IP, attempts UPnP port mapping,
|
|
410
|
+
and registers to the Registry. If UPnP fails, configure port forwarding
|
|
411
|
+
on your router and run: atel verify`);
|
|
560
412
|
process.exit(cmd ? 1 : 0);
|
|
561
413
|
}
|
|
562
414
|
|
|
563
|
-
commands[cmd]().catch(err => {
|
|
564
|
-
console.error(JSON.stringify({ error: err.message }));
|
|
565
|
-
process.exit(1);
|
|
566
|
-
});
|
|
415
|
+
commands[cmd]().catch(err => { console.error(JSON.stringify({ error: err.message })); process.exit(1); });
|