agentid-cli 0.1.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 +132 -0
- package/cli/agentid.js +185 -0
- package/data/academy/congressional-ai/FINAL_REPORT.md +97 -0
- package/data/academy/congressional-ai/commdaaf_prompt_v1.md +358 -0
- package/data/academy/congressional-ai/pilot_25_claude.json +252 -0
- package/data/academy/congressional-ai/pilot_batch_25.json +227 -0
- package/data/academy/index.json +57 -0
- package/data/academy/logs/agentacademy_results.json +1059 -0
- package/data/academy/prompts/commdaaf_global_south.md +75 -0
- package/data/academy/prompts/glm-adversarial.md +69 -0
- package/data/academy/prompts/kimi-adversarial.md +75 -0
- package/data/academy/prompts/primary-analysis.md +59 -0
- package/data/agents.json +13 -0
- package/data/challenges.json +1 -0
- package/data/credentials.json +11 -0
- package/lib/client.js +120 -0
- package/lib/index.js +136 -0
- package/package.json +25 -0
- package/public/index.html +768 -0
- package/server/data-routes.js +248 -0
- package/server/index.js +332 -0
- package/server/lite.js +315 -0
- package/server.log +2 -0
- package/test/run.js +120 -0
package/server/lite.js
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentID Server (Lite) - JSON file store, no native deps
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import express from 'express';
|
|
6
|
+
import cors from 'cors';
|
|
7
|
+
import { deriveAgentId, verifySignature } from '../lib/index.js';
|
|
8
|
+
import { randomBytes } from 'crypto';
|
|
9
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
10
|
+
import { dirname, join } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import academyRoutes from './data-routes.js';
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
const app = express();
|
|
17
|
+
const PORT = process.env.AGENTID_PORT || 3847;
|
|
18
|
+
const DATA_DIR = process.env.AGENTID_DATA || './data';
|
|
19
|
+
const AGENTS_FILE = `${DATA_DIR}/agents.json`;
|
|
20
|
+
const CREDENTIALS_FILE = `${DATA_DIR}/credentials.json`;
|
|
21
|
+
const CHALLENGES_FILE = `${DATA_DIR}/challenges.json`;
|
|
22
|
+
|
|
23
|
+
// Ensure data dir exists
|
|
24
|
+
if (!existsSync(DATA_DIR)) {
|
|
25
|
+
mkdirSync(DATA_DIR, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Simple JSON store
|
|
29
|
+
function loadJson(file, defaultValue = {}) {
|
|
30
|
+
if (!existsSync(file)) return defaultValue;
|
|
31
|
+
return JSON.parse(readFileSync(file, 'utf-8'));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function saveJson(file, data) {
|
|
35
|
+
writeFileSync(file, JSON.stringify(data, null, 2));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Middleware
|
|
39
|
+
app.use(cors());
|
|
40
|
+
app.use(express.json());
|
|
41
|
+
|
|
42
|
+
// Serve static files (landing page)
|
|
43
|
+
app.use(express.static(join(__dirname, '../public')));
|
|
44
|
+
|
|
45
|
+
// ============ API Routes ============
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* POST /api/agents/enroll
|
|
49
|
+
*/
|
|
50
|
+
app.post('/api/agents/enroll', (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
const { pubkey, agentId, metadata, timestamp, signature } = req.body;
|
|
53
|
+
|
|
54
|
+
if (!pubkey || !signature) {
|
|
55
|
+
return res.status(400).json({ error: 'Missing pubkey or signature' });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const expectedAgentId = deriveAgentId(pubkey);
|
|
59
|
+
if (agentId !== expectedAgentId) {
|
|
60
|
+
return res.status(400).json({ error: 'Invalid agent ID derivation' });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const payload = JSON.stringify({ pubkey, agentId, metadata, timestamp });
|
|
64
|
+
if (!verifySignature(payload, signature, pubkey)) {
|
|
65
|
+
return res.status(401).json({ error: 'Invalid signature' });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const agents = loadJson(AGENTS_FILE, {});
|
|
69
|
+
|
|
70
|
+
if (agents[agentId]) {
|
|
71
|
+
return res.status(200).json({
|
|
72
|
+
agent_id: agentId,
|
|
73
|
+
enrolled_at: agents[agentId].enrolled_at,
|
|
74
|
+
already_enrolled: true
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const enrolled_at = new Date().toISOString();
|
|
79
|
+
agents[agentId] = {
|
|
80
|
+
agent_id: agentId,
|
|
81
|
+
pubkey,
|
|
82
|
+
name: metadata?.name || null,
|
|
83
|
+
framework: metadata?.framework || null,
|
|
84
|
+
metadata: metadata || {},
|
|
85
|
+
enrolled_at
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
saveJson(AGENTS_FILE, agents);
|
|
89
|
+
|
|
90
|
+
res.status(201).json({
|
|
91
|
+
agent_id: agentId,
|
|
92
|
+
enrolled_at,
|
|
93
|
+
already_enrolled: false
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error('Enrollment error:', error);
|
|
98
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* GET /api/agents/:agentId
|
|
104
|
+
*/
|
|
105
|
+
app.get('/api/agents/:agentId', (req, res) => {
|
|
106
|
+
const agents = loadJson(AGENTS_FILE, {});
|
|
107
|
+
const agent = agents[req.params.agentId];
|
|
108
|
+
|
|
109
|
+
if (!agent) {
|
|
110
|
+
return res.status(404).json({ error: 'Agent not found' });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
res.json({
|
|
114
|
+
agent_id: agent.agent_id,
|
|
115
|
+
name: agent.name,
|
|
116
|
+
framework: agent.framework,
|
|
117
|
+
enrolled_at: agent.enrolled_at
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* GET /api/agents/:agentId/credentials
|
|
123
|
+
*/
|
|
124
|
+
app.get('/api/agents/:agentId/credentials', (req, res) => {
|
|
125
|
+
const agents = loadJson(AGENTS_FILE, {});
|
|
126
|
+
const agent = agents[req.params.agentId];
|
|
127
|
+
|
|
128
|
+
if (!agent) {
|
|
129
|
+
return res.status(404).json({ error: 'Agent not found' });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const allCredentials = loadJson(CREDENTIALS_FILE, {});
|
|
133
|
+
const credentials = allCredentials[req.params.agentId] || [];
|
|
134
|
+
|
|
135
|
+
res.json({
|
|
136
|
+
agent_id: agent.agent_id,
|
|
137
|
+
credentials
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* POST /api/agents/challenge
|
|
143
|
+
*/
|
|
144
|
+
app.post('/api/agents/challenge', (req, res) => {
|
|
145
|
+
const { agent_id } = req.body;
|
|
146
|
+
|
|
147
|
+
if (!agent_id) {
|
|
148
|
+
return res.status(400).json({ error: 'Missing agent_id' });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const agents = loadJson(AGENTS_FILE, {});
|
|
152
|
+
if (!agents[agent_id]) {
|
|
153
|
+
return res.status(404).json({ error: 'Agent not found' });
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const challenge = randomBytes(32).toString('base64url');
|
|
157
|
+
const created_at = new Date().toISOString();
|
|
158
|
+
const expires_at = new Date(Date.now() + 5 * 60 * 1000).toISOString();
|
|
159
|
+
|
|
160
|
+
const challenges = loadJson(CHALLENGES_FILE, {});
|
|
161
|
+
challenges[challenge] = { agent_id, created_at, expires_at };
|
|
162
|
+
saveJson(CHALLENGES_FILE, challenges);
|
|
163
|
+
|
|
164
|
+
res.json({ challenge, expires_at });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* POST /api/agents/verify
|
|
169
|
+
*/
|
|
170
|
+
app.post('/api/agents/verify', (req, res) => {
|
|
171
|
+
try {
|
|
172
|
+
const { agentId, challenge, signature } = req.body;
|
|
173
|
+
|
|
174
|
+
if (!agentId || !challenge || !signature) {
|
|
175
|
+
return res.status(400).json({ error: 'Missing required fields' });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const challenges = loadJson(CHALLENGES_FILE, {});
|
|
179
|
+
const challengeRecord = challenges[challenge];
|
|
180
|
+
|
|
181
|
+
if (!challengeRecord) {
|
|
182
|
+
return res.status(400).json({ error: 'Invalid or expired challenge' });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (new Date(challengeRecord.expires_at) < new Date()) {
|
|
186
|
+
delete challenges[challenge];
|
|
187
|
+
saveJson(CHALLENGES_FILE, challenges);
|
|
188
|
+
return res.status(400).json({ error: 'Challenge expired' });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (challengeRecord.agent_id !== agentId) {
|
|
192
|
+
return res.status(400).json({ error: 'Challenge not for this agent' });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const agents = loadJson(AGENTS_FILE, {});
|
|
196
|
+
const agent = agents[agentId];
|
|
197
|
+
|
|
198
|
+
if (!agent) {
|
|
199
|
+
return res.status(404).json({ error: 'Agent not found' });
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const message = `${agentId}:${challenge}`;
|
|
203
|
+
const valid = verifySignature(message, signature, agent.pubkey);
|
|
204
|
+
|
|
205
|
+
delete challenges[challenge];
|
|
206
|
+
saveJson(CHALLENGES_FILE, challenges);
|
|
207
|
+
|
|
208
|
+
if (!valid) {
|
|
209
|
+
return res.status(401).json({ valid: false, error: 'Invalid signature' });
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
res.json({
|
|
213
|
+
valid: true,
|
|
214
|
+
agent_id: agentId,
|
|
215
|
+
verified_at: new Date().toISOString()
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('Verification error:', error);
|
|
220
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* POST /api/credentials/issue
|
|
226
|
+
*/
|
|
227
|
+
app.post('/api/credentials/issue', (req, res) => {
|
|
228
|
+
try {
|
|
229
|
+
const { agent_id, skill, level, issued_by, metadata } = req.body;
|
|
230
|
+
|
|
231
|
+
if (!agent_id || !skill || !level) {
|
|
232
|
+
return res.status(400).json({ error: 'Missing required fields' });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const agents = loadJson(AGENTS_FILE, {});
|
|
236
|
+
if (!agents[agent_id]) {
|
|
237
|
+
return res.status(404).json({ error: 'Agent not found' });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const issued_at = new Date().toISOString();
|
|
241
|
+
const allCredentials = loadJson(CREDENTIALS_FILE, {});
|
|
242
|
+
|
|
243
|
+
if (!allCredentials[agent_id]) {
|
|
244
|
+
allCredentials[agent_id] = [];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Remove existing credential for same skill
|
|
248
|
+
allCredentials[agent_id] = allCredentials[agent_id].filter(c => c.skill !== skill);
|
|
249
|
+
|
|
250
|
+
allCredentials[agent_id].push({
|
|
251
|
+
skill,
|
|
252
|
+
level,
|
|
253
|
+
issued_at,
|
|
254
|
+
issued_by: issued_by || 'agentacademy',
|
|
255
|
+
metadata: metadata || {}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
saveJson(CREDENTIALS_FILE, allCredentials);
|
|
259
|
+
|
|
260
|
+
res.status(201).json({ agent_id, skill, level, issued_at });
|
|
261
|
+
|
|
262
|
+
} catch (error) {
|
|
263
|
+
console.error('Credential issue error:', error);
|
|
264
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* GET /api/stats
|
|
270
|
+
*/
|
|
271
|
+
app.get('/api/stats', (req, res) => {
|
|
272
|
+
const agents = loadJson(AGENTS_FILE, {});
|
|
273
|
+
const credentials = loadJson(CREDENTIALS_FILE, {});
|
|
274
|
+
|
|
275
|
+
const agentCount = Object.keys(agents).length;
|
|
276
|
+
const credentialCount = Object.values(credentials).reduce((sum, arr) => sum + arr.length, 0);
|
|
277
|
+
|
|
278
|
+
res.json({
|
|
279
|
+
agents_enrolled: agentCount,
|
|
280
|
+
credentials_issued: credentialCount
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* GET /api/agents
|
|
286
|
+
* List all agents (public info only)
|
|
287
|
+
*/
|
|
288
|
+
app.get('/api/agents', (req, res) => {
|
|
289
|
+
const agents = loadJson(AGENTS_FILE, {});
|
|
290
|
+
|
|
291
|
+
const list = Object.values(agents).map(a => ({
|
|
292
|
+
agent_id: a.agent_id,
|
|
293
|
+
name: a.name,
|
|
294
|
+
framework: a.framework,
|
|
295
|
+
enrolled_at: a.enrolled_at
|
|
296
|
+
}));
|
|
297
|
+
|
|
298
|
+
res.json({ agents: list, count: list.length });
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Academy data routes
|
|
302
|
+
app.use('/api/academy', academyRoutes);
|
|
303
|
+
|
|
304
|
+
// Health check
|
|
305
|
+
app.get('/health', (req, res) => {
|
|
306
|
+
res.json({ status: 'ok', service: 'agentid-lite' });
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Start server
|
|
310
|
+
app.listen(PORT, () => {
|
|
311
|
+
console.log(`AgentID (lite) server running on port ${PORT}`);
|
|
312
|
+
console.log(`Data directory: ${DATA_DIR}`);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
export default app;
|
package/server.log
ADDED
package/test/run.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentID Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
generateIdentity,
|
|
7
|
+
deriveAgentId,
|
|
8
|
+
signMessage,
|
|
9
|
+
verifySignature,
|
|
10
|
+
createEnrollmentRequest,
|
|
11
|
+
signChallenge
|
|
12
|
+
} from '../lib/index.js';
|
|
13
|
+
|
|
14
|
+
let passed = 0;
|
|
15
|
+
let failed = 0;
|
|
16
|
+
|
|
17
|
+
function test(name, fn) {
|
|
18
|
+
try {
|
|
19
|
+
fn();
|
|
20
|
+
console.log(`✓ ${name}`);
|
|
21
|
+
passed++;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.log(`✗ ${name}`);
|
|
24
|
+
console.log(` ${error.message}`);
|
|
25
|
+
failed++;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function assert(condition, message) {
|
|
30
|
+
if (!condition) throw new Error(message || 'Assertion failed');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Tests
|
|
34
|
+
|
|
35
|
+
test('generateIdentity creates valid keys', () => {
|
|
36
|
+
const identity = generateIdentity();
|
|
37
|
+
assert(identity.publicKey, 'should have publicKey');
|
|
38
|
+
assert(identity.privateKey, 'should have privateKey');
|
|
39
|
+
assert(identity.agentId, 'should have agentId');
|
|
40
|
+
assert(identity.agentId.startsWith('aa_'), 'agentId should start with aa_');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('deriveAgentId is deterministic', () => {
|
|
44
|
+
const identity = generateIdentity();
|
|
45
|
+
const derived = deriveAgentId(identity.publicKey);
|
|
46
|
+
assert(derived === identity.agentId, 'derived ID should match');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('signMessage and verifySignature work', () => {
|
|
50
|
+
const identity = generateIdentity();
|
|
51
|
+
const message = 'hello world';
|
|
52
|
+
|
|
53
|
+
const signature = signMessage(message, identity.privateKey);
|
|
54
|
+
assert(signature, 'should produce signature');
|
|
55
|
+
|
|
56
|
+
const valid = verifySignature(message, signature, identity.publicKey);
|
|
57
|
+
assert(valid, 'signature should verify');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('verifySignature rejects wrong message', () => {
|
|
61
|
+
const identity = generateIdentity();
|
|
62
|
+
const signature = signMessage('hello', identity.privateKey);
|
|
63
|
+
|
|
64
|
+
const valid = verifySignature('world', signature, identity.publicKey);
|
|
65
|
+
assert(!valid, 'should reject wrong message');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('verifySignature rejects wrong key', () => {
|
|
69
|
+
const identity1 = generateIdentity();
|
|
70
|
+
const identity2 = generateIdentity();
|
|
71
|
+
|
|
72
|
+
const signature = signMessage('hello', identity1.privateKey);
|
|
73
|
+
const valid = verifySignature('hello', signature, identity2.publicKey);
|
|
74
|
+
|
|
75
|
+
assert(!valid, 'should reject wrong key');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('createEnrollmentRequest produces valid request', () => {
|
|
79
|
+
const identity = generateIdentity();
|
|
80
|
+
const request = createEnrollmentRequest(
|
|
81
|
+
identity.publicKey,
|
|
82
|
+
identity.privateKey,
|
|
83
|
+
{ name: 'TestAgent', framework: 'openclaw' }
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
assert(request.pubkey === identity.publicKey, 'should include pubkey');
|
|
87
|
+
assert(request.agentId === identity.agentId, 'should include agentId');
|
|
88
|
+
assert(request.signature, 'should include signature');
|
|
89
|
+
assert(request.timestamp, 'should include timestamp');
|
|
90
|
+
assert(request.metadata.name === 'TestAgent', 'should include metadata');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('signChallenge produces valid response', () => {
|
|
94
|
+
const identity = generateIdentity();
|
|
95
|
+
const challenge = 'random-challenge-123';
|
|
96
|
+
|
|
97
|
+
const response = signChallenge(challenge, identity.agentId, identity.privateKey);
|
|
98
|
+
|
|
99
|
+
assert(response.agentId === identity.agentId, 'should include agentId');
|
|
100
|
+
assert(response.challenge === challenge, 'should include challenge');
|
|
101
|
+
assert(response.signature, 'should include signature');
|
|
102
|
+
|
|
103
|
+
// Verify the signature
|
|
104
|
+
const message = `${identity.agentId}:${challenge}`;
|
|
105
|
+
const valid = verifySignature(message, response.signature, identity.publicKey);
|
|
106
|
+
assert(valid, 'signature should verify');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('different agents have different IDs', () => {
|
|
110
|
+
const ids = new Set();
|
|
111
|
+
for (let i = 0; i < 100; i++) {
|
|
112
|
+
const identity = generateIdentity();
|
|
113
|
+
assert(!ids.has(identity.agentId), 'IDs should be unique');
|
|
114
|
+
ids.add(identity.agentId);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Summary
|
|
119
|
+
console.log(`\n${passed} passed, ${failed} failed`);
|
|
120
|
+
process.exit(failed > 0 ? 1 : 0);
|