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