@taifoon/jev 0.2.2 → 0.2.4

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/README.md CHANGED
@@ -76,6 +76,32 @@ seat. The decision is anchored on the Taifoon devnet in
76
76
  `npx @taifoon/jev run --demo` replays it from [`examples/jobs/base-bitagent-7287.recorded.json`](examples/jobs/base-bitagent-7287.recorded.json)
77
77
  with no key and no network, and arrives at the same decision digest.
78
78
 
79
+ ## In n8n
80
+
81
+ The same grader runs in n8n through [`@taifoon/n8n-nodes-typesafe`](https://www.npmjs.com/package/@taifoon/n8n-nodes-typesafe),
82
+ with eleven ready-to-import judge workflows: grade an agent's delivery, grade a Base job from its record, fact-check a
83
+ chatbot answer, settle a refund dispute and more. See [its README](https://github.com/taifoon-io/n8n-nodes-typesafe#judge-workflows-ready-to-import).
84
+
85
+ ## `jev run` flags
86
+
87
+ Safe by default: your own TypeSafe key only, nothing recorded, nothing signed or sent. A typo is refused, never guessed.
88
+
89
+ | Flag | Default | What it does |
90
+ |---|---|---|
91
+ | `--demo` | off | Replays the real Base job above, offline, no key |
92
+ | `--job <chain>:<id>` | first ready job in the layer's queue | Grade one live job, e.g. `8453:bitagent:8453:7287` |
93
+ | `--job-file job.json` | | Grade a job you describe (see `examples/jobs/`) |
94
+ | `--evidence pack.json` | | Grade your own evidence pack, no layer |
95
+ | `--answers answers.json` | | Answers you already have (e.g. from the n8n node); Jev is not asked |
96
+ | `--record none\|devnet\|base\|both` | `none` | Opt in to the unsigned calls that record the grade on chain |
97
+ | `--layer <https url>` / `--no-layer` | `https://coord.taifoon.dev` | Where jobs and quotes are read |
98
+ | `--protocol <name>` | from the job id | Evaluator seat: `virtuals-erc8183`, `virtuals-memo-acp`, `bitagent-erc8183`, `assurance-hook`, `judge-adapter` |
99
+ | `--price-usdc <n>` | the job's budget | Price for the premium quote |
100
+ | `--yes` / `--json` | ask per step / text | Run every step without asking / print the whole trace as JSON |
101
+
102
+ Environment: `TYPESAFE_KEY` (your key; without it a job that needs Jev stops and says where to get one, while a job the
103
+ code checks reject is still graded). Exit codes: `0` done, `1` a step failed, `2` a flag was refused.
104
+
79
105
  ## What you must know
80
106
 
81
107
  - **Code decides the facts first.** A failed check ends the job as reject, and Jev is never asked.
package/bin/jev.mjs CHANGED
@@ -12,7 +12,9 @@ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
12
12
  const PKG = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'));
13
13
  const args = process.argv.slice(2);
14
14
  const flag = (n) => { const i = args.indexOf(n); return i >= 0 ? args.splice(i, 2)[1] : undefined; };
15
- const version = flag('--version') ?? PKG.version;
15
+ // the newest bundle shipped (a bundle is versioned on its own and rebuilt only when the workflows change)
16
+ const BUNDLES = readdirSync(join(ROOT, 'workflows')).filter((d) => /^\d+\.\d+\.\d+$/.test(d)).sort((x, y) => x.localeCompare(y, undefined, { numeric: true }));
17
+ const version = flag('--version') ?? BUNDLES.at(-1) ?? PKG.version;
16
18
  const network = flag('--network') ?? 'any';
17
19
  const bundle = join(ROOT, 'workflows', version);
18
20
  const die = (m) => { console.error(m); process.exit(1); };
package/bin/run.mjs CHANGED
@@ -13,7 +13,7 @@
13
13
  // Keys come from the environment only: TYPESAFE_KEY (your own TypeSafe key, console.typesafe.ai) and
14
14
  // TAIFOON_RELAYER_KEY (records the answers on the layer). Neither is printed or written anywhere.
15
15
  // Output is @taifoon/term (vendored as ./term.mjs), the same rhythm as `taifoon up` and the STUDIO run log.
16
- import { readFileSync } from 'node:fs';
16
+ import { existsSync, readFileSync } from 'node:fs';
17
17
  import { createInterface } from 'node:readline/promises';
18
18
  import { createTerm } from './term.mjs';
19
19
 
@@ -35,18 +35,36 @@ function factOf(r) {
35
35
  }
36
36
  }
37
37
 
38
+ const USAGE = 'jev run [--demo | --job <chain>:<id> | --job-file job.json | --evidence pack.json] [--answers answers.json] [--record none|devnet|base|both] [--layer <https url> | --no-layer] [--protocol <name>] [--price-usdc <n>] [--yes] [--json]';
39
+ const PROTOCOLS = ['assurance-hook', 'judge-adapter', 'virtuals-erc8183', 'virtuals-memo-acp', 'bitagent-erc8183'];
40
+ /** A stranger's typo must never run something else: every flag is known, every value checked, exit 2 with the fix. */
41
+ function refuse(msg) { process.stderr.write(`jev run: ${msg}\n ${USAGE}\n`); process.exit(2); }
42
+
38
43
  export async function run(argv) {
39
44
  const a = [...argv];
40
45
  const has = (n) => { const i = a.indexOf(n); if (i < 0) return false; a.splice(i, 1); return true; };
41
- const val = (n) => { const i = a.indexOf(n); return i >= 0 ? a.splice(i, 2)[1] : undefined; };
46
+ const val = (n) => {
47
+ const i = a.indexOf(n); if (i < 0) return undefined;
48
+ const v = a[i + 1]; if (v === undefined || v.startsWith('--')) refuse(`${n} needs a value`);
49
+ a.splice(i, 2); return v;
50
+ };
51
+ const file = (n) => { const f = val(n); if (f !== undefined && !existsSync(f)) refuse(`${n}: no such file ${f}`); return f; };
42
52
  const yes = has('--yes') || !process.stdin.isTTY; const json = has('--json'); const noLayer = has('--no-layer') || argv.includes('--demo') || argv.includes('--job-file');
43
53
  const demo = has('--demo');
44
- const job = val('--job'); const evFile = val('--evidence');
45
- const jobFile = val('--job-file');
46
- const ansFile = val('--answers');
54
+ const job = val('--job'); const evFile = file('--evidence');
55
+ const jobFile = file('--job-file');
56
+ const ansFile = file('--answers');
47
57
  const rec = demo ? JSON.parse(readFileSync(new URL('../examples/jobs/base-bitagent-7287.recorded.json', import.meta.url), 'utf8')) : null;
48
58
  const network = val('--record') ?? val('--network') ?? 'none'; const layer = noLayer ? false : val('--layer');
49
59
  const protocol = val('--protocol'); const price = val('--price-usdc');
60
+ if (a.length) refuse(`unknown flag ${a[0]}`);
61
+ if (!['none', 'devnet', 'base', 'both'].includes(network)) refuse(`--record is none, devnet, base or both (got ${network})`);
62
+ if (demo && (job || evFile || jobFile || ansFile)) refuse('--demo replays one recorded job: drop --job, --job-file, --evidence and --answers');
63
+ if ([job, evFile, jobFile].filter(Boolean).length > 1) refuse('pick one source: --job, --job-file or --evidence');
64
+ if (job && !/^\d+:.+$/.test(job) && !/^\S+$/.test(job)) refuse(`--job is <chain>:<id>, e.g. 8453:bitagent:8453:7287 (got ${job})`);
65
+ if (layer && !/^https:\/\//.test(layer)) refuse('--layer must be an https:// URL');
66
+ if (protocol && !PROTOCOLS.includes(protocol)) refuse(`--protocol is one of ${PROTOCOLS.join(', ')}`);
67
+ if (price !== undefined && !(Number(price) > 0)) refuse(`--price-usdc must be a positive number (got ${price})`);
50
68
  const { pipeline, STEPS, prepareJob } = await import('../dist/index.js');
51
69
 
52
70
  const t = createTerm({ quiet: json });
package/dist/pipeline.js CHANGED
@@ -142,9 +142,15 @@ export async function pipeline(o = {}) {
142
142
  }
143
143
  else if (o.key)
144
144
  g.key = o.key;
145
- else
146
- throw new Error('Jev needs your TypeSafe key: set TYPESAFE_KEY (console.typesafe.ai), or pass answers you already have');
147
- t.receipt = await grade(g);
145
+ // no key: a job the code checks reject is still graded (Jev is not asked); only asking Jev needs the key
146
+ try {
147
+ t.receipt = await grade(g);
148
+ }
149
+ catch (e) {
150
+ if (!o.key && !o.answers && /TypeSafe key/.test(e.message))
151
+ throw new Error('Jev needs your TypeSafe key: set TYPESAFE_KEY (console.typesafe.ai), or pass answers you already have');
152
+ throw e;
153
+ }
148
154
  return { verdict: t.receipt.verdict, reasons: t.receipt.reasons, model: t.receipt.model, receiptHash: t.receipt.receiptHash };
149
155
  });
150
156
  if (failed() || !t.receipt)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taifoon/jev",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "Grade AI agent jobs with TypeSafe's Jev and record the verdict on chain: code checks the facts, Jev answers a published rubric, and you get a receipt, the on-chain record and the evaluator call for ERC-8183 and Virtuals ACP. No runtime dependencies, no key inside.",
5
5
  "keywords": [
6
6
  "jev",
@@ -60,7 +60,7 @@
60
60
  "build": "rm -rf dist && tsc -p tsconfig.build.json",
61
61
  "typecheck": "tsc --noEmit -p .",
62
62
  "test": "vitest run",
63
- "check": "npm run typecheck && npm test && npm run build",
63
+ "check": "npm run typecheck && npm run build && npm test",
64
64
  "prepublishOnly": "npm run check",
65
65
  "bundle": "node scripts/bundle-workflows.mjs",
66
66
  "zip": "node scripts/zip-workflows.mjs"