provenance-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.
Files changed (2) hide show
  1. package/package.json +24 -0
  2. package/src/index.js +502 -0
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "provenance-cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI for the Provenance agent identity registry and AJP protocol",
5
+ "type": "module",
6
+ "bin": {
7
+ "provenance": "./src/index.js"
8
+ },
9
+ "scripts": {
10
+ "test": "node src/index.js --help"
11
+ },
12
+ "dependencies": {
13
+ "provenance-protocol": "^0.1.1"
14
+ },
15
+ "engines": {
16
+ "node": ">=18"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/ilucky21c/provenance-cli"
21
+ },
22
+ "keywords": ["provenance", "ai", "agents", "ajp", "cli"],
23
+ "license": "MIT"
24
+ }
package/src/index.js ADDED
@@ -0,0 +1,502 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * provenance-cli — Provenance Protocol + AJP command-line tool
4
+ *
5
+ * Usage:
6
+ * provenance keygen
7
+ * provenance register --id <id> --url <url> [options]
8
+ * provenance status <id>
9
+ * provenance validate [file]
10
+ * provenance revoke --id <id>
11
+ * provenance hire <id> --instruction <text> [--budget <usd>] [--timeout <s>]
12
+ * provenance jobs <job_id> --endpoint <url>
13
+ */
14
+
15
+ import { createPrivateKey, createPublicKey, generateKeyPairSync, sign as nodeSign, randomBytes } from 'crypto';
16
+ import { readFileSync, existsSync } from 'fs';
17
+ import { resolve } from 'path';
18
+
19
+ const API = process.env.PROVENANCE_API_URL || 'https://provenance-web-mu.vercel.app';
20
+ const VERSION = '0.1.0';
21
+
22
+ // ── Colours ───────────────────────────────────────────────────────────────────
23
+
24
+ const c = {
25
+ reset: '\x1b[0m',
26
+ dim: '\x1b[2m',
27
+ bold: '\x1b[1m',
28
+ green: '\x1b[32m',
29
+ amber: '\x1b[33m',
30
+ red: '\x1b[31m',
31
+ blue: '\x1b[34m',
32
+ white: '\x1b[97m',
33
+ };
34
+
35
+ const ok = s => `${c.green}✓${c.reset} ${s}`;
36
+ const err = s => `${c.red}✗${c.reset} ${s}`;
37
+ const dim = s => `${c.dim}${s}${c.reset}`;
38
+ const hi = s => `${c.white}${c.bold}${s}${c.reset}`;
39
+ const amb = s => `${c.amber}${s}${c.reset}`;
40
+
41
+ // ── Arg parsing ───────────────────────────────────────────────────────────────
42
+
43
+ function parseArgs(argv) {
44
+ const args = { _: [] };
45
+ let i = 0;
46
+ while (i < argv.length) {
47
+ const a = argv[i];
48
+ if (a.startsWith('--')) {
49
+ const key = a.slice(2);
50
+ const next = argv[i + 1];
51
+ if (next && !next.startsWith('--')) { args[key] = next; i += 2; }
52
+ else { args[key] = true; i++; }
53
+ } else {
54
+ args._.push(a);
55
+ i++;
56
+ }
57
+ }
58
+ return args;
59
+ }
60
+
61
+ // ── Crypto helpers ────────────────────────────────────────────────────────────
62
+
63
+ function generateKeyPair() {
64
+ const { publicKey, privateKey } = generateKeyPairSync('ed25519', {
65
+ publicKeyEncoding: { type: 'spki', format: 'der' },
66
+ privateKeyEncoding: { type: 'pkcs8', format: 'der' },
67
+ });
68
+ return {
69
+ publicKey: Buffer.from(publicKey).toString('base64'),
70
+ privateKey: Buffer.from(privateKey).toString('base64'),
71
+ };
72
+ }
73
+
74
+ function signMessage(privateKeyBase64, message) {
75
+ const keyBuffer = Buffer.from(privateKeyBase64, 'base64');
76
+ const key = createPrivateKey({ key: keyBuffer, format: 'der', type: 'pkcs8' });
77
+ return nodeSign(null, Buffer.from(message, 'utf8'), key).toString('base64');
78
+ }
79
+
80
+ function signForProvenance(privateKey, provenanceId, publicKey) {
81
+ return signMessage(privateKey, `${provenanceId}:${publicKey}`);
82
+ }
83
+
84
+ function signChallenge(privateKey, provenanceId, nonce) {
85
+ return signMessage(privateKey, `${provenanceId}:${nonce}`);
86
+ }
87
+
88
+ function generateJobId() {
89
+ return `job_${Date.now().toString(36)}${randomBytes(6).toString('hex')}`;
90
+ }
91
+
92
+ function signOffer(offer, privateKeyBase64) {
93
+ const { signature: _, ...rest } = offer;
94
+ const canonical = JSON.stringify(rest, Object.keys(rest).sort());
95
+ const keyBuffer = Buffer.from(privateKeyBase64, 'base64');
96
+ const key = createPrivateKey({ key: keyBuffer, format: 'der', type: 'pkcs8' });
97
+ const sig = nodeSign(null, Buffer.from(canonical, 'utf8'), key);
98
+ return `ed25519:${sig.toString('base64')}`;
99
+ }
100
+
101
+ // ── Commands ──────────────────────────────────────────────────────────────────
102
+
103
+ async function cmdKeygen() {
104
+ console.log(`\n${amb('Generating Ed25519 keypair...')}\n`);
105
+ const { publicKey, privateKey } = generateKeyPair();
106
+
107
+ console.log(`${hi('Public key')} ${dim('(add to PROVENANCE.yml identity.public_key)')}`);
108
+ console.log(`${c.green}${publicKey}${c.reset}\n`);
109
+
110
+ console.log(`${hi('Private key')} ${dim('(store as PROVENANCE_PRIVATE_KEY — never commit)')}`);
111
+ console.log(`${c.amber}${privateKey}${c.reset}\n`);
112
+
113
+ console.log(dim('─'.repeat(60)));
114
+ console.log(dim('Add to your environment:'));
115
+ console.log(` PROVENANCE_PRIVATE_KEY=${privateKey}\n`);
116
+ console.log(dim('Add to PROVENANCE.yml:'));
117
+ console.log(` identity:`);
118
+ console.log(` public_key: "${publicKey}"`);
119
+ console.log(` algorithm: ed25519`);
120
+ console.log(` signature: "<run: provenance register --id <your-id>>\n"`);
121
+ }
122
+
123
+ async function cmdRegister(args) {
124
+ const id = args.id || args['provenance-id'];
125
+ const url = args.url;
126
+ const name = args.name;
127
+ const description = args.description || args.desc;
128
+ const caps = args.capabilities ? args.capabilities.split(',').map(s => s.trim()) : [];
129
+ const cons = args.constraints ? args.constraints.split(',').map(s => s.trim()) : [];
130
+ const model = args.model;
131
+ const modelId = args['model-id'];
132
+ const ajpEndpoint = args['ajp-endpoint'];
133
+ const privateKey = args['private-key'] || process.env.PROVENANCE_PRIVATE_KEY;
134
+ const publicKey = args['public-key'] || process.env.PROVENANCE_PUBLIC_KEY;
135
+
136
+ if (!id) { console.error(err('--id required')); process.exit(1); }
137
+ if (!url) { console.error(err('--url required')); process.exit(1); }
138
+
139
+ console.log(`\n${amb('Registering')} ${hi(id)}...\n`);
140
+
141
+ let signedChallenge, pubKey;
142
+ if (privateKey) {
143
+ const kp = publicKey ? { publicKey } : (() => {
144
+ // Derive public key from private key
145
+ const privBuf = Buffer.from(privateKey, 'base64');
146
+ const privKey = createPrivateKey({ key: privBuf, format: 'der', type: 'pkcs8' });
147
+ const pubBuf = createPublicKey(privKey).export({ type: 'spki', format: 'der' });
148
+ return { publicKey: Buffer.from(pubBuf).toString('base64') };
149
+ })();
150
+ pubKey = kp.publicKey;
151
+ signedChallenge = signChallenge(privateKey, id, 'REGISTER');
152
+ console.log(ok('Signing with private key'));
153
+ }
154
+
155
+ const body = {
156
+ provenance_id: id,
157
+ url,
158
+ ...(name && { name }),
159
+ ...(description && { description }),
160
+ ...(caps.length && { capabilities: caps }),
161
+ ...(cons.length && { constraints: cons }),
162
+ ...(model && { model_provider: model }),
163
+ ...(modelId && { model_id: modelId }),
164
+ ...(ajpEndpoint && { ajp_endpoint: ajpEndpoint }),
165
+ ...(pubKey && { public_key: pubKey }),
166
+ ...(signedChallenge && { signed_challenge: signedChallenge }),
167
+ };
168
+
169
+ const res = await fetch(`${API}/api/agents/register`, {
170
+ method: 'POST',
171
+ headers: { 'Content-Type': 'application/json' },
172
+ body: JSON.stringify(body),
173
+ });
174
+ const data = await res.json();
175
+
176
+ if (!res.ok) {
177
+ console.error(err(data.error || `HTTP ${res.status}`));
178
+ process.exit(1);
179
+ }
180
+
181
+ const agent = data.agent || data;
182
+ console.log(ok(data.created ? 'Agent registered' : 'Agent updated'));
183
+ console.log(` ${dim('provenance_id:')} ${agent.provenance_id}`);
184
+ console.log(` ${dim('confidence:')} ${c.green}${agent.confidence}${c.reset}`);
185
+ console.log(` ${dim('identity_verified:')} ${agent.identity_verified ? c.green + 'true' + c.reset : c.amber + 'false' + c.reset}`);
186
+ if (ajpEndpoint) console.log(` ${dim('ajp_endpoint:')} ${ajpEndpoint}`);
187
+
188
+ if (!agent.identity_verified) {
189
+ console.log(`\n${c.amber}Tip:${c.reset} Run with ${hi('--private-key')} to get identity_verified status (confidence 1.0)`);
190
+ }
191
+ console.log();
192
+ }
193
+
194
+ async function cmdStatus(args) {
195
+ const id = args._[1];
196
+ if (!id) { console.error(err('Usage: provenance status <provenance_id>')); process.exit(1); }
197
+
198
+ console.log(`\n${amb('Checking')} ${hi(id)}...\n`);
199
+
200
+ const res = await fetch(`${API}/api/agent/${id.replace('provenance:', '').replace(':', '/')}`);
201
+ const data = await res.json();
202
+
203
+ if (!res.ok || data.error) {
204
+ console.error(err(data.error || 'Not found'));
205
+ process.exit(1);
206
+ }
207
+
208
+ const trust = data.confidence ? Math.round(data.confidence * 100) : 0;
209
+ const trustColor = trust >= 80 ? c.green : trust >= 50 ? c.amber : c.red;
210
+
211
+ console.log(`${hi(data.name || id)}`);
212
+ console.log(`${dim(data.provenance_id)}\n`);
213
+
214
+ console.log(`${dim('Trust score:')} ${trustColor}${trust}/100${c.reset}`);
215
+ console.log(`${dim('Declared:')} ${data.declared ? c.green + 'yes' + c.reset : c.amber + 'no' + c.reset}`);
216
+ console.log(`${dim('Identity verified:')} ${data.identity_verified ? c.green + 'yes' + c.reset : c.amber + 'no' + c.reset}`);
217
+ console.log(`${dim('AJP endpoint:')} ${data.ajp?.endpoint ? c.green + data.ajp.endpoint + c.reset : c.dim + 'not set' + c.reset}`);
218
+ console.log(`${dim('Incidents:')} ${(data.incident_count || 0) === 0 ? c.green + '0' + c.reset : c.red + data.incident_count + c.reset}`);
219
+
220
+ if (data.capabilities?.length) {
221
+ console.log(`${dim('Capabilities:')} ${data.capabilities.join(', ')}`);
222
+ }
223
+ if (data.constraints?.length) {
224
+ console.log(`${dim('Constraints:')} ${data.constraints.join(', ')}`);
225
+ }
226
+
227
+ // Checklist
228
+ const checklist = [
229
+ [data.declared, 'PROVENANCE.yml declared'],
230
+ [data.identity_verified, 'Identity verified (Ed25519)'],
231
+ [!!data.ajp?.endpoint, 'AJP endpoint configured'],
232
+ [(data.incident_count||0)===0, 'No open incidents'],
233
+ ];
234
+ console.log();
235
+ for (const [pass, label] of checklist) {
236
+ console.log(` ${pass ? ok(label) : `${c.dim}○${c.reset} ${c.dim}${label}${c.reset}`}`);
237
+ }
238
+ console.log();
239
+ }
240
+
241
+ async function cmdValidate(args) {
242
+ const file = args._[1] || 'PROVENANCE.yml';
243
+ const path = resolve(process.cwd(), file);
244
+
245
+ if (!existsSync(path)) {
246
+ console.error(err(`File not found: ${path}`));
247
+ process.exit(1);
248
+ }
249
+
250
+ console.log(`\n${amb('Validating')} ${hi(file)}...\n`);
251
+
252
+ const content = readFileSync(path, 'utf8');
253
+ const res = await fetch(`${API}/api/mcp`, {
254
+ method: 'POST',
255
+ headers: { 'Content-Type': 'application/json' },
256
+ body: JSON.stringify({
257
+ jsonrpc: '2.0', id: 1,
258
+ method: 'tools/call',
259
+ params: { name: 'validate_provenance_yml', arguments: { content } },
260
+ }),
261
+ });
262
+ const rpc = await res.json();
263
+ const data = JSON.parse(rpc.result?.content?.[0]?.text || '{}');
264
+
265
+ if (data.valid) {
266
+ console.log(ok('Valid PROVENANCE.yml'));
267
+ } else {
268
+ console.log(err('Validation failed'));
269
+ for (const e of data.errors || []) console.log(` ${c.red}✗${c.reset} ${e}`);
270
+ }
271
+ for (const w of data.warnings || []) {
272
+ console.log(` ${c.amber}⚠${c.reset} ${w}`);
273
+ }
274
+ console.log();
275
+ }
276
+
277
+ async function cmdRevoke(args) {
278
+ const id = args.id || args['provenance-id'];
279
+ const privateKey = args['private-key'] || process.env.PROVENANCE_PRIVATE_KEY;
280
+
281
+ if (!id) { console.error(err('--id required')); process.exit(1); }
282
+ if (!privateKey) { console.error(err('--private-key or PROVENANCE_PRIVATE_KEY required')); process.exit(1); }
283
+
284
+ console.log(`\n${c.red}Revoking identity for${c.reset} ${hi(id)}...\n`);
285
+
286
+ const signedChallenge = signChallenge(privateKey, id, 'REVOKE');
287
+
288
+ const res = await fetch(`${API}/api/agents/revoke`, {
289
+ method: 'POST',
290
+ headers: { 'Content-Type': 'application/json' },
291
+ body: JSON.stringify({ provenance_id: id, signed_challenge: signedChallenge }),
292
+ });
293
+ const data = await res.json();
294
+
295
+ if (!res.ok || !data.success) {
296
+ console.error(err(data.error || `HTTP ${res.status}`));
297
+ process.exit(1);
298
+ }
299
+
300
+ console.log(ok('Identity revoked'));
301
+ console.log(dim('Run `provenance register` with a new keypair to re-establish.'));
302
+ console.log();
303
+ }
304
+
305
+ async function cmdHire(args) {
306
+ const targetId = args._[1];
307
+ const instruction = args.instruction || args.i;
308
+ const budget = parseFloat(args.budget || args.b || '1.0');
309
+ const timeout = parseInt(args.timeout || args.t || '120');
310
+ const privateKey = args['private-key'] || process.env.PROVENANCE_PRIVATE_KEY;
311
+ const provenanceId = args['from-id'] || process.env.PROVENANCE_ID;
312
+
313
+ if (!targetId) { console.error(err('Usage: provenance hire <provenance_id> --instruction <text>')); process.exit(1); }
314
+ if (!instruction) { console.error(err('--instruction required')); process.exit(1); }
315
+ if (!privateKey) { console.error(err('--private-key or PROVENANCE_PRIVATE_KEY required')); process.exit(1); }
316
+ if (!provenanceId){ console.error(err('--from-id or PROVENANCE_ID required')); process.exit(1); }
317
+
318
+ console.log(`\n${amb('Hiring')} ${hi(targetId)}...\n`);
319
+
320
+ // Resolve endpoint from index
321
+ process.stdout.write(dim(' Resolving endpoint...'));
322
+ const agentRes = await fetch(`${API}/api/agent/${targetId.replace('provenance:', '').replace(':', '/')}`);
323
+ const agentData = await agentRes.json();
324
+ if (!agentData?.ajp?.endpoint) {
325
+ console.log('\n' + err('Agent has no AJP endpoint declared'));
326
+ process.exit(1);
327
+ }
328
+ const endpoint = agentData.ajp.endpoint.replace(/\/$/, '');
329
+ console.log(` ${c.green}${endpoint}${c.reset}`);
330
+
331
+ // Build offer
332
+ const now = new Date();
333
+ const expiresAt = new Date(now.getTime() + timeout * 1000);
334
+ const jobId = generateJobId();
335
+
336
+ const offer = {
337
+ ajp: '0.1',
338
+ job_id: jobId,
339
+ parent_job_id: null,
340
+ from: { type: 'orchestrator', id: null, provenance_id: provenanceId },
341
+ to: { provenance_id: targetId },
342
+ task: { type: 'task', instruction, input: {}, output_format: 'json' },
343
+ context: { credentials: {}, memory: [], constraints: [] },
344
+ budget: { max_usd: budget, max_seconds: timeout, max_llm_tokens: 10000 },
345
+ callback: null,
346
+ issued_at: now.toISOString(),
347
+ expires_at: expiresAt.toISOString(),
348
+ signature: '',
349
+ };
350
+ offer.signature = signOffer(offer, privateKey);
351
+
352
+ // Submit
353
+ process.stdout.write(dim(' Submitting job...'));
354
+ const submitRes = await fetch(`${endpoint}/jobs`, {
355
+ method: 'POST',
356
+ headers: { 'Content-Type': 'application/json' },
357
+ body: JSON.stringify(offer),
358
+ });
359
+ const submitData = await submitRes.json().catch(() => ({}));
360
+
361
+ if (!submitRes.ok) {
362
+ console.log('\n' + err(submitData.error || submitData.reason || `HTTP ${submitRes.status}`));
363
+ process.exit(1);
364
+ }
365
+ console.log(` ${c.green}${jobId}${c.reset}`);
366
+
367
+ // Poll
368
+ const deadline = Date.now() + timeout * 1000;
369
+ const frames = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
370
+ let fi = 0;
371
+
372
+ while (Date.now() < deadline) {
373
+ await new Promise(r => setTimeout(r, 2000));
374
+ const pollRes = await fetch(`${endpoint}/jobs/${jobId}`);
375
+ const pollData = await pollRes.json().catch(() => ({}));
376
+
377
+ process.stdout.write(`\r ${c.blue}${frames[fi++ % frames.length]}${c.reset} ${c.dim}${pollData.status || 'polling'}...${c.reset} `);
378
+
379
+ if (pollData.status === 'completed') {
380
+ // Ack
381
+ await fetch(`${endpoint}/jobs/${jobId}/ack`, {
382
+ method: 'POST',
383
+ headers: { 'Content-Type': 'application/json' },
384
+ body: JSON.stringify({ received: true }),
385
+ }).catch(() => {});
386
+
387
+ const dur = pollData.usage?.duration_seconds?.toFixed(1);
388
+ process.stdout.write(`\r${ok(`Completed${dur ? ` in ${dur}s` : ''}`)} \n\n`);
389
+
390
+ const out = pollData.output;
391
+ if (typeof out === 'string') {
392
+ console.log(out);
393
+ } else {
394
+ console.log(JSON.stringify(out, null, 2));
395
+ }
396
+
397
+ if (pollData.usage) {
398
+ const u = pollData.usage;
399
+ const parts = [];
400
+ if (u.duration_seconds != null) parts.push(`${u.duration_seconds.toFixed(1)}s`);
401
+ if (u.cost_usd > 0) parts.push(`$${u.cost_usd.toFixed(4)}`);
402
+ if (u.llm_tokens > 0) parts.push(`${u.llm_tokens} tokens`);
403
+ if (parts.length) console.log('\n' + dim(parts.join(' · ')));
404
+ }
405
+ console.log();
406
+ return;
407
+ }
408
+
409
+ if (['failed','expired','rejected'].includes(pollData.status)) {
410
+ process.stdout.write(`\r${err(pollData.status + (pollData.message ? ': ' + pollData.message : ''))} \n\n`);
411
+ process.exit(1);
412
+ }
413
+ }
414
+
415
+ console.log('\n' + err(`Timed out after ${timeout}s`));
416
+ process.exit(1);
417
+ }
418
+
419
+ async function cmdJobs(args) {
420
+ const jobId = args._[1];
421
+ const endpoint = args.endpoint;
422
+
423
+ if (!jobId) { console.error(err('Usage: provenance jobs <job_id> --endpoint <url>')); process.exit(1); }
424
+ if (!endpoint) { console.error(err('--endpoint required')); process.exit(1); }
425
+
426
+ const res = await fetch(`${endpoint.replace(/\/$/, '')}/jobs/${jobId}`);
427
+ const data = await res.json();
428
+
429
+ const statusColor = { completed: c.green, failed: c.red, expired: c.red, running: c.blue, accepted: c.amber }[data.status] || c.dim;
430
+ console.log(`\n${dim('job_id:')} ${data.job_id}`);
431
+ console.log(`${dim('status:')} ${statusColor}${data.status}${c.reset}`);
432
+ if (data.output) console.log(`\n${JSON.stringify(data.output, null, 2)}`);
433
+ if (data.message) console.log(`${c.red}${data.message}${c.reset}`);
434
+ console.log();
435
+ }
436
+
437
+ function cmdHelp() {
438
+ console.log(`
439
+ ${hi('provenance')} ${dim(`v${VERSION}`)} — Provenance Protocol + AJP CLI
440
+
441
+ ${amb('Identity commands:')}
442
+ ${hi('keygen')} Generate an Ed25519 keypair
443
+ ${hi('register')} --id <id> --url <url> Register or update your agent
444
+ [--name <name>]
445
+ [--description <text>]
446
+ [--capabilities read:web,write:code]
447
+ [--constraints no:pii,no:financial:transact]
448
+ [--model anthropic] [--model-id claude-sonnet-4-6]
449
+ [--ajp-endpoint <url>]
450
+ [--private-key <key>]
451
+ ${hi('status')} <provenance_id> Check trust score and checklist
452
+ ${hi('validate')} [file] Validate PROVENANCE.yml (default: ./PROVENANCE.yml)
453
+ ${hi('revoke')} --id <id> Revoke cryptographic identity
454
+ [--private-key <key>]
455
+
456
+ ${amb('AJP commands:')}
457
+ ${hi('hire')} <provenance_id> Send a job to an agent
458
+ --instruction <text>
459
+ [--budget <usd>] default: 1.00
460
+ [--timeout <seconds>] default: 120
461
+ [--from-id <id>] default: \$PROVENANCE_ID
462
+ [--private-key <key>] default: \$PROVENANCE_PRIVATE_KEY
463
+ ${hi('jobs')} <job_id> Check job status
464
+ --endpoint <url>
465
+
466
+ ${amb('Environment variables:')}
467
+ PROVENANCE_ID Your agent's Provenance ID
468
+ PROVENANCE_PRIVATE_KEY Your Ed25519 private key (base64 PKCS8 DER)
469
+ PROVENANCE_API_URL Override API base URL (default: https://provenance-web-mu.vercel.app)
470
+
471
+ ${amb('Examples:')}
472
+ npx provenance keygen
473
+ npx provenance register --id provenance:github:alice/my-agent --url https://github.com/alice/my-agent
474
+ npx provenance status provenance:github:alice/my-agent
475
+ npx provenance hire provenance:github:alice/my-agent --instruction "Summarize this doc" --budget 0.50
476
+ `);
477
+ }
478
+
479
+ // ── Main ──────────────────────────────────────────────────────────────────────
480
+
481
+ const argv = process.argv.slice(2);
482
+ const args = parseArgs(argv);
483
+ const cmd = args._[0];
484
+
485
+ try {
486
+ if (!cmd || cmd === 'help' || args.help || args.h) { cmdHelp(); }
487
+ else if (cmd === 'keygen') { await cmdKeygen(); }
488
+ else if (cmd === 'register') { await cmdRegister(args); }
489
+ else if (cmd === 'status') { await cmdStatus(args); }
490
+ else if (cmd === 'validate') { await cmdValidate(args); }
491
+ else if (cmd === 'revoke') { await cmdRevoke(args); }
492
+ else if (cmd === 'hire') { await cmdHire(args); }
493
+ else if (cmd === 'jobs') { await cmdJobs(args); }
494
+ else {
495
+ console.error(err(`Unknown command: ${cmd}`));
496
+ console.error(dim('Run `provenance help` for usage.'));
497
+ process.exit(1);
498
+ }
499
+ } catch (e) {
500
+ console.error(err(e.message));
501
+ process.exit(1);
502
+ }