provenance-cli 0.1.0 → 0.1.1
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/index.js +272 -0
- package/package.json +7 -14
- package/src/index.js +0 -502
package/index.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* provenance-cli — Provenance Protocol identity CLI
|
|
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> [--private-key <key>]
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createPrivateKey, createPublicKey, generateKeyPairSync, sign as nodeSign } from 'crypto';
|
|
14
|
+
import { readFileSync, existsSync } from 'fs';
|
|
15
|
+
import { resolve } from 'path';
|
|
16
|
+
|
|
17
|
+
const API = process.env.PROVENANCE_API_URL || 'https://provenance-web-mu.vercel.app';
|
|
18
|
+
const VERSION = '0.1.1';
|
|
19
|
+
|
|
20
|
+
// ── Colours ───────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
const c = {
|
|
23
|
+
reset: '\x1b[0m', dim: '\x1b[2m', bold: '\x1b[1m',
|
|
24
|
+
green: '\x1b[32m', amber: '\x1b[33m', red: '\x1b[31m', white: '\x1b[97m',
|
|
25
|
+
};
|
|
26
|
+
const ok = s => `${c.green}✓${c.reset} ${s}`;
|
|
27
|
+
const err = s => `${c.red}✗${c.reset} ${s}`;
|
|
28
|
+
const dim = s => `${c.dim}${s}${c.reset}`;
|
|
29
|
+
const hi = s => `${c.white}${c.bold}${s}${c.reset}`;
|
|
30
|
+
const amb = s => `${c.amber}${s}${c.reset}`;
|
|
31
|
+
|
|
32
|
+
// ── Arg parsing ───────────────────────────────────────────────────────────────
|
|
33
|
+
|
|
34
|
+
function parseArgs(argv) {
|
|
35
|
+
const args = { _: [] };
|
|
36
|
+
let i = 0;
|
|
37
|
+
while (i < argv.length) {
|
|
38
|
+
const a = argv[i];
|
|
39
|
+
if (a.startsWith('--')) {
|
|
40
|
+
const key = a.slice(2);
|
|
41
|
+
const next = argv[i + 1];
|
|
42
|
+
if (next && !next.startsWith('--')) { args[key] = next; i += 2; }
|
|
43
|
+
else { args[key] = true; i++; }
|
|
44
|
+
} else { args._.push(a); i++; }
|
|
45
|
+
}
|
|
46
|
+
return args;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Crypto helpers ────────────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
function generateKeyPair() {
|
|
52
|
+
const { publicKey, privateKey } = generateKeyPairSync('ed25519', {
|
|
53
|
+
publicKeyEncoding: { type: 'spki', format: 'der' },
|
|
54
|
+
privateKeyEncoding: { type: 'pkcs8', format: 'der' },
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
publicKey: Buffer.from(publicKey).toString('base64'),
|
|
58
|
+
privateKey: Buffer.from(privateKey).toString('base64'),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function signMessage(privateKeyBase64, message) {
|
|
63
|
+
const key = createPrivateKey({ key: Buffer.from(privateKeyBase64, 'base64'), format: 'der', type: 'pkcs8' });
|
|
64
|
+
return nodeSign(null, Buffer.from(message, 'utf8'), key).toString('base64');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function derivePublicKey(privateKeyBase64) {
|
|
68
|
+
const priv = createPrivateKey({ key: Buffer.from(privateKeyBase64, 'base64'), format: 'der', type: 'pkcs8' });
|
|
69
|
+
return Buffer.from(createPublicKey(priv).export({ type: 'spki', format: 'der' })).toString('base64');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── Commands ──────────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
async function cmdKeygen() {
|
|
75
|
+
console.log(`\n${amb('Generating Ed25519 keypair...')}\n`);
|
|
76
|
+
const { publicKey, privateKey } = generateKeyPair();
|
|
77
|
+
|
|
78
|
+
console.log(`${hi('Public key')} ${dim('(add to PROVENANCE.yml identity.public_key)')}`);
|
|
79
|
+
console.log(`${c.green}${publicKey}${c.reset}\n`);
|
|
80
|
+
console.log(`${hi('Private key')} ${dim('(store as PROVENANCE_PRIVATE_KEY — never commit)')}`);
|
|
81
|
+
console.log(`${c.amber}${privateKey}${c.reset}\n`);
|
|
82
|
+
console.log(dim('─'.repeat(60)));
|
|
83
|
+
console.log(dim('Add to your environment:'));
|
|
84
|
+
console.log(` PROVENANCE_PRIVATE_KEY=${privateKey}\n`);
|
|
85
|
+
console.log(dim('Add to PROVENANCE.yml:'));
|
|
86
|
+
console.log(` identity:`);
|
|
87
|
+
console.log(` public_key: "${publicKey}"`);
|
|
88
|
+
console.log(` algorithm: ed25519\n`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function cmdRegister(args) {
|
|
92
|
+
const id = args.id;
|
|
93
|
+
const url = args.url;
|
|
94
|
+
const name = args.name;
|
|
95
|
+
const description = args.description || args.desc;
|
|
96
|
+
const caps = args.capabilities ? args.capabilities.split(',').map(s => s.trim()) : [];
|
|
97
|
+
const cons = args.constraints ? args.constraints.split(',').map(s => s.trim()) : [];
|
|
98
|
+
const model = args.model;
|
|
99
|
+
const modelId = args['model-id'];
|
|
100
|
+
const ajpEndpoint = args['ajp-endpoint'];
|
|
101
|
+
const privateKey = args['private-key'] || process.env.PROVENANCE_PRIVATE_KEY;
|
|
102
|
+
|
|
103
|
+
if (!id) { console.error(err('--id required')); process.exit(1); }
|
|
104
|
+
if (!url) { console.error(err('--url required')); process.exit(1); }
|
|
105
|
+
|
|
106
|
+
console.log(`\n${amb('Registering')} ${hi(id)}...\n`);
|
|
107
|
+
|
|
108
|
+
let pubKey, signedChallenge;
|
|
109
|
+
if (privateKey) {
|
|
110
|
+
pubKey = args['public-key'] || process.env.PROVENANCE_PUBLIC_KEY || derivePublicKey(privateKey);
|
|
111
|
+
signedChallenge = signMessage(privateKey, `${id}:REGISTER`);
|
|
112
|
+
console.log(ok('Signing with private key'));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const body = {
|
|
116
|
+
provenance_id: id, url,
|
|
117
|
+
...(name && { name }),
|
|
118
|
+
...(description && { description }),
|
|
119
|
+
...(caps.length && { capabilities: caps }),
|
|
120
|
+
...(cons.length && { constraints: cons }),
|
|
121
|
+
...(model && { model_provider: model }),
|
|
122
|
+
...(modelId && { model_id: modelId }),
|
|
123
|
+
...(ajpEndpoint && { ajp_endpoint: ajpEndpoint }),
|
|
124
|
+
...(pubKey && { public_key: pubKey }),
|
|
125
|
+
...(signedChallenge && { signed_challenge: signedChallenge }),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const res = await fetch(`${API}/api/agents/register`, {
|
|
129
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body),
|
|
130
|
+
});
|
|
131
|
+
const data = await res.json();
|
|
132
|
+
|
|
133
|
+
if (!res.ok) { console.error(err(data.error || `HTTP ${res.status}`)); process.exit(1); }
|
|
134
|
+
|
|
135
|
+
const agent = data.agent || data;
|
|
136
|
+
console.log(ok(data.created ? 'Agent registered' : 'Agent updated'));
|
|
137
|
+
console.log(` ${dim('confidence:')} ${c.green}${agent.confidence}${c.reset}`);
|
|
138
|
+
console.log(` ${dim('identity_verified:')} ${agent.identity_verified ? c.green + 'true' : c.amber + 'false'}${c.reset}`);
|
|
139
|
+
if (ajpEndpoint) console.log(` ${dim('ajp_endpoint:')} ${ajpEndpoint}`);
|
|
140
|
+
if (!agent.identity_verified)
|
|
141
|
+
console.log(`\n${c.amber}Tip:${c.reset} Run with ${hi('--private-key')} to get identity_verified status`);
|
|
142
|
+
console.log();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function cmdStatus(args) {
|
|
146
|
+
const id = args._[1];
|
|
147
|
+
if (!id) { console.error(err('Usage: provenance status <provenance_id>')); process.exit(1); }
|
|
148
|
+
|
|
149
|
+
console.log(`\n${amb('Checking')} ${hi(id)}...\n`);
|
|
150
|
+
|
|
151
|
+
const res = await fetch(`${API}/api/agent/${id.replace('provenance:', '').replace(':', '/')}`);
|
|
152
|
+
const data = await res.json();
|
|
153
|
+
|
|
154
|
+
if (!res.ok || data.error) { console.error(err(data.error || 'Not found')); process.exit(1); }
|
|
155
|
+
|
|
156
|
+
const trust = Math.round((data.confidence || 0) * 100);
|
|
157
|
+
const trustColor = trust >= 80 ? c.green : trust >= 50 ? c.amber : c.red;
|
|
158
|
+
|
|
159
|
+
console.log(`${hi(data.name || id)}`);
|
|
160
|
+
console.log(`${dim(data.provenance_id)}\n`);
|
|
161
|
+
console.log(`${dim('Trust score:')} ${trustColor}${trust}/100${c.reset}`);
|
|
162
|
+
console.log(`${dim('Declared:')} ${data.declared ? c.green + 'yes' : c.amber + 'no'}${c.reset}`);
|
|
163
|
+
console.log(`${dim('Identity verified:')} ${data.identity_verified ? c.green + 'yes' : c.amber + 'no'}${c.reset}`);
|
|
164
|
+
console.log(`${dim('AJP endpoint:')} ${data.ajp?.endpoint ? c.green + data.ajp.endpoint : c.dim + 'not set'}${c.reset}`);
|
|
165
|
+
console.log(`${dim('Incidents:')} ${(data.incident_count || 0) === 0 ? c.green + '0' : c.red + data.incident_count}${c.reset}`);
|
|
166
|
+
if (data.capabilities?.length) console.log(`${dim('Capabilities:')} ${data.capabilities.join(', ')}`);
|
|
167
|
+
if (data.constraints?.length) console.log(`${dim('Constraints:')} ${data.constraints.join(', ')}`);
|
|
168
|
+
|
|
169
|
+
console.log();
|
|
170
|
+
for (const [pass, label] of [
|
|
171
|
+
[data.declared, 'PROVENANCE.yml declared'],
|
|
172
|
+
[data.identity_verified, 'Identity verified (Ed25519)'],
|
|
173
|
+
[!!data.ajp?.endpoint, 'AJP endpoint configured'],
|
|
174
|
+
[(data.incident_count||0)===0, 'No open incidents'],
|
|
175
|
+
]) console.log(` ${pass ? ok(label) : dim('○ ' + label)}`);
|
|
176
|
+
console.log();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function cmdValidate(args) {
|
|
180
|
+
const file = args._[1] || 'PROVENANCE.yml';
|
|
181
|
+
const path = resolve(process.cwd(), file);
|
|
182
|
+
if (!existsSync(path)) { console.error(err(`File not found: ${path}`)); process.exit(1); }
|
|
183
|
+
|
|
184
|
+
console.log(`\n${amb('Validating')} ${hi(file)}...\n`);
|
|
185
|
+
const content = readFileSync(path, 'utf8');
|
|
186
|
+
const res = await fetch(`${API}/api/mcp`, {
|
|
187
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
188
|
+
body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call',
|
|
189
|
+
params: { name: 'validate_provenance_yml', arguments: { content } } }),
|
|
190
|
+
});
|
|
191
|
+
const rpc = await res.json();
|
|
192
|
+
const data = JSON.parse(rpc.result?.content?.[0]?.text || '{}');
|
|
193
|
+
|
|
194
|
+
if (data.valid) console.log(ok('Valid PROVENANCE.yml'));
|
|
195
|
+
else { console.log(err('Validation failed')); for (const e of data.errors || []) console.log(` ${c.red}✗${c.reset} ${e}`); }
|
|
196
|
+
for (const w of data.warnings || []) console.log(` ${c.amber}⚠${c.reset} ${w}`);
|
|
197
|
+
console.log();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function cmdRevoke(args) {
|
|
201
|
+
const id = args.id;
|
|
202
|
+
const privateKey = args['private-key'] || process.env.PROVENANCE_PRIVATE_KEY;
|
|
203
|
+
if (!id) { console.error(err('--id required')); process.exit(1); }
|
|
204
|
+
if (!privateKey) { console.error(err('--private-key or PROVENANCE_PRIVATE_KEY required')); process.exit(1); }
|
|
205
|
+
|
|
206
|
+
console.log(`\n${c.red}Revoking identity for${c.reset} ${hi(id)}...\n`);
|
|
207
|
+
|
|
208
|
+
const res = await fetch(`${API}/api/agents/revoke`, {
|
|
209
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
210
|
+
body: JSON.stringify({ provenance_id: id, signed_challenge: signMessage(privateKey, `${id}:REVOKE`) }),
|
|
211
|
+
});
|
|
212
|
+
const data = await res.json();
|
|
213
|
+
if (!res.ok || !data.success) { console.error(err(data.error || `HTTP ${res.status}`)); process.exit(1); }
|
|
214
|
+
|
|
215
|
+
console.log(ok('Identity revoked'));
|
|
216
|
+
console.log(dim('Run `provenance register` with a new keypair to re-establish.\n'));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function cmdHelp() {
|
|
220
|
+
console.log(`
|
|
221
|
+
${hi('provenance')} ${dim(`v${VERSION}`)} — Provenance Protocol identity CLI
|
|
222
|
+
|
|
223
|
+
${amb('Commands:')}
|
|
224
|
+
${hi('keygen')} Generate an Ed25519 keypair
|
|
225
|
+
${hi('register')} --id <id> --url <url> Register or update your agent
|
|
226
|
+
[--name <name>]
|
|
227
|
+
[--description <text>]
|
|
228
|
+
[--capabilities read:web,write:code]
|
|
229
|
+
[--constraints no:pii,no:financial:transact]
|
|
230
|
+
[--model anthropic] [--model-id claude-sonnet-4-6]
|
|
231
|
+
[--ajp-endpoint <url>]
|
|
232
|
+
[--private-key <key>]
|
|
233
|
+
${hi('status')} <provenance_id> Check trust score and checklist
|
|
234
|
+
${hi('validate')} [file] Validate PROVENANCE.yml (default: ./PROVENANCE.yml)
|
|
235
|
+
${hi('revoke')} --id <id> Revoke cryptographic identity
|
|
236
|
+
[--private-key <key>]
|
|
237
|
+
|
|
238
|
+
${amb('Environment variables:')}
|
|
239
|
+
PROVENANCE_ID Your agent's Provenance ID
|
|
240
|
+
PROVENANCE_PRIVATE_KEY Your Ed25519 private key (base64 PKCS8 DER)
|
|
241
|
+
PROVENANCE_API_URL Override API base (default: https://provenance-web-mu.vercel.app)
|
|
242
|
+
|
|
243
|
+
${amb('For AJP job delegation:')}
|
|
244
|
+
${dim('npm install -g ajp-cli')}
|
|
245
|
+
${dim('npx ajp hire <id> --instruction "..."')}
|
|
246
|
+
|
|
247
|
+
${amb('Examples:')}
|
|
248
|
+
npx provenance keygen
|
|
249
|
+
npx provenance register --id provenance:github:alice/my-agent --url https://github.com/alice/my-agent
|
|
250
|
+
npx provenance status provenance:github:alice/my-agent
|
|
251
|
+
npx provenance validate
|
|
252
|
+
`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
const argv = process.argv.slice(2);
|
|
258
|
+
const args = parseArgs(argv);
|
|
259
|
+
const cmd = args._[0];
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
if (!cmd || cmd === 'help' || args.help) cmdHelp();
|
|
263
|
+
else if (cmd === 'keygen') await cmdKeygen();
|
|
264
|
+
else if (cmd === 'register') await cmdRegister(args);
|
|
265
|
+
else if (cmd === 'status') await cmdStatus(args);
|
|
266
|
+
else if (cmd === 'validate') await cmdValidate(args);
|
|
267
|
+
else if (cmd === 'revoke') await cmdRevoke(args);
|
|
268
|
+
else { console.error(err(`Unknown command: ${cmd}\nRun \`provenance help\` for usage.`)); process.exit(1); }
|
|
269
|
+
} catch (e) {
|
|
270
|
+
console.error(err(e.message));
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
package/package.json
CHANGED
|
@@ -1,24 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "provenance-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "CLI for the Provenance agent identity
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "CLI for the Provenance Protocol — agent identity, registration, and trust",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"provenance": "./
|
|
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"
|
|
7
|
+
"provenance": "./index.js"
|
|
17
8
|
},
|
|
9
|
+
"engines": { "node": ">=18" },
|
|
18
10
|
"repository": {
|
|
19
11
|
"type": "git",
|
|
20
|
-
"url": "https://github.com/ilucky21c/provenance-
|
|
12
|
+
"url": "git+https://github.com/ilucky21c/provenance-protocol.git",
|
|
13
|
+
"directory": "cli"
|
|
21
14
|
},
|
|
22
|
-
"keywords": ["provenance", "ai", "agents", "
|
|
15
|
+
"keywords": ["provenance", "ai", "agents", "identity", "cli"],
|
|
23
16
|
"license": "MIT"
|
|
24
17
|
}
|
package/src/index.js
DELETED
|
@@ -1,502 +0,0 @@
|
|
|
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
|
-
}
|