@kleros/agentkit 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/dist/index.js ADDED
@@ -0,0 +1,477 @@
1
+ // src/contracts/abis/v2.ts
2
+ import { getContractsViem } from "@kleros/kleros-v2-contracts/cjs/deployments";
3
+ async function getV2Contracts(publicClient, deployment) {
4
+ return getContractsViem({ publicClient, deployment });
5
+ }
6
+
7
+ // src/contracts/registry.ts
8
+ var V1_CONTRACTS = {
9
+ ethereum: {
10
+ KlerosLiquid: {
11
+ name: "KlerosLiquid",
12
+ proxyAddress: "0x988b3A538b618C7A603e1c11Ab82Cd16dbE28069"
13
+ },
14
+ PolicyRegistry: {
15
+ name: "PolicyRegistry",
16
+ proxyAddress: "0xcf1f07713d5193fae5c1653c9f61953d048bece4"
17
+ },
18
+ PNK: {
19
+ name: "PNK",
20
+ proxyAddress: "0x93ED3FBe21207Ec2E8f2d3c3de6e058Cb73Bc04d"
21
+ },
22
+ KlerosLiquidExtraViews: {
23
+ name: "KlerosLiquidExtraViews",
24
+ proxyAddress: "0x2B562ea613ad2f58746935C842d09EB147E1E940"
25
+ },
26
+ ArbitrableProxy: {
27
+ name: "ArbitrableProxy",
28
+ proxyAddress: "0x99489D7bb33539F3D1A401741E56e8f02B9AE0Cf"
29
+ },
30
+ TransactionBatcher: {
31
+ name: "TransactionBatcher",
32
+ proxyAddress: "0x82458d1C812D7c930Bb3229c9e159cbabD9AA8Cb"
33
+ },
34
+ RNG: {
35
+ name: "RNG",
36
+ proxyAddress: "0x90992fb4E15ce0C59aEFfb376460Fda4Ee19C879"
37
+ }
38
+ },
39
+ gnosis: {
40
+ KlerosLiquid: {
41
+ name: "xKlerosLiquid",
42
+ proxyAddress: "0x9C1dA9A04925bDfDedf0f6421bC7EEa8305F9002",
43
+ implementationAddress: "0x87E1bfEB31Ac4FA857a08471847122ec338F3cF2"
44
+ },
45
+ PolicyRegistry: {
46
+ name: "PolicyRegistry",
47
+ proxyAddress: "0x9d494768936b6bDaabc46733b8D53A937A6c6D7e"
48
+ },
49
+ xPNK: {
50
+ name: "xPNK",
51
+ proxyAddress: "0x37b60f4E9A31A64cCc0024dce7D0fD07eAA0F7B3",
52
+ implementationAddress: "0xf8d1677c8a0c961938bf2f9adc3f3cfda759a9d9"
53
+ },
54
+ stPNK: {
55
+ name: "stPNK",
56
+ proxyAddress: "0xcb3231aBA3b451343e0Fddfc45883c842f223846",
57
+ implementationAddress: "0xad17051DBA7d6992DaB13a0989ecB8E3B2aE519B"
58
+ },
59
+ KlerosLiquidExtraViews: {
60
+ name: "KlerosLiquidExtraViews",
61
+ proxyAddress: "0xFA71f907B48f27d22f670d9E446f8137b0769e4B"
62
+ },
63
+ ArbitrableProxy: {
64
+ name: "ArbitrableProxy",
65
+ proxyAddress: "0xC7aDD3C961f7935CB4914E37DA991D2f1Cd7986c"
66
+ },
67
+ TransactionBatcher: {
68
+ name: "TransactionBatcher",
69
+ proxyAddress: "0x6426800F8508b15AED271337498fa5e7D0794d46"
70
+ },
71
+ RNG: {
72
+ name: "RNG",
73
+ proxyAddress: "0x52a2a2cD6775f35E53ea757Dd2F7E96ee81d7DD7"
74
+ }
75
+ },
76
+ sepolia: {
77
+ KlerosLiquid: {
78
+ name: "KlerosLiquid",
79
+ proxyAddress: "0x90992fb4E15ce0C59aEFfb376460Fda4Ee19C879"
80
+ },
81
+ KlerosLiquidExtraViews: {
82
+ name: "KlerosLiquidExtraViews",
83
+ proxyAddress: "0x5562Ac605764DC4039fb6aB56a74f7321396Cdf2"
84
+ },
85
+ PolicyRegistry: {
86
+ name: "PolicyRegistry",
87
+ proxyAddress: "0x88Fb25D399310c07d35cB9091b8346d8b1893aa5"
88
+ },
89
+ ArbitrableProxy: {
90
+ name: "ArbitrableProxy",
91
+ proxyAddress: "0x009cA5A0B816156F91B29A93d7688c52480BaB24"
92
+ }
93
+ }
94
+ };
95
+ function getV1Contract(chainId, contractName) {
96
+ return V1_CONTRACTS[chainId]?.[contractName];
97
+ }
98
+
99
+ // src/contracts/versions.ts
100
+ function getContractsByChain(config) {
101
+ if (config.version === "v1") {
102
+ return {
103
+ version: "v1",
104
+ getContract: (name) => getV1Contract(config.id, name)
105
+ };
106
+ }
107
+ return {
108
+ version: "v2",
109
+ // RULE-07: config.deployment is threaded through. The cast is safe here — the only
110
+ // caller today (src/infra/onchain.ts fetchDisputeOnChainData) checks config.deployment
111
+ // for undefined and returns DomainErrorCode.DEPLOYMENT_NOT_CONFIGURED BEFORE ever
112
+ // reaching this call site. A future second v2 on-chain caller should either replicate
113
+ // that guard or hoist the check into this function itself.
114
+ getContracts: (publicClient) => getV2Contracts(publicClient, config.deployment)
115
+ };
116
+ }
117
+
118
+ // src/core/types.ts
119
+ function ok(data) {
120
+ return { success: true, data };
121
+ }
122
+ function error(code, message, details = {}) {
123
+ return { success: false, code, message, details };
124
+ }
125
+
126
+ // src/types/errors.ts
127
+ var InfraErrorCode = {
128
+ SUBGRAPH_ERROR: "SUBGRAPH_ERROR",
129
+ IPFS_ERROR: "IPFS_ERROR",
130
+ RPC_ERROR: "RPC_ERROR",
131
+ API_ERROR: "API_ERROR",
132
+ TIMEOUT_ERROR: "TIMEOUT_ERROR",
133
+ BODY_TOO_LARGE: "BODY_TOO_LARGE",
134
+ NETWORK_ERROR: "NETWORK_ERROR",
135
+ WALLET_NOT_CONFIGURED: "WALLET_NOT_CONFIGURED",
136
+ // Phase 4 scaffold: write commands needing a wallet
137
+ IPFS_UNAVAILABLE: "IPFS_UNAVAILABLE"
138
+ // Phase 15 (RELY-02): cdn.kleros.link cannot serve this CID after one attempt — the repin signal. Default-gateway only (see D-28 in src/infra/ipfs.ts). Reserved names AUTH_REQUIRED/QUOTA_EXCEEDED belong to a future SEED-012 — do not reuse them here.
139
+ };
140
+ var DomainErrorCode = {
141
+ DISPUTE_NOT_FOUND: "DISPUTE_NOT_FOUND",
142
+ COURT_NOT_FOUND: "COURT_NOT_FOUND",
143
+ CHAIN_NOT_SUPPORTED: "CHAIN_NOT_SUPPORTED",
144
+ CONFIG_NOT_FOUND: "CONFIG_NOT_FOUND",
145
+ CONTRACT_NOT_FOUND: "CONTRACT_NOT_FOUND",
146
+ // Phase 2 additions (per D-05)
147
+ JUROR_NOT_FOUND: "JUROR_NOT_FOUND",
148
+ EVIDENCE_NOT_FOUND: "EVIDENCE_NOT_FOUND",
149
+ STAKE_NOT_FOUND: "STAKE_NOT_FOUND",
150
+ INVALID_ADDRESS: "INVALID_ADDRESS",
151
+ INVALID_STATUS: "INVALID_STATUS",
152
+ // Phase 4 additions (LIST-01..04)
153
+ INVALID_LIMIT: "INVALID_LIMIT",
154
+ INVALID_ORDER_BY: "INVALID_ORDER_BY",
155
+ INVALID_ORDER_DIRECTION: "INVALID_ORDER_DIRECTION",
156
+ INVALID_CURSOR: "INVALID_CURSOR",
157
+ // Phase 6 additions
158
+ CHAIN_REQUIRED_FOR_COURT_FILTER: "CHAIN_REQUIRED_FOR_COURT_FILTER",
159
+ // --court requires --chain (D-20)
160
+ CHAIN_REQUIRED: "CHAIN_REQUIRED",
161
+ // command requires explicit --chain (e.g. juror info)
162
+ INVALID_COURT_ID: "INVALID_COURT_ID",
163
+ // --court value is not a non-negative integer (juror top)
164
+ // Phase 7 additions
165
+ INVALID_CONFIG_KEY: "INVALID_CONFIG_KEY",
166
+ // config set/unset received an unrecognised key
167
+ // Phase 9 additions (Meta-Evidence Resolver + Reality Handler)
168
+ // Resolver-layer hard failures — surfaced as success: false to callers:
169
+ META_EVIDENCE_NOT_FOUND: "META_EVIDENCE_NOT_FOUND",
170
+ META_EVIDENCE_PARSE_ERROR: "META_EVIDENCE_PARSE_ERROR",
171
+ // Handler-layer soft failures — caught by dispatcher, surfaced as enrichment.handler_warnings[]:
172
+ // These are returned from handler as error() but NEVER propagate as top-level success: false
173
+ // (D-10/D-11: KlerosResult<MetaEvidence> is success: true whenever MetaEvidence was fetched+parsed)
174
+ REALITY_DISPUTE_QUESTION_NOT_FOUND: "REALITY_DISPUTE_QUESTION_NOT_FOUND",
175
+ REALITY_QUESTION_NOT_FOUND: "REALITY_QUESTION_NOT_FOUND",
176
+ REALITY_TEMPLATE_NOT_FOUND: "REALITY_TEMPLATE_NOT_FOUND",
177
+ REALITY_HANDLER_FAILURE: "REALITY_HANDLER_FAILURE",
178
+ // Phase 12 additions (Composite Brief + Feedback Loop)
179
+ RATE_LIMITED: "RATE_LIMITED",
180
+ // report-issue: 3/60s sliding window exceeded (FB-04)
181
+ GITHUB_TOKEN_MISSING: "GITHUB_TOKEN_MISSING",
182
+ // --submit but GITHUB_TOKEN env var absent (D-09)
183
+ REPORT_WRITE_FAILED: "REPORT_WRITE_FAILED",
184
+ // local file write to ~/.kleros/reports/ failed
185
+ // Phase 14 additions (Ruling Semantics + URI Cleanup + Deployment Threading)
186
+ DISPUTE_ENRICHMENT_FAILED: "DISPUTE_ENRICHMENT_FAILED",
187
+ // on-chain ruling read failed on an existing dispute
188
+ DEPLOYMENT_NOT_CONFIGURED: "DEPLOYMENT_NOT_CONFIGURED"
189
+ // v2 ChainConfig missing `deployment`
190
+ };
191
+
192
+ // src/infra/http.ts
193
+ var DEFAULT_TIMEOUT = 1e4;
194
+ async function fetchJson(url, options) {
195
+ const timeout = options?.timeout ?? DEFAULT_TIMEOUT;
196
+ try {
197
+ const response = await fetch(url, {
198
+ signal: AbortSignal.timeout(timeout)
199
+ });
200
+ if (!response.ok) {
201
+ return error(InfraErrorCode.API_ERROR, `HTTP ${response.status} ${response.statusText}`, {
202
+ url,
203
+ status: response.status
204
+ });
205
+ }
206
+ const data = await response.json();
207
+ return ok(data);
208
+ } catch (e) {
209
+ if (e instanceof DOMException && (e.name === "TimeoutError" || e.name === "AbortError")) {
210
+ return error(InfraErrorCode.TIMEOUT_ERROR, "HTTP fetch timed out", { url, timeout });
211
+ }
212
+ const message = e instanceof Error ? e.message : String(e);
213
+ return error(InfraErrorCode.API_ERROR, message, { url });
214
+ }
215
+ }
216
+
217
+ // src/infra/ipfs.ts
218
+ import QuickLRU from "quick-lru";
219
+
220
+ // src/infra/codec.ts
221
+ function decodeAsJson(bytes) {
222
+ try {
223
+ const text = new TextDecoder().decode(bytes);
224
+ return JSON.parse(text);
225
+ } catch {
226
+ return null;
227
+ }
228
+ }
229
+ function decodeAsText(bytes) {
230
+ return new TextDecoder().decode(bytes);
231
+ }
232
+
233
+ // src/infra/ipfs.ts
234
+ var DEFAULT_GATEWAY = "https://cdn.kleros.link";
235
+ var DEFAULT_TIMEOUT2 = 1e4;
236
+ var MAX_CACHED_BODY_BYTES = 1048576;
237
+ var _ipfsCache = new QuickLRU({
238
+ maxSize: 200
239
+ });
240
+ var _inflight = /* @__PURE__ */ new Map();
241
+ function normalizeIpfsCid(uri) {
242
+ if (uri.startsWith("ipfs://")) return uri.slice(7);
243
+ if (uri.startsWith("/ipfs/")) return uri.slice(6);
244
+ return uri;
245
+ }
246
+ async function _fetchRaw(url, timeout, uri, isDefaultGateway, maxBodyBytes) {
247
+ try {
248
+ const response = await fetch(url, { signal: AbortSignal.timeout(timeout) });
249
+ if (!response.ok) {
250
+ if (isDefaultGateway && response.status >= 500) {
251
+ const cid = normalizeIpfsCid(uri);
252
+ return error(
253
+ InfraErrorCode.IPFS_UNAVAILABLE,
254
+ `IPFS_UNAVAILABLE: cdn.kleros.link returned ${response.status} ${response.statusText} for this CID \u2014 content may be unpinned. No automatic gateway fallback is attempted (by design).`,
255
+ { uri, cid, status: response.status, reason: "http-5xx" }
256
+ );
257
+ }
258
+ return error(
259
+ InfraErrorCode.IPFS_ERROR,
260
+ `IPFS fetch failed: ${response.status} ${response.statusText}`,
261
+ { uri, status: response.status }
262
+ );
263
+ }
264
+ if (maxBodyBytes !== void 0) {
265
+ const cl = response.headers.get("content-length");
266
+ if (cl !== null) {
267
+ const clNum = Number(cl);
268
+ if (!Number.isNaN(clNum) && clNum > maxBodyBytes) {
269
+ return error(
270
+ InfraErrorCode.BODY_TOO_LARGE,
271
+ `IPFS body too large: Content-Length ${clNum} exceeds ${maxBodyBytes}`,
272
+ { uri, contentLength: clNum, maxBodyBytes }
273
+ );
274
+ }
275
+ }
276
+ }
277
+ const contentType = response.headers.get("content-type");
278
+ const arrayBuffer = await response.arrayBuffer();
279
+ const body = new Uint8Array(arrayBuffer);
280
+ return ok({ body, contentType });
281
+ } catch (e) {
282
+ if (e instanceof DOMException && (e.name === "TimeoutError" || e.name === "AbortError")) {
283
+ if (isDefaultGateway) {
284
+ const cid = normalizeIpfsCid(uri);
285
+ return error(
286
+ InfraErrorCode.IPFS_UNAVAILABLE,
287
+ "IPFS_UNAVAILABLE: cdn.kleros.link timed out fetching this CID \u2014 content may be unpinned. No automatic gateway fallback is attempted (by design).",
288
+ { uri, cid, timeout, reason: "timeout" }
289
+ );
290
+ }
291
+ return error(InfraErrorCode.TIMEOUT_ERROR, "IPFS fetch timed out", { uri, timeout });
292
+ }
293
+ const message = e instanceof Error ? e.message : String(e);
294
+ return error(InfraErrorCode.IPFS_ERROR, message, { uri });
295
+ }
296
+ }
297
+ function _fetchRawCoalesced(url, timeout, uri, isDefaultGateway, maxBodyBytes, cid, gateway) {
298
+ const key = `${cid}|${maxBodyBytes ?? "none"}|${gateway}`;
299
+ const existing = _inflight.get(key);
300
+ if (existing !== void 0) return existing;
301
+ const promise = _fetchRaw(url, timeout, uri, isDefaultGateway, maxBodyBytes).finally(() => {
302
+ _inflight.delete(key);
303
+ });
304
+ _inflight.set(key, promise);
305
+ return promise;
306
+ }
307
+ async function fetchIpfs(uri, options) {
308
+ const gateway = options?.gateway ?? DEFAULT_GATEWAY;
309
+ const timeout = options?.timeout ?? DEFAULT_TIMEOUT2;
310
+ const cid = normalizeIpfsCid(uri);
311
+ const cacheKey = `${gateway}|${cid}`;
312
+ const cached = _ipfsCache.get(cacheKey);
313
+ if (cached !== void 0) {
314
+ const json2 = decodeAsJson(cached.body);
315
+ if (json2 !== null) return ok(json2);
316
+ return ok(decodeAsText(cached.body));
317
+ }
318
+ const url = `${gateway}/ipfs/${cid}`;
319
+ const isDefaultGateway = gateway === DEFAULT_GATEWAY;
320
+ const raw = await _fetchRawCoalesced(
321
+ url,
322
+ timeout,
323
+ uri,
324
+ isDefaultGateway,
325
+ void 0,
326
+ cid,
327
+ gateway
328
+ );
329
+ if (!raw.success) return raw;
330
+ if (raw.data.body.length <= MAX_CACHED_BODY_BYTES) {
331
+ _ipfsCache.set(cacheKey, raw.data);
332
+ }
333
+ const json = decodeAsJson(raw.data.body);
334
+ if (json !== null) return ok(json);
335
+ return ok(decodeAsText(raw.data.body));
336
+ }
337
+
338
+ // src/infra/ipfs-walk.ts
339
+ import { fileTypeFromBuffer } from "file-type";
340
+
341
+ // src/infra/rpc.ts
342
+ import { createPublicClient, http } from "viem";
343
+
344
+ // src/types/network.ts
345
+ function getSubgraphUrl(chain, type) {
346
+ const envKey = `KLEROS_SUBGRAPH_URL_${chain.id.toUpperCase().replace("-", "_")}_${type.toUpperCase()}`;
347
+ const envValue = process.env[envKey];
348
+ if (envValue) return envValue;
349
+ return type === "core" ? chain.subgraph.core : chain.subgraph.drt;
350
+ }
351
+ function getRpcUrl(chain) {
352
+ const envKey = `KLEROS_RPC_URL_${chain.id.toUpperCase().replace("-", "_")}`;
353
+ const envValue = process.env[envKey];
354
+ if (envValue) return envValue;
355
+ return chain.rpcUrl;
356
+ }
357
+
358
+ // src/infra/rpc.ts
359
+ function createKlerosClient(config) {
360
+ return createPublicClient({
361
+ chain: config.viemChain,
362
+ transport: http(getRpcUrl(config))
363
+ });
364
+ }
365
+
366
+ // src/infra/subgraph.ts
367
+ import { GraphQLClient } from "graphql-request";
368
+ async function querySubgraph(url, query, variables) {
369
+ try {
370
+ const client = new GraphQLClient(url);
371
+ const data = await client.request(query, variables);
372
+ return ok(data);
373
+ } catch (e) {
374
+ const message = e instanceof Error ? e.message : String(e);
375
+ return error(InfraErrorCode.SUBGRAPH_ERROR, message, { url, query });
376
+ }
377
+ }
378
+ async function querySubgraphPaginated(url, query, entityName, variables, pageSize = 100) {
379
+ const client = new GraphQLClient(url);
380
+ const allEntities = [];
381
+ let lastId = "";
382
+ try {
383
+ while (true) {
384
+ const vars = { ...variables, first: pageSize, lastId };
385
+ const data = await client.request(query, vars);
386
+ const page = data[entityName];
387
+ if (!page || page.length === 0) {
388
+ break;
389
+ }
390
+ allEntities.push(...page);
391
+ if (page.length < pageSize) {
392
+ break;
393
+ }
394
+ lastId = page[page.length - 1].id;
395
+ }
396
+ return ok(allEntities);
397
+ } catch (e) {
398
+ const message = e instanceof Error ? e.message : String(e);
399
+ return error(InfraErrorCode.SUBGRAPH_ERROR, message, { url, entityName });
400
+ }
401
+ }
402
+
403
+ // src/types/chains.ts
404
+ import { arbitrumSepolia, gnosis, mainnet, sepolia } from "viem/chains";
405
+ var CHAIN_CONFIGS = {
406
+ ethereum: {
407
+ id: "ethereum",
408
+ chainId: 1,
409
+ viemChain: mainnet,
410
+ version: "v1",
411
+ name: "Ethereum",
412
+ rpcUrl: "https://eth.llamarpc.com",
413
+ subgraph: {
414
+ core: "https://gateway.thegraph.com/api/28aec30509f7a09a74a0690880025e3b/subgraphs/id/BqbBhB4R5pNAtdYya2kcojMrQMp8nVHioUnP22qN8JoN"
415
+ }
416
+ },
417
+ gnosis: {
418
+ id: "gnosis",
419
+ chainId: 100,
420
+ viemChain: gnosis,
421
+ version: "v1",
422
+ name: "Gnosis",
423
+ rpcUrl: "https://rpc.gnosischain.com",
424
+ subgraph: {
425
+ core: "https://gateway.thegraph.com/api/28aec30509f7a09a74a0690880025e3b/subgraphs/id/FxhLntVBELrZ4t1c2HNNvLWEYfBjpB8iKZiEymuFSPSr"
426
+ }
427
+ },
428
+ "arbitrum-sepolia": {
429
+ id: "arbitrum-sepolia",
430
+ chainId: 421614,
431
+ viemChain: arbitrumSepolia,
432
+ version: "v2",
433
+ name: "Arbitrum Sepolia (devnet)",
434
+ rpcUrl: "https://sepolia-rollup.arbitrum.io/rpc",
435
+ subgraph: {
436
+ core: "https://gateway.thegraph.com/api/28aec30509f7a09a74a0690880025e3b/subgraphs/id/4Rg3NAW99hX8RDD281ycfeRWxp92k6RdqpW3pf6imGTS",
437
+ drt: "https://gateway.thegraph.com/api/28aec30509f7a09a74a0690880025e3b/subgraphs/id/HZHLr6QXebWvttsCuJsSEzYGuQLHZNdVLfEcGUSESoMs"
438
+ },
439
+ deployment: "devnet"
440
+ },
441
+ sepolia: {
442
+ id: "sepolia",
443
+ chainId: 11155111,
444
+ viemChain: sepolia,
445
+ version: "v1",
446
+ name: "Sepolia (testnet)",
447
+ rpcUrl: "https://ethereum-sepolia-rpc.publicnode.com",
448
+ // DEFAULT — override via KLEROS_RPC_URL_SEPOLIA (Infura/Alchemy required for event scans)
449
+ subgraph: {
450
+ core: "https://gateway.thegraph.com/api/28aec30509f7a09a74a0690880025e3b/subgraphs/id/DCc7YMYrpTodkQpmmyduPmtbaQNkNtx5UMm1RsuVX7vn"
451
+ }
452
+ }
453
+ };
454
+ var DEFAULT_CHAIN = "ethereum";
455
+ function resolveChainConfig(id) {
456
+ return CHAIN_CONFIGS[id];
457
+ }
458
+ export {
459
+ CHAIN_CONFIGS,
460
+ DEFAULT_CHAIN,
461
+ DomainErrorCode,
462
+ InfraErrorCode,
463
+ V1_CONTRACTS,
464
+ createKlerosClient,
465
+ error,
466
+ fetchIpfs,
467
+ fetchJson,
468
+ getContractsByChain,
469
+ getRpcUrl,
470
+ getSubgraphUrl,
471
+ getV1Contract,
472
+ getV2Contracts,
473
+ ok,
474
+ querySubgraph,
475
+ querySubgraphPaginated,
476
+ resolveChainConfig
477
+ };
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@kleros/agentkit",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Agent-first CLI/MCP/HTTP toolkit for Kleros dispute resolution",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/kleros/agentkit.git"
9
+ },
10
+ "main": "dist/index.js",
11
+ "types": "dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.js"
16
+ }
17
+ },
18
+ "bin": {
19
+ "kleros": "dist/cli.js"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "skills/agentkit/SKILL.md",
24
+ "README.md",
25
+ "LICENSE"
26
+ ],
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "scripts": {
31
+ "dev": "tsx src/cli.ts",
32
+ "build": "tsup",
33
+ "test": "vitest",
34
+ "test:integration": "vitest --config vitest.integration.config.ts",
35
+ "lint": "biome check .",
36
+ "format": "biome format . --write",
37
+ "lint:no-eval": "! grep -rE 'eval\\(|new Function\\(|vm\\.runInNewContext\\(' src/contracts/ src/core/",
38
+ "verify:skill": "bash scripts/verify-skill-commands.sh",
39
+ "update-deployment-blocks": "tsx scripts/update-deployment-blocks.ts",
40
+ "prepublishOnly": "pnpm build"
41
+ },
42
+ "dependencies": {
43
+ "@kleros/kleros-v2-contracts": "^2.0.0-rc.2",
44
+ "conf": "^15.1.0",
45
+ "dotenv": "^17.3.1",
46
+ "file-type": "~21.3.4",
47
+ "graphql": "^16.10.0",
48
+ "graphql-request": "^7.4.0",
49
+ "incur": "~0.4.11",
50
+ "picocolors": "^1.1.1",
51
+ "quick-lru": "^7.3.0",
52
+ "viem": "^2.47.6"
53
+ },
54
+ "devDependencies": {
55
+ "@biomejs/biome": "^2.4.8",
56
+ "@types/node": "^25.5.0",
57
+ "tsup": "^8.5.1",
58
+ "tsx": "^4.21.0",
59
+ "typescript": "~5.8.3",
60
+ "vitest": "^4.1.1"
61
+ },
62
+ "engines": {
63
+ "node": ">=22"
64
+ },
65
+ "license": "MIT"
66
+ }
@@ -0,0 +1,125 @@
1
+ ---
2
+ name: agentkit
3
+ description: Query the Kleros dispute resolution protocol — disputes, courts, jurors, evidence, policy, arbitrable contracts — and compose peer MCPs (markitdown) for binary content.
4
+ trigger: when the user asks about Kleros disputes, court policy, juror behavior, PNK staking, dispute evidence, or arbitrable contract classification
5
+ ---
6
+
7
+ ## What this skill does
8
+
9
+ AgentKit (`@kleros/agentkit`) is a token-efficient CLI/MCP/HTTP toolkit for the Kleros dispute resolution protocol. It ships three command groups: v1.0 (courts, disputes, evidence, jurors, stakes, config — Ethereum and Gnosis), v1.1 (juror leaderboard — cross-chain), and v1.2 (dispute policy, composite brief, arbitrable classification, issue reporting — v1 chains only). All output defaults to TOON format (40% fewer tokens); use `--format json`, `--format yaml`, or `--format md` when structured parsing is needed.
10
+
11
+ ## Command surface
12
+
13
+ | Command | What it does | Chains | Version |
14
+ |---------|-------------|--------|---------|
15
+ | `kleros arbitrable classify <address>` | Classify an arbitrable contract as a known Kleros type (curated registry hit) or free-text category (Meta-Evidence fallback — callers must not branch on category value). Requires `--chain`. | ethereum, gnosis | v1.2 |
16
+ | `kleros config get <key>` | Read a single persisted configuration value by key. | any | v1.0 |
17
+ | `kleros config list` | List all persisted configuration values. | any | v1.0 |
18
+ | `kleros config set <key> <value>` | Persist a configuration default (e.g., chain) so subsequent commands inherit it without `--chain`. | any | v1.0 |
19
+ | `kleros config unset <key>` | Remove a persisted configuration key. | any | v1.0 |
20
+ | `kleros court get <id>` | Fetch detailed parameters for a specific court: timing, fee, min stake, parent, hidden votes. | ethereum, gnosis | v1.0 |
21
+ | `kleros court list` | List all Kleros arbitration courts with stake requirements and fee structure. | ethereum, gnosis | v1.0 |
22
+ | `kleros court policy <id>` | Retrieve the arbitration policy document for a court from IPFS. | ethereum, gnosis | v1.0 |
23
+ | `kleros dispute brief <id>` | Composite snapshot: policy URIs, evidence with IPFS hints, ruling labels, parties, appeal state. `--depth 0` subgraph only; `--depth 1` (default) resolved URIs; `--depth 2` full IPFS content with extractionHints. **v1 only** — arbitrum-sepolia returns `CHAIN_NOT_SUPPORTED`. | ethereum, gnosis | v1.2 |
24
+ | `kleros dispute get <id>` | Fetch full dispute details: period, juror count, ruling, deadlines, arbitrated contract. | ethereum, gnosis, arbitrum-sepolia | v1.0 |
25
+ | `kleros dispute list` | List disputes with optional filters (court, status, chain). | ethereum, gnosis, arbitrum-sepolia | v1.0 |
26
+ | `kleros dispute policy <id>` | Fetch dispute-specific policy via Meta-Evidence chain. Returns `metaEvidenceUri`, `policyUri`, and enrichment. Effective constraint = court ∩ dispute — always also fetch court policy. **v1 only** — arbitrum-sepolia returns `CHAIN_NOT_SUPPORTED`. | ethereum, gnosis | v1.2 |
27
+ | `kleros evidence list` | List evidence submitted for a dispute including resolved IPFS metadata. Items with binary attachments carry `extractionHint` for markitdown-mcp composition. | ethereum, gnosis, arbitrum-sepolia | v1.0/v1.2 |
28
+ | `kleros juror info` | Fetch a juror's profile: PNK balance breakdown, active dispute count, coherence score, per-court stake positions. | ethereum, gnosis | v1.0 |
29
+ | `kleros juror top` | Rank jurors by total staked PNK. Without `--chain`, merges ethereum + gnosis. | ethereum, gnosis | v1.1 |
30
+ | `kleros report-issue create` | Create a feedback report. Writes a local `.md` file by default; `--submit` posts to GitHub Issues (requires `GITHUB_TOKEN`). | any | v1.2 |
31
+ | `kleros stake list` | List PNK token stakes held by a juror address across courts. | ethereum, gnosis | v1.0 |
32
+
33
+ Run `kleros --llms-full` for the full manifest. Run `kleros <command> --schema` for argument details.
34
+
35
+ ## Common workflows
36
+
37
+ ### Example 1 — Policy resolution (intersection rule)
38
+
39
+ ```
40
+ Q: What does Kleros consider when ruling on dispute 797?
41
+
42
+ A: Run two commands together — the effective juror constraint is the intersection:
43
+
44
+ kleros court policy <courtId> --chain gnosis
45
+ kleros dispute policy 797 --chain gnosis
46
+
47
+ court policy = what the court always requires
48
+ dispute policy = what this dispute adds
49
+
50
+ The effective juror constraint = court ∩ dispute. The juror must satisfy BOTH.
51
+ Neither alone is sufficient.
52
+ ```
53
+
54
+ ### Example 2 — Evidence with binary attachments
55
+
56
+ ```
57
+ Q: What evidence was submitted, including the attached PDFs?
58
+
59
+ A: Step 1: kleros evidence list --dispute 797 --chain gnosis
60
+ Step 2: For each item with an extractionHint field:
61
+ Call peer MCP: convert_to_markdown(uri=<extractionHint.uri>)
62
+ AgentKit returns URIs and extractionHints — markitdown-mcp does the text extraction.
63
+ AgentKit never extracts the binary itself.
64
+ ```
65
+
66
+ ### Example 3 — Composite brief
67
+
68
+ ```
69
+ Q: Give me everything about dispute 797 in one call.
70
+
71
+ A: kleros dispute brief 797 --chain gnosis --depth 1
72
+
73
+ Returns: policy URIs, evidence list with extractionHints, ruling labels, parties, appeal state.
74
+ For full IPFS-resolved content: --depth 2 (higher token cost).
75
+ Note: roundHistoryNote will explain round history is deferred to v1.3.
76
+ ```
77
+
78
+ ## Composing peer MCPs — markitdown-mcp
79
+
80
+ When `evidence list` or `dispute brief` returns an item with an `extractionHint`, the agent should call the peer MCP server `markitdown-mcp` to extract text content from the binary attachment. AgentKit never bundles or calls markitdown directly — it returns URIs and lets agents compose the peer MCP.
81
+
82
+ **Wire format:**
83
+
84
+ ```
85
+ Agent receives extractionHint:
86
+ { recommendedTool: "markitdown", mcpToolName: "convert_to_markdown", uri: "ipfs://Qm..." }
87
+
88
+ Agent calls peer MCP:
89
+ convert_to_markdown(uri="ipfs://Qm...")
90
+ ```
91
+
92
+ The `uri` field accepts `http:`, `https:`, `ipfs:`, `file:`, and `data:` schemes.
93
+
94
+ Install: https://github.com/microsoft/markitdown/tree/main/packages/markitdown-mcp
95
+
96
+ ```
97
+ pip install markitdown-mcp
98
+ ```
99
+
100
+ ## Common gotchas
101
+
102
+ - **Chain-specificity:** Court and arbitrable addresses differ between `ethereum` and `gnosis`. Always pass `--chain` when querying a specific dispute, court, or arbitrable contract. Without `--chain`, the default is `ethereum`.
103
+ - **v1/v2 boundary:** `dispute policy`, `dispute brief`, and `arbitrable classify` are **v1 only**. Running any of these on `--chain arbitrum-sepolia` returns `CHAIN_NOT_SUPPORTED`. `dispute get`, `dispute list`, and `evidence list` work on all chains including v2.
104
+ - **`category` is advisory, not structural:** `arbitrable classify` may return a free-text `category` field under `curationSource: 'ipfs-category'`. Never branch logic on this value — it is derived from unstructured Meta-Evidence and is advisory only.
105
+ - **Intersection rule:** Court policy and dispute policy are **both required**. `court policy` defines the court-wide rules; `dispute policy` adds dispute-specific constraints. The effective juror constraint is their intersection (court ∩ dispute). Running only one will miss critical context.
106
+ - **Ruling labels may be absent:** `dispute get` returns `rulingOptions.titles[]` and `rulingLabel` after Meta-Evidence enrichment. These fields may be absent if the dispute has no associated Meta-Evidence or if enrichment fails. Treat them as optional in downstream logic.
107
+ - **`dispute get` is live on-chain; `dispute list` is a subgraph snapshot:** `dispute get`'s `ruling`/`ruled` fields are read directly from the arbitrator contract (`currentRuling()`/`disputes()`), on both v1 and v2, on every invocation shape (`--chain` or the default multi-chain probe) — authoritative arbitrator-side state. `dispute list` items stay subgraph-sourced for performance and may show v2 `ruled` flipping up to ~2h before the arbitrable contract is actually notified (the subgraph's `ruled` is set when the `Ruling` event fires inside `passPeriod`, which is the arbitrator-side period transition — not when `executeRuling()` is later called to notify the arbitrable). Use `dispute get` when the ruling value must be current.
108
+ - **Dead IPFS content surfaces as `IPFS_UNAVAILABLE`, not a generic error:** when `cdn.kleros.link` times out or returns a 5xx for a specific CID, `evidence list` (per-item `error` field), `dispute policy`, and `dispute brief` (per-section `error.code`) all report the distinct `IPFS_UNAVAILABLE` code instead of the generic `IPFS_ERROR`/`TIMEOUT_ERROR`. This is a single-attempt classification — AgentKit does not retry internally and does not try a second gateway (a client-side fallback would mask the signal that the content genuinely needs repinning, since `cdn.kleros.link` already fans out server-side). If you see `IPFS_UNAVAILABLE`, retrying later may help (transient failures do happen); a gateway swap will not.
109
+ - **`dispute brief` is single-chain by design, not a bug:** it always returns `CHAIN_REQUIRED` with an actionable CTA when invoked without an explicit `--chain`. There is no multi-chain fan-out for `dispute brief` — pass `--chain ethereum` or `--chain gnosis` explicitly. This mirrors the v1/v2 boundary bullet above: a deliberate design constraint, not something to route around.
110
+
111
+ ## Feedback
112
+
113
+ When something unexpected happens — unknown script CID, missing ruling labels, wrong arbitrable classification, or evidence content that does not load — file a feedback report:
114
+
115
+ ```
116
+ kleros report-issue create --type mcp-gap --description "Dispute 797 on gnosis: evidence item QmXxx returned no extractionHint but file extension is .pdf"
117
+ ```
118
+
119
+ Use `--submit` to post directly to GitHub Issues (requires `$GITHUB_TOKEN`):
120
+
121
+ ```
122
+ kleros report-issue create --type mcp-gap --description "..." --submit
123
+ ```
124
+
125
+ Reports are stored locally as `.md` files and optionally posted upstream. Filing a report helps improve coverage in future AgentKit releases.