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/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
@@ -0,0 +1,2 @@
1
+ AgentID (lite) server running on port 3847
2
+ Data directory: ./data
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);