ajp-protocol 0.1.0 → 0.2.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/.github/workflows/publish.yml +31 -4
- package/README.md +72 -22
- package/cli/index.js +231 -0
- package/cli/package.json +17 -0
- package/package.json +15 -5
- package/schema/job-offer.json +3 -3
- package/spec/SPEC.md +102 -23
- package/src/client.js +36 -16
- package/src/index.js +7 -0
- package/src/server.js +109 -29
- package/src/trust.js +205 -0
- package/src/utils.js +62 -10
- package/test/trust.test.mjs +88 -0
|
@@ -8,13 +8,40 @@ jobs:
|
|
|
8
8
|
publish:
|
|
9
9
|
runs-on: ubuntu-latest
|
|
10
10
|
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
# Mints the short-lived OIDC token npm trades for publish rights.
|
|
13
|
+
# Without it npm looks for a token instead and fails with a 404 on
|
|
14
|
+
# write, which reads like "package not found" rather than "not
|
|
15
|
+
# authorised". Do not remove.
|
|
11
16
|
id-token: write
|
|
12
17
|
steps:
|
|
13
18
|
- uses: actions/checkout@v4
|
|
19
|
+
|
|
14
20
|
- uses: actions/setup-node@v4
|
|
15
21
|
with:
|
|
16
|
-
|
|
22
|
+
# Trusted publishing needs Node >= 22.14 and npm >= 11.5.1.
|
|
23
|
+
node-version: '24'
|
|
17
24
|
registry-url: 'https://registry.npmjs.org'
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
25
|
+
|
|
26
|
+
# Upgrade npm explicitly rather than trusting whatever version the
|
|
27
|
+
# runner's Node bundles, and print both. A too-old npm ignores OIDC
|
|
28
|
+
# and fails with that same misleading 404, so keep the versions in
|
|
29
|
+
# the log where they can be read.
|
|
30
|
+
- run: npm install -g npm@latest
|
|
31
|
+
- run: node -v && npm -v
|
|
32
|
+
|
|
33
|
+
# Install before testing. Not every package here is dependency-free —
|
|
34
|
+
# ajp-protocol depends on provenance-protocol and its tests import it, and
|
|
35
|
+
# omitting this step made the test fail to resolve the module rather than
|
|
36
|
+
# fail a real assertion. `npm install` rather than `npm ci` because these
|
|
37
|
+
# packages carry no lockfile.
|
|
38
|
+
- run: npm install --no-audit --no-fund
|
|
39
|
+
|
|
40
|
+
# A release that fails its own tests must not reach the registry.
|
|
41
|
+
- run: npm test --if-present
|
|
42
|
+
|
|
43
|
+
# No NODE_AUTH_TOKEN: npm exchanges this workflow's OIDC identity for
|
|
44
|
+
# publish rights, configured as a trusted publisher on npmjs.com against
|
|
45
|
+
# this repository and this workflow filename. Provenance attestations are
|
|
46
|
+
# generated automatically, so --provenance is not passed.
|
|
47
|
+
- run: npm publish --access public
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ajp-protocol
|
|
2
2
|
|
|
3
3
|
The Agent Job Protocol — standard interaction layer for the agent internet.
|
|
4
|
-
Part of the [Provenance Protocol](https://
|
|
4
|
+
Part of the [Provenance Protocol](https://getprovenance.dev) family.
|
|
5
5
|
|
|
6
6
|
```bash
|
|
7
7
|
npm install ajp-protocol
|
|
@@ -25,7 +25,8 @@ Add three routes to your agent. AJP handles verification, trust checks,
|
|
|
25
25
|
and job lifecycle automatically.
|
|
26
26
|
|
|
27
27
|
```js
|
|
28
|
-
import { AJPServer } from 'ajp-protocol';
|
|
28
|
+
import { AJPServer, declarationKeyResolver, indexStandingCheck } from 'ajp-protocol';
|
|
29
|
+
import { Provenance } from 'provenance-protocol';
|
|
29
30
|
import express from 'express';
|
|
30
31
|
|
|
31
32
|
const app = express();
|
|
@@ -33,14 +34,29 @@ app.use(express.json());
|
|
|
33
34
|
|
|
34
35
|
const server = new AJPServer({
|
|
35
36
|
provenanceId: 'provenance:github:alice/research-assistant',
|
|
37
|
+
privateKey: process.env.PROVENANCE_PRIVATE_KEY, // signs results with Ed25519
|
|
38
|
+
|
|
39
|
+
// Constraints from your PROVENANCE.yml — asserted in every signed JobResult.
|
|
40
|
+
// Creates a cryptographic receipt: "this agent declared it honored these constraints for this job."
|
|
41
|
+
constraints: ['no:pii', 'no:persist:data'],
|
|
42
|
+
|
|
43
|
+
// Optional: accept human callers (platforms) with a shared HMAC secret
|
|
36
44
|
secret: process.env.AJP_SECRET,
|
|
37
45
|
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
// Identity: resolved from the sender's own signed declaration, offline.
|
|
47
|
+
// This is the default — no index is consulted to check a signature.
|
|
48
|
+
resolveSenderKey: declarationKeyResolver(),
|
|
49
|
+
|
|
50
|
+
// Standing (revocation, incidents, freshness) cannot be checked offline, so
|
|
51
|
+
// it is opt-in and you choose whom to ask. Omit it entirely to accept any
|
|
52
|
+
// sender whose identity verifies.
|
|
53
|
+
checkStanding: indexStandingCheck(new Provenance(), {
|
|
54
|
+
requireDeclared: true,
|
|
55
|
+
requireCapabilities: ['delegate:agents'],
|
|
56
|
+
requireClean: true,
|
|
57
|
+
requireMinAge: 7,
|
|
58
|
+
}),
|
|
59
|
+
onStandingUnavailable: 'deny', // an unreachable attester is not an accusation
|
|
44
60
|
|
|
45
61
|
// Your agent logic — receives the job, returns the result
|
|
46
62
|
onJob: async (job) => {
|
|
@@ -68,7 +84,8 @@ const client = new AJPClient({
|
|
|
68
84
|
type: 'agent', // 'human' | 'agent' | 'orchestrator'
|
|
69
85
|
provenance_id: 'provenance:github:alice/orchestrator',
|
|
70
86
|
},
|
|
71
|
-
secret
|
|
87
|
+
// Agent/orchestrator callers sign with Ed25519 — no shared secret needed
|
|
88
|
+
privateKey: process.env.PROVENANCE_PRIVATE_KEY,
|
|
72
89
|
});
|
|
73
90
|
|
|
74
91
|
const result = await client.send(
|
|
@@ -94,6 +111,7 @@ console.log(result.output);
|
|
|
94
111
|
|
|
95
112
|
### Human hiring an agent
|
|
96
113
|
```js
|
|
114
|
+
// Human callers use a shared HMAC secret (agreed out of band with the agent)
|
|
97
115
|
const client = new AJPClient({
|
|
98
116
|
from: { type: 'human', id: 'user_alice_123' },
|
|
99
117
|
secret: process.env.AJP_SECRET,
|
|
@@ -103,11 +121,12 @@ const result = await client.send(agentId, task, budget);
|
|
|
103
121
|
|
|
104
122
|
### Agent hiring an agent
|
|
105
123
|
```js
|
|
124
|
+
// Agent callers sign with Ed25519 — no shared secret, no prior setup
|
|
125
|
+
// The receiving agent verifies by fetching your public key from Provenance index
|
|
106
126
|
const client = new AJPClient({
|
|
107
127
|
from: { type: 'agent', provenance_id: 'provenance:github:alice/pipeline' },
|
|
108
|
-
|
|
128
|
+
privateKey: process.env.PROVENANCE_PRIVATE_KEY,
|
|
109
129
|
});
|
|
110
|
-
// Receiving agent automatically verifies sender via Provenance
|
|
111
130
|
const result = await client.send(agentId, task, budget);
|
|
112
131
|
```
|
|
113
132
|
|
|
@@ -124,21 +143,28 @@ const [resultA, resultB] = await Promise.all([
|
|
|
124
143
|
|
|
125
144
|
## How trust works
|
|
126
145
|
|
|
127
|
-
|
|
128
|
-
automatically calls `provenance-protocol` to verify the sender:
|
|
146
|
+
Two separate questions, and only one of them needs a network service.
|
|
129
147
|
|
|
130
148
|
```
|
|
131
149
|
AJPServer.receive()
|
|
132
|
-
→
|
|
133
|
-
→
|
|
134
|
-
|
|
135
|
-
→
|
|
136
|
-
→
|
|
137
|
-
→
|
|
138
|
-
|
|
139
|
-
|
|
150
|
+
→ validate the offer's shape
|
|
151
|
+
→ IDENTITY (offline, always)
|
|
152
|
+
fetch the sender's declaration from from.declaration_url
|
|
153
|
+
→ does it verify against the key inside it?
|
|
154
|
+
→ was it served from the location its provenance id names?
|
|
155
|
+
→ is that id the one the offer claims?
|
|
156
|
+
→ has this sender's key changed since last time?
|
|
157
|
+
then check the offer's signature with that key
|
|
158
|
+
→ STANDING (online, optional, you choose the attester)
|
|
159
|
+
→ revoked? open incidents? evidence stale? old enough?
|
|
160
|
+
→ unreachable attester → your policy, not a failed trust check
|
|
161
|
+
→ run onJob() only if both pass
|
|
162
|
+
→ 403 with a reason and a code if either does not
|
|
140
163
|
```
|
|
141
164
|
|
|
165
|
+
Identity never depends on anyone's uptime. Only standing does, and you decide
|
|
166
|
+
whose — one index, several, your own attester, or none at all.
|
|
167
|
+
|
|
142
168
|
Human senders (`from.type: 'human'`) skip Provenance verification.
|
|
143
169
|
Platform-level auth is assumed for humans.
|
|
144
170
|
|
|
@@ -198,4 +224,28 @@ export async function POST(req) {
|
|
|
198
224
|
|
|
199
225
|
---
|
|
200
226
|
|
|
201
|
-
##
|
|
227
|
+
## CLI
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Send a job to any indexed agent from the terminal
|
|
231
|
+
npx @ilucky21c/ajp-cli hire provenance:github:alice/summarizer \
|
|
232
|
+
--instruction "Summarize this paper: https://arxiv.org/abs/..." \
|
|
233
|
+
--budget 0.50 --timeout 60
|
|
234
|
+
|
|
235
|
+
# Check job status
|
|
236
|
+
npx @ilucky21c/ajp-cli jobs job_m0abc123 --endpoint https://alice-agent.example.com/api/agent
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Requires Provenance identity — set up first with `npx provenance-protocol keygen` and `npx provenance-protocol register`.
|
|
240
|
+
|
|
241
|
+
Full CLI reference: [getprovenance.dev/docs/ajp#cli](https://getprovenance.dev/docs/ajp#cli)
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Full documentation
|
|
246
|
+
|
|
247
|
+
[getprovenance.dev/docs/ajp](https://getprovenance.dev/docs/ajp)
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## MIT License — getprovenance.dev
|
package/cli/index.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ajp-cli — Agent Job Protocol CLI
|
|
4
|
+
*
|
|
5
|
+
* Requires Provenance identity (PROVENANCE_ID + PROVENANCE_PRIVATE_KEY).
|
|
6
|
+
* For identity setup: npx provenance-protocol keygen / npx provenance-protocol register
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ajp hire <provenance_id> --instruction <text> [--budget <usd>] [--timeout <s>]
|
|
10
|
+
* ajp jobs <job_id> --endpoint <url>
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createPrivateKey, sign as nodeSign, randomBytes } from 'crypto';
|
|
14
|
+
|
|
15
|
+
const API = process.env.PROVENANCE_API_URL || 'https://getprovenance.dev';
|
|
16
|
+
const VERSION = '0.1.0';
|
|
17
|
+
|
|
18
|
+
// ── Colours ───────────────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
const c = {
|
|
21
|
+
reset: '\x1b[0m', dim: '\x1b[2m', bold: '\x1b[1m',
|
|
22
|
+
green: '\x1b[32m', amber: '\x1b[33m', red: '\x1b[31m', blue: '\x1b[34m', white: '\x1b[97m',
|
|
23
|
+
};
|
|
24
|
+
const ok = s => `${c.green}✓${c.reset} ${s}`;
|
|
25
|
+
const err = s => `${c.red}✗${c.reset} ${s}`;
|
|
26
|
+
const dim = s => `${c.dim}${s}${c.reset}`;
|
|
27
|
+
const hi = s => `${c.white}${c.bold}${s}${c.reset}`;
|
|
28
|
+
const amb = s => `${c.amber}${s}${c.reset}`;
|
|
29
|
+
|
|
30
|
+
// ── Arg parsing ───────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
function parseArgs(argv) {
|
|
33
|
+
const args = { _: [] };
|
|
34
|
+
let i = 0;
|
|
35
|
+
while (i < argv.length) {
|
|
36
|
+
const a = argv[i];
|
|
37
|
+
if (a.startsWith('--')) {
|
|
38
|
+
const key = a.slice(2);
|
|
39
|
+
const next = argv[i + 1];
|
|
40
|
+
if (next && !next.startsWith('--')) { args[key] = next; i += 2; }
|
|
41
|
+
else { args[key] = true; i++; }
|
|
42
|
+
} else { args._.push(a); i++; }
|
|
43
|
+
}
|
|
44
|
+
return args;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ── Signing ───────────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
function generateJobId() {
|
|
50
|
+
return `job_${Date.now().toString(36)}${randomBytes(6).toString('hex')}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function signOffer(offer, privateKeyBase64) {
|
|
54
|
+
const { signature: _, ...rest } = offer;
|
|
55
|
+
const canonical = JSON.stringify(rest, Object.keys(rest).sort());
|
|
56
|
+
const key = createPrivateKey({ key: Buffer.from(privateKeyBase64, 'base64'), format: 'der', type: 'pkcs8' });
|
|
57
|
+
return `ed25519:${nodeSign(null, Buffer.from(canonical, 'utf8'), key).toString('base64')}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Commands ──────────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
async function cmdHire(args) {
|
|
63
|
+
const targetId = args._[1];
|
|
64
|
+
const instruction = args.instruction || args.i;
|
|
65
|
+
const budget = parseFloat(args.budget || args.b || '1.0');
|
|
66
|
+
const timeout = parseInt(args.timeout || args.t || '120');
|
|
67
|
+
const privateKey = args['private-key'] || process.env.PROVENANCE_PRIVATE_KEY;
|
|
68
|
+
const provenanceId = args['from-id'] || process.env.PROVENANCE_ID;
|
|
69
|
+
|
|
70
|
+
if (!targetId) { console.error(err('Usage: ajp hire <provenance_id> --instruction <text>')); process.exit(1); }
|
|
71
|
+
if (!instruction) { console.error(err('--instruction required')); process.exit(1); }
|
|
72
|
+
if (!privateKey) { console.error(err('PROVENANCE_PRIVATE_KEY not set. Run: npx provenance-protocol keygen')); process.exit(1); }
|
|
73
|
+
if (!provenanceId) { console.error(err('PROVENANCE_ID not set. Run: npx provenance-protocol register')); process.exit(1); }
|
|
74
|
+
|
|
75
|
+
console.log(`\n${amb('Hiring')} ${hi(targetId)}...\n`);
|
|
76
|
+
|
|
77
|
+
// Resolve endpoint
|
|
78
|
+
process.stdout.write(dim(' Resolving endpoint...'));
|
|
79
|
+
const agentRes = await fetch(`${API}/api/agent/${targetId.replace('provenance:', '').replace(':', '/')}`);
|
|
80
|
+
const agentData = await agentRes.json();
|
|
81
|
+
if (!agentData?.ajp?.endpoint) {
|
|
82
|
+
console.log('\n' + err('Agent has no AJP endpoint. Ask them to add ajp.endpoint to PROVENANCE.yml.'));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
const endpoint = agentData.ajp.endpoint.replace(/\/$/, '');
|
|
86
|
+
console.log(` ${c.green}${endpoint}${c.reset}`);
|
|
87
|
+
|
|
88
|
+
// Build and sign offer
|
|
89
|
+
const now = new Date();
|
|
90
|
+
const expiresAt = new Date(now.getTime() + timeout * 1000);
|
|
91
|
+
const jobId = generateJobId();
|
|
92
|
+
|
|
93
|
+
const offer = {
|
|
94
|
+
ajp: '0.1', job_id: jobId, parent_job_id: null,
|
|
95
|
+
from: { type: 'orchestrator', id: null, provenance_id: provenanceId },
|
|
96
|
+
to: { provenance_id: targetId },
|
|
97
|
+
task: { type: 'task', instruction, input: {}, output_format: 'json' },
|
|
98
|
+
context: { credentials: {}, memory: [], constraints: [] },
|
|
99
|
+
budget: { max_usd: budget, max_seconds: timeout, max_llm_tokens: 10000 },
|
|
100
|
+
callback: null,
|
|
101
|
+
issued_at: now.toISOString(),
|
|
102
|
+
expires_at: expiresAt.toISOString(),
|
|
103
|
+
signature: '',
|
|
104
|
+
};
|
|
105
|
+
offer.signature = signOffer(offer, privateKey);
|
|
106
|
+
|
|
107
|
+
// Submit
|
|
108
|
+
process.stdout.write(dim(' Submitting job...'));
|
|
109
|
+
const submitRes = await fetch(`${endpoint}/jobs`, {
|
|
110
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(offer),
|
|
111
|
+
});
|
|
112
|
+
const submitData = await submitRes.json().catch(() => ({}));
|
|
113
|
+
|
|
114
|
+
if (!submitRes.ok) {
|
|
115
|
+
console.log('\n' + err(submitData.error || submitData.reason || `HTTP ${submitRes.status}`));
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
console.log(` ${c.green}${jobId}${c.reset}`);
|
|
119
|
+
|
|
120
|
+
// Poll
|
|
121
|
+
const deadline = Date.now() + timeout * 1000;
|
|
122
|
+
const frames = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
|
|
123
|
+
let fi = 0;
|
|
124
|
+
|
|
125
|
+
while (Date.now() < deadline) {
|
|
126
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
127
|
+
const pollRes = await fetch(`${endpoint}/jobs/${jobId}`);
|
|
128
|
+
const pollData = await pollRes.json().catch(() => ({}));
|
|
129
|
+
|
|
130
|
+
process.stdout.write(`\r ${c.blue}${frames[fi++ % frames.length]}${c.reset} ${dim(pollData.status || 'polling...')} `);
|
|
131
|
+
|
|
132
|
+
if (pollData.status === 'completed') {
|
|
133
|
+
await fetch(`${endpoint}/jobs/${jobId}/ack`, {
|
|
134
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
135
|
+
body: JSON.stringify({ received: true }),
|
|
136
|
+
}).catch(() => {});
|
|
137
|
+
|
|
138
|
+
const dur = pollData.usage?.duration_seconds?.toFixed(1);
|
|
139
|
+
process.stdout.write(`\r${ok(`Completed${dur ? ` in ${dur}s` : ''}`)} \n\n`);
|
|
140
|
+
|
|
141
|
+
const out = pollData.output;
|
|
142
|
+
console.log(typeof out === 'string' ? out : JSON.stringify(out, null, 2));
|
|
143
|
+
|
|
144
|
+
if (pollData.usage) {
|
|
145
|
+
const u = pollData.usage;
|
|
146
|
+
const parts = [];
|
|
147
|
+
if (u.duration_seconds != null) parts.push(`${u.duration_seconds.toFixed(1)}s`);
|
|
148
|
+
if (u.cost_usd > 0) parts.push(`$${u.cost_usd.toFixed(4)}`);
|
|
149
|
+
if (u.llm_tokens > 0) parts.push(`${u.llm_tokens} tokens`);
|
|
150
|
+
if (parts.length) console.log('\n' + dim(parts.join(' · ')));
|
|
151
|
+
}
|
|
152
|
+
console.log();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (['failed','expired','rejected'].includes(pollData.status)) {
|
|
157
|
+
process.stdout.write(`\r${err(pollData.status + (pollData.message ? ': ' + pollData.message : ''))} \n\n`);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.log('\n' + err(`Timed out after ${timeout}s`));
|
|
163
|
+
process.exit(1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function cmdJobs(args) {
|
|
167
|
+
const jobId = args._[1];
|
|
168
|
+
const endpoint = args.endpoint;
|
|
169
|
+
if (!jobId) { console.error(err('Usage: ajp jobs <job_id> --endpoint <url>')); process.exit(1); }
|
|
170
|
+
if (!endpoint) { console.error(err('--endpoint required')); process.exit(1); }
|
|
171
|
+
|
|
172
|
+
const res = await fetch(`${endpoint.replace(/\/$/, '')}/jobs/${jobId}`);
|
|
173
|
+
const data = await res.json();
|
|
174
|
+
|
|
175
|
+
const statusColor = { completed: c.green, failed: c.red, expired: c.red, running: c.blue, accepted: c.amber }[data.status] || c.dim;
|
|
176
|
+
console.log(`\n${dim('job_id:')} ${data.job_id}`);
|
|
177
|
+
console.log(`${dim('status:')} ${statusColor}${data.status}${c.reset}`);
|
|
178
|
+
if (data.output) console.log(`\n${JSON.stringify(data.output, null, 2)}`);
|
|
179
|
+
if (data.message) console.log(`${c.red}${data.message}${c.reset}`);
|
|
180
|
+
console.log();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function cmdHelp() {
|
|
184
|
+
console.log(`
|
|
185
|
+
${hi('ajp')} ${dim(`v${VERSION}`)} — Agent Job Protocol CLI
|
|
186
|
+
|
|
187
|
+
${amb('Commands:')}
|
|
188
|
+
${hi('hire')} <provenance_id> Send a job to an agent via AJP
|
|
189
|
+
--instruction <text> What you want the agent to do
|
|
190
|
+
[--budget <usd>] Max cost ceiling (default: 1.00)
|
|
191
|
+
[--timeout <seconds>] Max wait time (default: 120)
|
|
192
|
+
[--from-id <id>] Your Provenance ID (default: $PROVENANCE_ID)
|
|
193
|
+
[--private-key <key>] Your private key (default: $PROVENANCE_PRIVATE_KEY)
|
|
194
|
+
|
|
195
|
+
${hi('jobs')} <job_id> Check status of a job
|
|
196
|
+
--endpoint <url> The agent's AJP endpoint URL
|
|
197
|
+
|
|
198
|
+
${amb('Environment variables:')}
|
|
199
|
+
PROVENANCE_ID Your Provenance ID (set up with: npx provenance-protocol register)
|
|
200
|
+
PROVENANCE_PRIVATE_KEY Your Ed25519 private key (set up with: npx provenance-protocol keygen)
|
|
201
|
+
PROVENANCE_API_URL Override Provenance API base URL
|
|
202
|
+
|
|
203
|
+
${amb('Examples:')}
|
|
204
|
+
ajp hire provenance:github:alice/summarizer \\
|
|
205
|
+
--instruction "Summarize https://arxiv.org/abs/2501.00001" \\
|
|
206
|
+
--budget 0.50 --timeout 60
|
|
207
|
+
|
|
208
|
+
ajp jobs job_m0abc123 --endpoint https://alice-agent.example.com/api/agent
|
|
209
|
+
|
|
210
|
+
${amb('Identity setup (first time):')}
|
|
211
|
+
npx provenance-protocol keygen
|
|
212
|
+
npx provenance-protocol register --id provenance:github:your-org/your-agent --url <url>
|
|
213
|
+
${dim('Then set PROVENANCE_ID and PROVENANCE_PRIVATE_KEY in your environment.')}
|
|
214
|
+
`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ── Main ──────────────────────────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
const argv = process.argv.slice(2);
|
|
220
|
+
const args = parseArgs(argv);
|
|
221
|
+
const cmd = args._[0];
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
if (!cmd || cmd === 'help' || args.help) cmdHelp();
|
|
225
|
+
else if (cmd === 'hire') await cmdHire(args);
|
|
226
|
+
else if (cmd === 'jobs') await cmdJobs(args);
|
|
227
|
+
else { console.error(err(`Unknown command: ${cmd}\nRun \`ajp help\` for usage.`)); process.exit(1); }
|
|
228
|
+
} catch (e) {
|
|
229
|
+
console.error(err(e.message));
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
package/cli/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ilucky21c/ajp-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for the Agent Job Protocol — hire agents and check job status from the terminal",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ajp": "./index.js"
|
|
8
|
+
},
|
|
9
|
+
"engines": { "node": ">=18" },
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/ilucky21c/ajp-protocol.git",
|
|
13
|
+
"directory": "cli"
|
|
14
|
+
},
|
|
15
|
+
"keywords": ["ajp", "agent-job-protocol", "provenance", "ai", "agents", "cli"],
|
|
16
|
+
"license": "MIT"
|
|
17
|
+
}
|
package/package.json
CHANGED
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ajp-protocol",
|
|
3
|
-
"version": "0.1
|
|
4
|
-
"description": "Agent Job Protocol
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Agent Job Protocol \u2014 standard interaction layer for the agent internet",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./src/index.js"
|
|
9
9
|
},
|
|
10
|
-
"keywords": [
|
|
10
|
+
"keywords": [
|
|
11
|
+
"ai-agent",
|
|
12
|
+
"agent-protocol",
|
|
13
|
+
"ajp",
|
|
14
|
+
"provenance",
|
|
15
|
+
"llm",
|
|
16
|
+
"agent-to-agent"
|
|
17
|
+
],
|
|
11
18
|
"license": "MIT",
|
|
12
19
|
"dependencies": {
|
|
13
|
-
"provenance-protocol": "^0.
|
|
20
|
+
"provenance-protocol": "^0.2.2"
|
|
14
21
|
},
|
|
15
22
|
"repository": {
|
|
16
23
|
"type": "git",
|
|
17
24
|
"url": "https://github.com/ilucky21c/ajp-protocol"
|
|
18
25
|
},
|
|
19
|
-
"homepage": "https://
|
|
26
|
+
"homepage": "https://getprovenance.dev/ajp",
|
|
27
|
+
"scripts": {
|
|
28
|
+
"test": "node test/trust.test.mjs"
|
|
29
|
+
}
|
|
20
30
|
}
|
package/schema/job-offer.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
-
"$id": "https://
|
|
3
|
+
"$id": "https://getprovenance.dev/ajp/schema/0.1/job-offer.json",
|
|
4
4
|
"title": "AJP JobOffer",
|
|
5
5
|
"description": "Agent Job Protocol v0.1 — JobOffer message",
|
|
6
6
|
"type": "object",
|
|
@@ -86,8 +86,8 @@
|
|
|
86
86
|
|
|
87
87
|
"signature": {
|
|
88
88
|
"type": "string",
|
|
89
|
-
"description": "
|
|
90
|
-
"pattern": "^sha256:[a-f0-9]{64}$"
|
|
89
|
+
"description": "Signature of the message body excluding this field. Human senders: sha256:hex (HMAC-SHA256). Agent/orchestrator senders: ed25519:base64 (Ed25519 with registered Provenance private key).",
|
|
90
|
+
"pattern": "^(sha256:[a-f0-9]{64}|ed25519:[A-Za-z0-9+/=]+)$"
|
|
91
91
|
}
|
|
92
92
|
},
|
|
93
93
|
|