plures 1.14.0 → 1.16.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/README.md CHANGED
@@ -44,10 +44,12 @@ if (event.type === "task.completed" && event.data.result?.agreement === 1) {
44
44
 
45
45
  ## API
46
46
 
47
+ - `plures.check(id, data, overrides?)`: run a check from your app's map, one line before the action it guards: `const check = await plures.check("refunds", { order_total, reason }); if (check.answer !== "Approve") return`. The check saved on Plures supplies the question, options, which fields people see (the rest come back in `kept_private`), who decides, how often, its conditions and what happens on a timeout, so you change them on Plures, not in code. `checked` says whether a person looked; `skipped_because` says why not. Overrides: `question`, `options`, `verifiers`, `team`, `route`, `sample`, `timeout`, `always`.
48
+ - `npx plures check add <id> --app <app>`: Claude Code puts that line before the action on a new branch. On GitHub (the dashboard's **Add it to my code**) Plures then opens a pull request.
47
49
  - `plures.tasks.create(params, { idempotencyKey? })`: send a task. Params: `question`, `options` (2 to 8, strings or `{ id, label }`), `context`, `image_url`, `audio_url` or `video_url` with `media_seconds` (a clip up to 180 seconds that people play through before answering), `sensitive`, `instructions`, `verifiers` (1, 3, 5, 7 or 9), `max_verifiers`, `presence_check` (default true), `webhook_url`, `metadata`, `expires_in`, `route` (`"team"` sends it straight to your own people, $0.05 per internal vote live), `category` (matches your routing rules) and `team` (one of your named teams), `team_votes` and `team_unanimous` (internal consensus: how many of your team vote, and whether they must all agree; a split goes to the team's lead).
48
50
  - `plures.tasks.retrieve(id)`, `plures.tasks.list({ status?, limit?, starting_after?, app? })`, `plures.tasks.cancel(id)`
49
51
  - `new Plures(key, { app: "support-agent" })` or `PLURES_APP`: send everything to one of your apps. `plures.apps.list()`, `.create({ name, id? })`, `.retrieve(id)`, `.update(id, { name?, id?, default? })`, `.del(id)`. Each app keeps its own tasks, ledger view, map and quality.
50
- - `plures.maps.guide()`, `.retrieve(app)`, `.upload(app, map)`, `.setCheckpoint(app, id, status)`: the agent map, where your agent needs a person. Or run `npx plures map` in your repo: your own coding agent reads the code and only the map is sent.
52
+ - `plures.maps.guide()`, `.retrieve(app)`, `.upload(app, map, { branch? })` (a branch other than main gets its own map, compared with main and noted on its pull request), `.setCheckpoint(app, id, status)`: the agent map, where your agent needs a person. Or run `npx plures map` in your repo: your own coding agent reads the code and only the map is sent.
51
53
  - `plures.maps.createKey(app, { label? })`, `.keys(app)`, `.revokeKey(app, id)`: map keys for CI, which can only read and upload that app's map. `.workflow(app, branch?)` returns the GitHub Action that maps on every push; `npx plures map workflow` writes it to `.github/workflows/plures-map.yml`. Or use **Map with GitHub** on the app's map page and Plures opens the pull request for you, then **Turn on mapping** merges it. `plures.maps.startRun(app)` and `.runs(app)`: in CI with no Claude key, `npx plures map` starts a run where Plures runs Claude (first map free, then Claude's cost plus 30%, at most $5).
52
54
  - In your terminal, `npx plures map` opens your map page so you can watch it build (`--no-open` to skip), keeps one live line of what it's reading, and ends by drawing the map's flows with the checks Plures suggests. On GitHub and other CI it prints a plain log.
53
55
  - `npx plures map` shows each file as Claude Code reads it and sends the file names (never their contents) to Plures, so the map page shows the reading live and the map builds itself when it lands. `plures.maps.progress(app, { session, phase, events })` is the call behind it.
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- var VERSION = "1.14.0";
2
+ var VERSION = "1.16.0";
3
3
  var PluresError = class extends Error {
4
4
  constructor(message, status, type) {
5
5
  super(message);
@@ -100,7 +100,8 @@ var Plures = class {
100
100
  },
101
101
  retrieve: (app) => this.req("GET", `/api/v1/apps/${encodeURIComponent(app)}/map`),
102
102
  /** Uploads a new version. Checks you already added or dismissed keep that. */
103
- upload: (app, map) => this.req("PUT", `/api/v1/apps/${encodeURIComponent(app)}/map`, map),
103
+ /** A branch other than the main one gets its own map, compared with main, and noted on its pull request. */
104
+ upload: (app, map, opts = {}) => this.req("PUT", `/api/v1/apps/${encodeURIComponent(app)}/map`, map, opts.branch ? { "plures-branch": opts.branch } : {}),
104
105
  setCheckpoint: (app, id, status) => this.req("PATCH", `/api/v1/apps/${encodeURIComponent(app)}/map/checkpoints/${encodeURIComponent(id)}`, { status }),
105
106
  /** A key that can only read and upload this app's map, for CI. The key is in the response once. */
106
107
  createKey: (app, opts = {}) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/map_keys`, opts),
@@ -111,6 +112,8 @@ var Plures = class {
111
112
  runs: (app) => this.req("GET", `/api/v1/apps/${encodeURIComponent(app)}/map_runs`),
112
113
  /** While mapping, which files the coding agent is reading, shown live on the map page. File names only. npx plures map does this. */
113
114
  progress: (app, p) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/map/progress`, p),
115
+ /** After npx plures check add pushed a branch with the check in it: Plures opens the pull request. Pass error instead if adding it failed. */
116
+ checkPr: (app, id, p) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/checks/${encodeURIComponent(id)}/pr`, p),
114
117
  /** The GitHub Actions workflow that maps a repo on every push to its main branch. */
115
118
  workflow: async (app, branch = "main") => {
116
119
  const r = await fetch(`${this.base}/api/v1/map_workflow?app=${encodeURIComponent(app)}&branch=${encodeURIComponent(branch)}`);
@@ -168,6 +171,32 @@ var Plures = class {
168
171
  };
169
172
  /** Your prepaid credit. A live task that could cost more than available_cents is refused with a 402 insufficient_credits error. */
170
173
  balance = () => this.req("GET", "/api/v1/balance");
174
+ /**
175
+ * Runs a check from your app's map and waits for the decision. Send the data by name; the check
176
+ * saved on Plures supplies the question, options, guidance, which fields people see (the rest
177
+ * never leave Plures), who decides, how often and what happens on a timeout. Any of those can be
178
+ * overridden for this call. `answer` is the option to act on: what people chose, the first option
179
+ * when the check wasn't needed (a sample or a condition) or timed out set to go ahead, and null
180
+ * when it timed out set to hold.
181
+ */
182
+ async check(id, data = {}, overrides = {}, opts = {}) {
183
+ const t = await this.req("POST", `/api/v1/checks/${encodeURIComponent(id)}`, { data, ...overrides }, opts.idempotencyKey ? { "idempotency-key": opts.idempotencyKey } : {});
184
+ const final = t.status === "pending" ? await this.tasks.waitFor(t.id, { timeoutMs: opts.timeoutMs }) : t;
185
+ const opt = (k) => final.options.find((o) => o.id === k)?.label ?? k ?? null;
186
+ const first = final.options[0]?.label ?? null;
187
+ const timedOut = final.status === "expired" || final.status === "pending";
188
+ const answer = final.status === "completed" ? opt(final.result?.answer) : final.status === "skipped" ? first : timedOut && t.on_timeout === "proceed" ? first : null;
189
+ return {
190
+ ...final,
191
+ check: t.check,
192
+ on_timeout: t.on_timeout,
193
+ kept_private: t.kept_private ?? [],
194
+ skipped_because: t.skipped_because ?? null,
195
+ answer,
196
+ checked: final.status !== "skipped",
197
+ timed_out: timedOut
198
+ };
199
+ }
171
200
  /** Creates a task and waits for people to answer it. */
172
201
  async verify(params, opts = {}) {
173
202
  const t = await this.tasks.create(params, { idempotencyKey: opts.idempotencyKey });
package/dist/cli.cjs CHANGED
@@ -30,7 +30,7 @@ var import_node_path = require("path");
30
30
  var import_node_fs = require("fs");
31
31
 
32
32
  // src/index.ts
33
- var VERSION = "1.14.0";
33
+ var VERSION = "1.16.0";
34
34
  var PluresError = class extends Error {
35
35
  constructor(message, status, type) {
36
36
  super(message);
@@ -131,7 +131,8 @@ var Plures = class {
131
131
  },
132
132
  retrieve: (app2) => this.req("GET", `/api/v1/apps/${encodeURIComponent(app2)}/map`),
133
133
  /** Uploads a new version. Checks you already added or dismissed keep that. */
134
- upload: (app2, map2) => this.req("PUT", `/api/v1/apps/${encodeURIComponent(app2)}/map`, map2),
134
+ /** A branch other than the main one gets its own map, compared with main, and noted on its pull request. */
135
+ upload: (app2, map2, opts = {}) => this.req("PUT", `/api/v1/apps/${encodeURIComponent(app2)}/map`, map2, opts.branch ? { "plures-branch": opts.branch } : {}),
135
136
  setCheckpoint: (app2, id, status) => this.req("PATCH", `/api/v1/apps/${encodeURIComponent(app2)}/map/checkpoints/${encodeURIComponent(id)}`, { status }),
136
137
  /** A key that can only read and upload this app's map, for CI. The key is in the response once. */
137
138
  createKey: (app2, opts = {}) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app2)}/map_keys`, opts),
@@ -142,6 +143,8 @@ var Plures = class {
142
143
  runs: (app2) => this.req("GET", `/api/v1/apps/${encodeURIComponent(app2)}/map_runs`),
143
144
  /** While mapping, which files the coding agent is reading, shown live on the map page. File names only. npx plures map does this. */
144
145
  progress: (app2, p) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app2)}/map/progress`, p),
146
+ /** After npx plures check add pushed a branch with the check in it: Plures opens the pull request. Pass error instead if adding it failed. */
147
+ checkPr: (app2, id, p) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app2)}/checks/${encodeURIComponent(id)}/pr`, p),
145
148
  /** The GitHub Actions workflow that maps a repo on every push to its main branch. */
146
149
  workflow: async (app2, branch2 = "main") => {
147
150
  const r = await fetch(`${this.base}/api/v1/map_workflow?app=${encodeURIComponent(app2)}&branch=${encodeURIComponent(branch2)}`);
@@ -199,6 +202,32 @@ var Plures = class {
199
202
  };
200
203
  /** Your prepaid credit. A live task that could cost more than available_cents is refused with a 402 insufficient_credits error. */
201
204
  balance = () => this.req("GET", "/api/v1/balance");
205
+ /**
206
+ * Runs a check from your app's map and waits for the decision. Send the data by name; the check
207
+ * saved on Plures supplies the question, options, guidance, which fields people see (the rest
208
+ * never leave Plures), who decides, how often and what happens on a timeout. Any of those can be
209
+ * overridden for this call. `answer` is the option to act on: what people chose, the first option
210
+ * when the check wasn't needed (a sample or a condition) or timed out set to go ahead, and null
211
+ * when it timed out set to hold.
212
+ */
213
+ async check(id, data = {}, overrides = {}, opts = {}) {
214
+ const t = await this.req("POST", `/api/v1/checks/${encodeURIComponent(id)}`, { data, ...overrides }, opts.idempotencyKey ? { "idempotency-key": opts.idempotencyKey } : {});
215
+ const final = t.status === "pending" ? await this.tasks.waitFor(t.id, { timeoutMs: opts.timeoutMs }) : t;
216
+ const opt = (k) => final.options.find((o) => o.id === k)?.label ?? k ?? null;
217
+ const first = final.options[0]?.label ?? null;
218
+ const timedOut = final.status === "expired" || final.status === "pending";
219
+ const answer = final.status === "completed" ? opt(final.result?.answer) : final.status === "skipped" ? first : timedOut && t.on_timeout === "proceed" ? first : null;
220
+ return {
221
+ ...final,
222
+ check: t.check,
223
+ on_timeout: t.on_timeout,
224
+ kept_private: t.kept_private ?? [],
225
+ skipped_because: t.skipped_because ?? null,
226
+ answer,
227
+ checked: final.status !== "skipped",
228
+ timed_out: timedOut
229
+ };
230
+ }
202
231
  /** Creates a task and waits for people to answer it. */
203
232
  async verify(params, opts = {}) {
204
233
  const t = await this.tasks.create(params, { idempotencyKey: opts.idempotencyKey });
@@ -327,7 +356,9 @@ function drawMap(m, url) {
327
356
  const chip = (id) => {
328
357
  const n = nodes.get(id), c2 = cps.get(id);
329
358
  const label = cut(n.label, 34);
330
- const tag = !c2 ? "" : c2.status === "added" ? ` ${C.cyan("\u2713 checked")}` : ` ${C.violet("\u25CC check suggested")}`;
359
+ const rate = c2?.draft?.sample;
360
+ const often = rate ? `, 1 in ${Math.round(1 / rate)}` : "";
361
+ const tag = !c2 ? "" : c2.status === "added" ? ` ${C.cyan(`\u2713 checked${often}`)}` : ` ${C.violet(`\u25CC check suggested${often}`)}`;
331
362
  if (n.kind === "action") {
332
363
  if (c2?.status === "added") return `${C.cyan(ICON.action)} ${C.text(label)}${tag}`;
333
364
  const risky = n.risk === "high" || n.risk === "medium";
@@ -379,6 +410,9 @@ function drawMap(m, url) {
379
410
  const on = actions.filter((a) => cps.get(a.id)?.status === "added").length;
380
411
  const sugg = m.checkpoints.filter((c2) => c2.status === "suggested").length;
381
412
  out.write(` ${LANES.map((c2) => c2("\u2501")).join("")} ${C.bold(C.text(sugg ? `Plures found ${sugg} ${sugg === 1 ? "place" : "places"} a person should check.` : "Your map is ready."))}
413
+ `);
414
+ const scored = m.engine?.version;
415
+ if (scored) out.write(` ${C.dim(`Scored by the Plures oversight engine ${scored}: how risky each action is, and how often people check it.`)}
382
416
  `);
383
417
  out.write(` ${C.mute(`${on} of ${actions.length} ${actions.length === 1 ? "action" : "actions"} with consequences ${on === 1 ? "has" : "have"} a human check.`)}
384
418
 
@@ -441,11 +475,19 @@ async function upload(file) {
441
475
  }
442
476
  if (!fancy) console.log(dim(`Sending the map to Plures. No source code is sent.`));
443
477
  let m;
478
+ const gitBranch = (0, import_node_child_process.spawnSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf8" }).stdout?.trim();
479
+ const branchName = process.env.PLURES_BRANCH || process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || (gitBranch && gitBranch !== "HEAD" ? gitBranch : "");
444
480
  try {
445
- m = await client().maps.upload(app, map2);
481
+ m = await client().maps.upload(app, map2, branchName ? { branch: branchName } : {});
446
482
  } catch (e) {
447
483
  die(e instanceof PluresError ? `Plures said: ${e.message}` : String(e));
448
484
  }
485
+ if (m.branch) console.log(`${bold(`Branch ${m.branch}`)}: compared with the main map${m.main_version ? ` (version ${m.main_version})` : ""}, not replacing it.${m.pull_request ? ` Noted on ${m.pull_request}` : ""}`);
486
+ if (m.changes && (m.changes.added.length || m.changes.removed.length || m.changes.renamed.length || m.changes.risky.length)) {
487
+ const c2 = m.changes;
488
+ console.log(`${dim("Changes:")} ${[c2.added.length ? `${c2.added.length} new` : "", c2.renamed.length ? `${c2.renamed.length} renamed, checks kept` : "", c2.removed.length ? `${c2.removed.length} removed` : ""].filter(Boolean).join(", ") || "no new actions"}`);
489
+ for (const r of c2.risky) console.log(` ${coral("no check")} ${r.label} ${dim(`score ${r.score}: ${r.why.join("; ")}`)}`);
490
+ }
449
491
  if (fancy) drawMap(m, m.url ?? `${base}/dashboard/apps/${m.app}/map`);
450
492
  else report(m);
451
493
  }
@@ -499,7 +541,7 @@ With plures-mcp connected, just ask: ${cyan("Map this app for Plures")}`);
499
541
  }
500
542
  const pageUrl = `${base}/dashboard/apps/${encodeURIComponent(app)}/map`;
501
543
  if (fancy) {
502
- banner(app, "Reading this repo on your machine with Claude Code. Only file names and the map go to Plures.");
544
+ banner(app, "Claude Code reads this repo on your machine for the Plures oversight engine. Only file names and the map go to Plures.");
503
545
  if (key && !noOpen && await openBrowser(pageUrl)) console.log(` ${dim("Watch it live in your browser:")} ${cyan(pageUrl)}
504
546
  `);
505
547
  } else console.log(`Reading this repo ${process.env.CI ? "on this runner" : "on your machine"} with Claude Code. ${dim("Only the map is sent to Plures.")}
@@ -664,8 +706,87 @@ It adds one workflow file. Review it, then turn mapping on with ${cyan("npx plur
664
706
  throw e;
665
707
  }
666
708
  }
709
+ async function checkAdd(id) {
710
+ if (!id) die("npx plures check add <check id> [--app <app>]");
711
+ const cl = client();
712
+ const report2 = async (p) => {
713
+ if (process.env.GITHUB_ACTIONS) await cl.maps.checkPr(app, id, p).catch(() => {
714
+ });
715
+ };
716
+ const fail = async (msg) => {
717
+ await report2({ error: msg });
718
+ die(msg);
719
+ };
720
+ let m;
721
+ try {
722
+ m = await cl.maps.retrieve(app);
723
+ } catch (e) {
724
+ return fail(e instanceof PluresError ? e.message : String(e));
725
+ }
726
+ const cp0 = m.checkpoints.find((x) => x.id === id || x.category === id);
727
+ const cp = cp0;
728
+ if (!cp0) return fail(`No check called ${id} on ${app}'s map.`);
729
+ const action = m.nodes.find((n) => n.id === cp.at);
730
+ const cid = cp.id ?? id;
731
+ if (!cp.code) return fail(`The check ${cid} has no drafted question yet. Edit it on Plures first.`);
732
+ if (!hasClaude()) return fail("Claude Code isn't installed here, so nothing could add the line. Copy it from the check's page instead.");
733
+ const git = (...a) => (0, import_node_child_process.spawnSync)("git", a, { encoding: "utf8" });
734
+ if (git("status", "--porcelain").stdout.trim()) return fail("This repo has changes that aren't committed. Commit or stash them first.");
735
+ const childEnv = { ...process.env };
736
+ const ownClaude = Boolean(process.env.ANTHROPIC_API_KEY || process.env.CLAUDE_CODE_OAUTH_TOKEN || process.env.ANTHROPIC_AUTH_TOKEN);
737
+ if (!ownClaude && process.env.CI) {
738
+ try {
739
+ const run = await cl.maps.startRun(app);
740
+ childEnv.ANTHROPIC_BASE_URL = run.base_url;
741
+ childEnv.ANTHROPIC_API_KEY = run.token;
742
+ } catch (e) {
743
+ return fail(e instanceof PluresError ? e.message : String(e));
744
+ }
745
+ }
746
+ const where = action?.file ? `${action.file}${action.line ? ` near line ${action.line}` : ""}` : "the code";
747
+ console.log(`Adding the check ${bold(cid)} before ${bold(action?.label ?? cp.at)}, in ${where}.`);
748
+ const prompt = `Add one Plures human check to this codebase. Change as little as possible.
749
+
750
+ Where: just before the step "${action?.label ?? cp.at}"${action?.via ? ` (it goes through ${action.via})` : ""}, in ${where}. Find the exact place; the line may have moved.
751
+
752
+ What to add, adapted to the file's language and style (for Python: \`check = plures.check("${cid}", fields)\` and \`check.answer\`):
753
+
754
+ ${cp.code}
755
+
756
+ - fields: an object with the values a person needs to decide, taken from variables already in scope at that point${cp.draft?.show?.length ? `. Use these field names: ${cp.draft.show.join(", ")}` : ""}. Never pass secrets, card numbers or passwords.
757
+ - If the answer isn't the first option, the agent must not do the step: return, skip, or raise, whichever fits the code around it.
758
+ - If the file has no Plures client yet, add one: JavaScript or TypeScript \`import { Plures } from "plures"\` and \`const plures = new Plures(process.env.PLURES_API_KEY${app !== "default" ? `, { app: "${app}" }` : ""})\`; Python \`from plures import Plures\` and \`plures = Plures(os.environ["PLURES_API_KEY"]${app !== "default" ? `, app="${app}"` : ""})\`. Keep to how the project sets up other clients.
759
+ - If the package isn't a dependency yet, add "plures" to package.json dependencies, or plures to requirements.txt or pyproject.toml, whichever the project uses. Don't run installers.
760
+ - Don't change anything else, don't reformat, don't add tests, and don't run commands.`;
761
+ const code = await new Promise((resolve) => {
762
+ const p = (0, import_node_child_process.spawn)("claude", ["-p", prompt, "--allowedTools", "Read,Grep,Glob,Edit,Write"], { stdio: ["ignore", "inherit", "inherit"], env: childEnv });
763
+ p.on("close", (n) => resolve(n ?? 1));
764
+ p.on("error", () => resolve(1));
765
+ });
766
+ const changed = git("status", "--porcelain").stdout.trim();
767
+ if (code !== 0 || !changed) return fail("Claude Code didn't change any files, so there's nothing to review. Copy the line from the check's page instead.");
768
+ const br = `plures/check-${cid.replace(/[^a-z0-9_-]+/gi, "-")}-${Date.now().toString(36)}`;
769
+ for (const a of [["checkout", "-b", br], ["add", "-A"], ["commit", "-m", `Add the Plures check ${cid} before ${(action?.label ?? cp.at).toLowerCase()}`]]) {
770
+ const r = git(...a);
771
+ if (r.status !== 0) return fail(`git ${a[0]} failed: ${(r.stderr || r.stdout).trim().slice(0, 200)}`);
772
+ }
773
+ if (!process.env.GITHUB_ACTIONS) {
774
+ console.log(`
775
+ ${cyan("\u2713")} The check is on the branch ${bold(br)}. Review it with ${cyan("git show")}, then push it and open a pull request.`);
776
+ return;
777
+ }
778
+ const push = git("push", "-u", "origin", br);
779
+ if (push.status !== 0) return fail(`Pushing the branch failed: ${(push.stderr || push.stdout).trim().slice(0, 200)}`);
780
+ try {
781
+ const r = await cl.maps.checkPr(app, cid, { branch: br });
782
+ console.log(`${cyan("\u2713")} Pull request: ${r.pull_request ?? "opened"}`);
783
+ } catch (e) {
784
+ die(e instanceof PluresError ? e.message : String(e));
785
+ }
786
+ }
667
787
  async function main() {
668
788
  const [cmd, sub, arg] = argv;
789
+ if (cmd === "check" && sub === "add") return checkAdd(arg);
669
790
  if (cmd !== "map") {
670
791
  console.log(`Plures
671
792
 
@@ -676,6 +797,7 @@ async function main() {
676
797
  npx plures map github connect let Plures add that Action by pull request
677
798
  npx plures map github on merge it, which turns mapping on
678
799
  npx plures map github run map now on GitHub, and watch it build
800
+ npx plures check add <id> put a check into your code, on a new branch
679
801
 
680
802
  Docs: https://plures.si/docs`);
681
803
  return;
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  Plures,
4
4
  PluresError,
5
5
  VERSION
6
- } from "./chunk-7OMKME5Z.js";
6
+ } from "./chunk-Z75ZTXHZ.js";
7
7
 
8
8
  // src/cli.ts
9
9
  import { spawnSync, spawn } from "child_process";
@@ -109,7 +109,9 @@ function drawMap(m, url) {
109
109
  const chip = (id) => {
110
110
  const n = nodes.get(id), c2 = cps.get(id);
111
111
  const label = cut(n.label, 34);
112
- const tag = !c2 ? "" : c2.status === "added" ? ` ${C.cyan("\u2713 checked")}` : ` ${C.violet("\u25CC check suggested")}`;
112
+ const rate = c2?.draft?.sample;
113
+ const often = rate ? `, 1 in ${Math.round(1 / rate)}` : "";
114
+ const tag = !c2 ? "" : c2.status === "added" ? ` ${C.cyan(`\u2713 checked${often}`)}` : ` ${C.violet(`\u25CC check suggested${often}`)}`;
113
115
  if (n.kind === "action") {
114
116
  if (c2?.status === "added") return `${C.cyan(ICON.action)} ${C.text(label)}${tag}`;
115
117
  const risky = n.risk === "high" || n.risk === "medium";
@@ -161,6 +163,9 @@ function drawMap(m, url) {
161
163
  const on = actions.filter((a) => cps.get(a.id)?.status === "added").length;
162
164
  const sugg = m.checkpoints.filter((c2) => c2.status === "suggested").length;
163
165
  out.write(` ${LANES.map((c2) => c2("\u2501")).join("")} ${C.bold(C.text(sugg ? `Plures found ${sugg} ${sugg === 1 ? "place" : "places"} a person should check.` : "Your map is ready."))}
166
+ `);
167
+ const scored = m.engine?.version;
168
+ if (scored) out.write(` ${C.dim(`Scored by the Plures oversight engine ${scored}: how risky each action is, and how often people check it.`)}
164
169
  `);
165
170
  out.write(` ${C.mute(`${on} of ${actions.length} ${actions.length === 1 ? "action" : "actions"} with consequences ${on === 1 ? "has" : "have"} a human check.`)}
166
171
 
@@ -223,11 +228,19 @@ async function upload(file) {
223
228
  }
224
229
  if (!fancy) console.log(dim(`Sending the map to Plures. No source code is sent.`));
225
230
  let m;
231
+ const gitBranch = spawnSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], { encoding: "utf8" }).stdout?.trim();
232
+ const branchName = process.env.PLURES_BRANCH || process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME || (gitBranch && gitBranch !== "HEAD" ? gitBranch : "");
226
233
  try {
227
- m = await client().maps.upload(app, map2);
234
+ m = await client().maps.upload(app, map2, branchName ? { branch: branchName } : {});
228
235
  } catch (e) {
229
236
  die(e instanceof PluresError ? `Plures said: ${e.message}` : String(e));
230
237
  }
238
+ if (m.branch) console.log(`${bold(`Branch ${m.branch}`)}: compared with the main map${m.main_version ? ` (version ${m.main_version})` : ""}, not replacing it.${m.pull_request ? ` Noted on ${m.pull_request}` : ""}`);
239
+ if (m.changes && (m.changes.added.length || m.changes.removed.length || m.changes.renamed.length || m.changes.risky.length)) {
240
+ const c2 = m.changes;
241
+ console.log(`${dim("Changes:")} ${[c2.added.length ? `${c2.added.length} new` : "", c2.renamed.length ? `${c2.renamed.length} renamed, checks kept` : "", c2.removed.length ? `${c2.removed.length} removed` : ""].filter(Boolean).join(", ") || "no new actions"}`);
242
+ for (const r of c2.risky) console.log(` ${coral("no check")} ${r.label} ${dim(`score ${r.score}: ${r.why.join("; ")}`)}`);
243
+ }
231
244
  if (fancy) drawMap(m, m.url ?? `${base}/dashboard/apps/${m.app}/map`);
232
245
  else report(m);
233
246
  }
@@ -281,7 +294,7 @@ With plures-mcp connected, just ask: ${cyan("Map this app for Plures")}`);
281
294
  }
282
295
  const pageUrl = `${base}/dashboard/apps/${encodeURIComponent(app)}/map`;
283
296
  if (fancy) {
284
- banner(app, "Reading this repo on your machine with Claude Code. Only file names and the map go to Plures.");
297
+ banner(app, "Claude Code reads this repo on your machine for the Plures oversight engine. Only file names and the map go to Plures.");
285
298
  if (key && !noOpen && await openBrowser(pageUrl)) console.log(` ${dim("Watch it live in your browser:")} ${cyan(pageUrl)}
286
299
  `);
287
300
  } else console.log(`Reading this repo ${process.env.CI ? "on this runner" : "on your machine"} with Claude Code. ${dim("Only the map is sent to Plures.")}
@@ -446,8 +459,87 @@ It adds one workflow file. Review it, then turn mapping on with ${cyan("npx plur
446
459
  throw e;
447
460
  }
448
461
  }
462
+ async function checkAdd(id) {
463
+ if (!id) die("npx plures check add <check id> [--app <app>]");
464
+ const cl = client();
465
+ const report2 = async (p) => {
466
+ if (process.env.GITHUB_ACTIONS) await cl.maps.checkPr(app, id, p).catch(() => {
467
+ });
468
+ };
469
+ const fail = async (msg) => {
470
+ await report2({ error: msg });
471
+ die(msg);
472
+ };
473
+ let m;
474
+ try {
475
+ m = await cl.maps.retrieve(app);
476
+ } catch (e) {
477
+ return fail(e instanceof PluresError ? e.message : String(e));
478
+ }
479
+ const cp0 = m.checkpoints.find((x) => x.id === id || x.category === id);
480
+ const cp = cp0;
481
+ if (!cp0) return fail(`No check called ${id} on ${app}'s map.`);
482
+ const action = m.nodes.find((n) => n.id === cp.at);
483
+ const cid = cp.id ?? id;
484
+ if (!cp.code) return fail(`The check ${cid} has no drafted question yet. Edit it on Plures first.`);
485
+ if (!hasClaude()) return fail("Claude Code isn't installed here, so nothing could add the line. Copy it from the check's page instead.");
486
+ const git = (...a) => spawnSync("git", a, { encoding: "utf8" });
487
+ if (git("status", "--porcelain").stdout.trim()) return fail("This repo has changes that aren't committed. Commit or stash them first.");
488
+ const childEnv = { ...process.env };
489
+ const ownClaude = Boolean(process.env.ANTHROPIC_API_KEY || process.env.CLAUDE_CODE_OAUTH_TOKEN || process.env.ANTHROPIC_AUTH_TOKEN);
490
+ if (!ownClaude && process.env.CI) {
491
+ try {
492
+ const run = await cl.maps.startRun(app);
493
+ childEnv.ANTHROPIC_BASE_URL = run.base_url;
494
+ childEnv.ANTHROPIC_API_KEY = run.token;
495
+ } catch (e) {
496
+ return fail(e instanceof PluresError ? e.message : String(e));
497
+ }
498
+ }
499
+ const where = action?.file ? `${action.file}${action.line ? ` near line ${action.line}` : ""}` : "the code";
500
+ console.log(`Adding the check ${bold(cid)} before ${bold(action?.label ?? cp.at)}, in ${where}.`);
501
+ const prompt = `Add one Plures human check to this codebase. Change as little as possible.
502
+
503
+ Where: just before the step "${action?.label ?? cp.at}"${action?.via ? ` (it goes through ${action.via})` : ""}, in ${where}. Find the exact place; the line may have moved.
504
+
505
+ What to add, adapted to the file's language and style (for Python: \`check = plures.check("${cid}", fields)\` and \`check.answer\`):
506
+
507
+ ${cp.code}
508
+
509
+ - fields: an object with the values a person needs to decide, taken from variables already in scope at that point${cp.draft?.show?.length ? `. Use these field names: ${cp.draft.show.join(", ")}` : ""}. Never pass secrets, card numbers or passwords.
510
+ - If the answer isn't the first option, the agent must not do the step: return, skip, or raise, whichever fits the code around it.
511
+ - If the file has no Plures client yet, add one: JavaScript or TypeScript \`import { Plures } from "plures"\` and \`const plures = new Plures(process.env.PLURES_API_KEY${app !== "default" ? `, { app: "${app}" }` : ""})\`; Python \`from plures import Plures\` and \`plures = Plures(os.environ["PLURES_API_KEY"]${app !== "default" ? `, app="${app}"` : ""})\`. Keep to how the project sets up other clients.
512
+ - If the package isn't a dependency yet, add "plures" to package.json dependencies, or plures to requirements.txt or pyproject.toml, whichever the project uses. Don't run installers.
513
+ - Don't change anything else, don't reformat, don't add tests, and don't run commands.`;
514
+ const code = await new Promise((resolve) => {
515
+ const p = spawn("claude", ["-p", prompt, "--allowedTools", "Read,Grep,Glob,Edit,Write"], { stdio: ["ignore", "inherit", "inherit"], env: childEnv });
516
+ p.on("close", (n) => resolve(n ?? 1));
517
+ p.on("error", () => resolve(1));
518
+ });
519
+ const changed = git("status", "--porcelain").stdout.trim();
520
+ if (code !== 0 || !changed) return fail("Claude Code didn't change any files, so there's nothing to review. Copy the line from the check's page instead.");
521
+ const br = `plures/check-${cid.replace(/[^a-z0-9_-]+/gi, "-")}-${Date.now().toString(36)}`;
522
+ for (const a of [["checkout", "-b", br], ["add", "-A"], ["commit", "-m", `Add the Plures check ${cid} before ${(action?.label ?? cp.at).toLowerCase()}`]]) {
523
+ const r = git(...a);
524
+ if (r.status !== 0) return fail(`git ${a[0]} failed: ${(r.stderr || r.stdout).trim().slice(0, 200)}`);
525
+ }
526
+ if (!process.env.GITHUB_ACTIONS) {
527
+ console.log(`
528
+ ${cyan("\u2713")} The check is on the branch ${bold(br)}. Review it with ${cyan("git show")}, then push it and open a pull request.`);
529
+ return;
530
+ }
531
+ const push = git("push", "-u", "origin", br);
532
+ if (push.status !== 0) return fail(`Pushing the branch failed: ${(push.stderr || push.stdout).trim().slice(0, 200)}`);
533
+ try {
534
+ const r = await cl.maps.checkPr(app, cid, { branch: br });
535
+ console.log(`${cyan("\u2713")} Pull request: ${r.pull_request ?? "opened"}`);
536
+ } catch (e) {
537
+ die(e instanceof PluresError ? e.message : String(e));
538
+ }
539
+ }
449
540
  async function main() {
450
541
  const [cmd, sub, arg] = argv;
542
+ if (cmd === "check" && sub === "add") return checkAdd(arg);
451
543
  if (cmd !== "map") {
452
544
  console.log(`Plures
453
545
 
@@ -458,6 +550,7 @@ async function main() {
458
550
  npx plures map github connect let Plures add that Action by pull request
459
551
  npx plures map github on merge it, which turns mapping on
460
552
  npx plures map github run map now on GitHub, and watch it build
553
+ npx plures check add <id> put a check into your code, on a new branch
461
554
 
462
555
  Docs: https://plures.si/docs`);
463
556
  return;
package/dist/index.cjs CHANGED
@@ -26,7 +26,7 @@ __export(index_exports, {
26
26
  default: () => index_default
27
27
  });
28
28
  module.exports = __toCommonJS(index_exports);
29
- var VERSION = "1.14.0";
29
+ var VERSION = "1.16.0";
30
30
  var PluresError = class extends Error {
31
31
  constructor(message, status, type) {
32
32
  super(message);
@@ -127,7 +127,8 @@ var Plures = class {
127
127
  },
128
128
  retrieve: (app) => this.req("GET", `/api/v1/apps/${encodeURIComponent(app)}/map`),
129
129
  /** Uploads a new version. Checks you already added or dismissed keep that. */
130
- upload: (app, map) => this.req("PUT", `/api/v1/apps/${encodeURIComponent(app)}/map`, map),
130
+ /** A branch other than the main one gets its own map, compared with main, and noted on its pull request. */
131
+ upload: (app, map, opts = {}) => this.req("PUT", `/api/v1/apps/${encodeURIComponent(app)}/map`, map, opts.branch ? { "plures-branch": opts.branch } : {}),
131
132
  setCheckpoint: (app, id, status) => this.req("PATCH", `/api/v1/apps/${encodeURIComponent(app)}/map/checkpoints/${encodeURIComponent(id)}`, { status }),
132
133
  /** A key that can only read and upload this app's map, for CI. The key is in the response once. */
133
134
  createKey: (app, opts = {}) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/map_keys`, opts),
@@ -138,6 +139,8 @@ var Plures = class {
138
139
  runs: (app) => this.req("GET", `/api/v1/apps/${encodeURIComponent(app)}/map_runs`),
139
140
  /** While mapping, which files the coding agent is reading, shown live on the map page. File names only. npx plures map does this. */
140
141
  progress: (app, p) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/map/progress`, p),
142
+ /** After npx plures check add pushed a branch with the check in it: Plures opens the pull request. Pass error instead if adding it failed. */
143
+ checkPr: (app, id, p) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/checks/${encodeURIComponent(id)}/pr`, p),
141
144
  /** The GitHub Actions workflow that maps a repo on every push to its main branch. */
142
145
  workflow: async (app, branch = "main") => {
143
146
  const r = await fetch(`${this.base}/api/v1/map_workflow?app=${encodeURIComponent(app)}&branch=${encodeURIComponent(branch)}`);
@@ -195,6 +198,32 @@ var Plures = class {
195
198
  };
196
199
  /** Your prepaid credit. A live task that could cost more than available_cents is refused with a 402 insufficient_credits error. */
197
200
  balance = () => this.req("GET", "/api/v1/balance");
201
+ /**
202
+ * Runs a check from your app's map and waits for the decision. Send the data by name; the check
203
+ * saved on Plures supplies the question, options, guidance, which fields people see (the rest
204
+ * never leave Plures), who decides, how often and what happens on a timeout. Any of those can be
205
+ * overridden for this call. `answer` is the option to act on: what people chose, the first option
206
+ * when the check wasn't needed (a sample or a condition) or timed out set to go ahead, and null
207
+ * when it timed out set to hold.
208
+ */
209
+ async check(id, data = {}, overrides = {}, opts = {}) {
210
+ const t = await this.req("POST", `/api/v1/checks/${encodeURIComponent(id)}`, { data, ...overrides }, opts.idempotencyKey ? { "idempotency-key": opts.idempotencyKey } : {});
211
+ const final = t.status === "pending" ? await this.tasks.waitFor(t.id, { timeoutMs: opts.timeoutMs }) : t;
212
+ const opt = (k) => final.options.find((o) => o.id === k)?.label ?? k ?? null;
213
+ const first = final.options[0]?.label ?? null;
214
+ const timedOut = final.status === "expired" || final.status === "pending";
215
+ const answer = final.status === "completed" ? opt(final.result?.answer) : final.status === "skipped" ? first : timedOut && t.on_timeout === "proceed" ? first : null;
216
+ return {
217
+ ...final,
218
+ check: t.check,
219
+ on_timeout: t.on_timeout,
220
+ kept_private: t.kept_private ?? [],
221
+ skipped_because: t.skipped_because ?? null,
222
+ answer,
223
+ checked: final.status !== "skipped",
224
+ timed_out: timedOut
225
+ };
226
+ }
198
227
  /** Creates a task and waits for people to answer it. */
199
228
  async verify(params, opts = {}) {
200
229
  const t = await this.tasks.create(params, { idempotencyKey: opts.idempotencyKey });
package/dist/index.d.cts CHANGED
@@ -4,7 +4,33 @@ type Option = {
4
4
  };
5
5
  /** "verifiers" asks ID verified people first; "team" goes straight to your own people ($0.05 per internal vote live). */
6
6
  type Route = "verifiers" | "team";
7
- type TaskStatus = "pending" | "completed" | "expired" | "canceled";
7
+ type CheckOverrides = {
8
+ question?: string;
9
+ options?: string[];
10
+ instructions?: string;
11
+ verifiers?: number;
12
+ team?: string;
13
+ route?: "verifiers" | "team";
14
+ /** Check this one even if the sample or a condition would skip it. */ always?: boolean;
15
+ /** Share of calls people check, 0 to 1, for this call. */ sample?: number;
16
+ /** Seconds people have to answer. */ timeout?: number;
17
+ metadata?: Record<string, unknown>;
18
+ webhook_url?: string;
19
+ };
20
+ type CheckExtra = {
21
+ check: string;
22
+ on_timeout: "proceed" | "hold";
23
+ kept_private?: string[];
24
+ skipped_because?: string;
25
+ };
26
+ type CheckResult = Task & Omit<CheckExtra, "kept_private" | "skipped_because"> & {
27
+ /** The option to act on, by label. */ answer: string | null;
28
+ /** false when the check wasn't needed this time (a sample or a condition). */ checked: boolean;
29
+ timed_out: boolean;
30
+ kept_private: string[];
31
+ skipped_because: string | null;
32
+ };
33
+ type TaskStatus = "pending" | "completed" | "expired" | "canceled" | "skipped";
8
34
  type Result = {
9
35
  answer: string | null;
10
36
  label: string | null;
@@ -57,6 +83,8 @@ type Task = {
57
83
  app?: string | null;
58
84
  mode: "live" | "test";
59
85
  status: TaskStatus;
86
+ /** false when a sampled check didn't pick this task: it wasn't sent to people, and result.answer is the first option. */
87
+ sampled?: boolean;
60
88
  question: string;
61
89
  context: string | null;
62
90
  image_url: string | null;
@@ -251,6 +279,30 @@ type MapKey = {
251
279
  created_at: string;
252
280
  last_used_at: string | null;
253
281
  };
282
+ type MapChanges = {
283
+ since: number | null;
284
+ added: {
285
+ id: string;
286
+ label: string;
287
+ score: number | null;
288
+ checked: boolean;
289
+ }[];
290
+ removed: {
291
+ id: string;
292
+ label: string;
293
+ }[];
294
+ renamed: {
295
+ from: string;
296
+ to: string;
297
+ label: string;
298
+ }[];
299
+ risky: {
300
+ id: string;
301
+ label: string;
302
+ score: number;
303
+ why: string[];
304
+ }[];
305
+ };
254
306
  type StoredAgentMap = AgentMap & {
255
307
  object: "agent_map";
256
308
  app: string;
@@ -258,6 +310,23 @@ type StoredAgentMap = AgentMap & {
258
310
  created_at?: string;
259
311
  via?: string;
260
312
  url?: string;
313
+ /** Set for a branch map: the branch, the main map version it's compared with, and its pull request. */
314
+ branch?: string;
315
+ main_version?: number | null;
316
+ pull_request?: string | null;
317
+ changes?: MapChanges;
318
+ engine?: {
319
+ version: string;
320
+ actions: Record<string, {
321
+ score: number;
322
+ factors: string[];
323
+ oversight: {
324
+ mode: string;
325
+ rate: number | null;
326
+ verifiers: number;
327
+ };
328
+ }>;
329
+ };
261
330
  };
262
331
  type Rubric = {
263
332
  object: "rubric";
@@ -350,7 +419,7 @@ type WebhookEvent = {
350
419
  created_at: string;
351
420
  data: Task;
352
421
  };
353
- declare const VERSION = "1.14.0";
422
+ declare const VERSION = "1.16.0";
354
423
  type GitHubLink = {
355
424
  object: "github_link";
356
425
  app: string;
@@ -467,7 +536,10 @@ declare class Plures {
467
536
  guide: () => Promise<string>;
468
537
  retrieve: (app: string) => Promise<StoredAgentMap>;
469
538
  /** Uploads a new version. Checks you already added or dismissed keep that. */
470
- upload: (app: string, map: AgentMap) => Promise<StoredAgentMap>;
539
+ /** A branch other than the main one gets its own map, compared with main, and noted on its pull request. */
540
+ upload: (app: string, map: AgentMap, opts?: {
541
+ branch?: string;
542
+ }) => Promise<StoredAgentMap>;
471
543
  setCheckpoint: (app: string, id: string, status: "added" | "dismissed" | "suggested") => Promise<StoredAgentMap>;
472
544
  /** A key that can only read and upload this app's map, for CI. The key is in the response once. */
473
545
  createKey: (app: string, opts?: {
@@ -523,6 +595,17 @@ declare class Plures {
523
595
  phase: string;
524
596
  received: number;
525
597
  }>;
598
+ /** After npx plures check add pushed a branch with the check in it: Plures opens the pull request. Pass error instead if adding it failed. */
599
+ checkPr: (app: string, id: string, p: {
600
+ branch: string;
601
+ } | {
602
+ error: string;
603
+ }) => Promise<{
604
+ object: "check_pr";
605
+ check: string;
606
+ opened: boolean;
607
+ pull_request?: string;
608
+ }>;
526
609
  /** The GitHub Actions workflow that maps a repo on every push to its main branch. */
527
610
  workflow: (app: string, branch?: string) => Promise<string>;
528
611
  };
@@ -652,6 +735,18 @@ declare class Plures {
652
735
  };
653
736
  /** Your prepaid credit. A live task that could cost more than available_cents is refused with a 402 insufficient_credits error. */
654
737
  balance: () => Promise<Balance>;
738
+ /**
739
+ * Runs a check from your app's map and waits for the decision. Send the data by name; the check
740
+ * saved on Plures supplies the question, options, guidance, which fields people see (the rest
741
+ * never leave Plures), who decides, how often and what happens on a timeout. Any of those can be
742
+ * overridden for this call. `answer` is the option to act on: what people chose, the first option
743
+ * when the check wasn't needed (a sample or a condition) or timed out set to go ahead, and null
744
+ * when it timed out set to hold.
745
+ */
746
+ check(id: string, data?: Record<string, unknown>, overrides?: CheckOverrides, opts?: {
747
+ timeoutMs?: number;
748
+ idempotencyKey?: string;
749
+ }): Promise<CheckResult>;
655
750
  /** Creates a task and waits for people to answer it. */
656
751
  verify(params: CreateTask, opts?: {
657
752
  timeoutMs?: number;
@@ -665,4 +760,4 @@ declare class Plures {
665
760
  static verifyWebhook(rawBody: string, signatureHeader: string | null, secret: string, toleranceSec?: number): Promise<WebhookEvent>;
666
761
  }
667
762
 
668
- export { type AgentMap, type App, type Balance, type Consensus, type CreateTask, type Escalation, type GitHubLink, type MapCheckpoint, type MapDraft, type MapKey, type MapNode, type Mix, type Option, Plures, PluresError, type Release, type Result, type Route, type Routing, type RoutingRule, type RoutingUpdate, type Rubric, type RubricParams, type Sample, type StoredAgentMap, type Task, type TaskStatus, type TeamTally, type TeamVote, type TestMix, VERSION, type WebhookEvent, Plures as default };
763
+ export { type AgentMap, type App, type Balance, type CheckOverrides, type CheckResult, type Consensus, type CreateTask, type Escalation, type GitHubLink, type MapChanges, type MapCheckpoint, type MapDraft, type MapKey, type MapNode, type Mix, type Option, Plures, PluresError, type Release, type Result, type Route, type Routing, type RoutingRule, type RoutingUpdate, type Rubric, type RubricParams, type Sample, type StoredAgentMap, type Task, type TaskStatus, type TeamTally, type TeamVote, type TestMix, VERSION, type WebhookEvent, Plures as default };
package/dist/index.d.ts CHANGED
@@ -4,7 +4,33 @@ type Option = {
4
4
  };
5
5
  /** "verifiers" asks ID verified people first; "team" goes straight to your own people ($0.05 per internal vote live). */
6
6
  type Route = "verifiers" | "team";
7
- type TaskStatus = "pending" | "completed" | "expired" | "canceled";
7
+ type CheckOverrides = {
8
+ question?: string;
9
+ options?: string[];
10
+ instructions?: string;
11
+ verifiers?: number;
12
+ team?: string;
13
+ route?: "verifiers" | "team";
14
+ /** Check this one even if the sample or a condition would skip it. */ always?: boolean;
15
+ /** Share of calls people check, 0 to 1, for this call. */ sample?: number;
16
+ /** Seconds people have to answer. */ timeout?: number;
17
+ metadata?: Record<string, unknown>;
18
+ webhook_url?: string;
19
+ };
20
+ type CheckExtra = {
21
+ check: string;
22
+ on_timeout: "proceed" | "hold";
23
+ kept_private?: string[];
24
+ skipped_because?: string;
25
+ };
26
+ type CheckResult = Task & Omit<CheckExtra, "kept_private" | "skipped_because"> & {
27
+ /** The option to act on, by label. */ answer: string | null;
28
+ /** false when the check wasn't needed this time (a sample or a condition). */ checked: boolean;
29
+ timed_out: boolean;
30
+ kept_private: string[];
31
+ skipped_because: string | null;
32
+ };
33
+ type TaskStatus = "pending" | "completed" | "expired" | "canceled" | "skipped";
8
34
  type Result = {
9
35
  answer: string | null;
10
36
  label: string | null;
@@ -57,6 +83,8 @@ type Task = {
57
83
  app?: string | null;
58
84
  mode: "live" | "test";
59
85
  status: TaskStatus;
86
+ /** false when a sampled check didn't pick this task: it wasn't sent to people, and result.answer is the first option. */
87
+ sampled?: boolean;
60
88
  question: string;
61
89
  context: string | null;
62
90
  image_url: string | null;
@@ -251,6 +279,30 @@ type MapKey = {
251
279
  created_at: string;
252
280
  last_used_at: string | null;
253
281
  };
282
+ type MapChanges = {
283
+ since: number | null;
284
+ added: {
285
+ id: string;
286
+ label: string;
287
+ score: number | null;
288
+ checked: boolean;
289
+ }[];
290
+ removed: {
291
+ id: string;
292
+ label: string;
293
+ }[];
294
+ renamed: {
295
+ from: string;
296
+ to: string;
297
+ label: string;
298
+ }[];
299
+ risky: {
300
+ id: string;
301
+ label: string;
302
+ score: number;
303
+ why: string[];
304
+ }[];
305
+ };
254
306
  type StoredAgentMap = AgentMap & {
255
307
  object: "agent_map";
256
308
  app: string;
@@ -258,6 +310,23 @@ type StoredAgentMap = AgentMap & {
258
310
  created_at?: string;
259
311
  via?: string;
260
312
  url?: string;
313
+ /** Set for a branch map: the branch, the main map version it's compared with, and its pull request. */
314
+ branch?: string;
315
+ main_version?: number | null;
316
+ pull_request?: string | null;
317
+ changes?: MapChanges;
318
+ engine?: {
319
+ version: string;
320
+ actions: Record<string, {
321
+ score: number;
322
+ factors: string[];
323
+ oversight: {
324
+ mode: string;
325
+ rate: number | null;
326
+ verifiers: number;
327
+ };
328
+ }>;
329
+ };
261
330
  };
262
331
  type Rubric = {
263
332
  object: "rubric";
@@ -350,7 +419,7 @@ type WebhookEvent = {
350
419
  created_at: string;
351
420
  data: Task;
352
421
  };
353
- declare const VERSION = "1.14.0";
422
+ declare const VERSION = "1.16.0";
354
423
  type GitHubLink = {
355
424
  object: "github_link";
356
425
  app: string;
@@ -467,7 +536,10 @@ declare class Plures {
467
536
  guide: () => Promise<string>;
468
537
  retrieve: (app: string) => Promise<StoredAgentMap>;
469
538
  /** Uploads a new version. Checks you already added or dismissed keep that. */
470
- upload: (app: string, map: AgentMap) => Promise<StoredAgentMap>;
539
+ /** A branch other than the main one gets its own map, compared with main, and noted on its pull request. */
540
+ upload: (app: string, map: AgentMap, opts?: {
541
+ branch?: string;
542
+ }) => Promise<StoredAgentMap>;
471
543
  setCheckpoint: (app: string, id: string, status: "added" | "dismissed" | "suggested") => Promise<StoredAgentMap>;
472
544
  /** A key that can only read and upload this app's map, for CI. The key is in the response once. */
473
545
  createKey: (app: string, opts?: {
@@ -523,6 +595,17 @@ declare class Plures {
523
595
  phase: string;
524
596
  received: number;
525
597
  }>;
598
+ /** After npx plures check add pushed a branch with the check in it: Plures opens the pull request. Pass error instead if adding it failed. */
599
+ checkPr: (app: string, id: string, p: {
600
+ branch: string;
601
+ } | {
602
+ error: string;
603
+ }) => Promise<{
604
+ object: "check_pr";
605
+ check: string;
606
+ opened: boolean;
607
+ pull_request?: string;
608
+ }>;
526
609
  /** The GitHub Actions workflow that maps a repo on every push to its main branch. */
527
610
  workflow: (app: string, branch?: string) => Promise<string>;
528
611
  };
@@ -652,6 +735,18 @@ declare class Plures {
652
735
  };
653
736
  /** Your prepaid credit. A live task that could cost more than available_cents is refused with a 402 insufficient_credits error. */
654
737
  balance: () => Promise<Balance>;
738
+ /**
739
+ * Runs a check from your app's map and waits for the decision. Send the data by name; the check
740
+ * saved on Plures supplies the question, options, guidance, which fields people see (the rest
741
+ * never leave Plures), who decides, how often and what happens on a timeout. Any of those can be
742
+ * overridden for this call. `answer` is the option to act on: what people chose, the first option
743
+ * when the check wasn't needed (a sample or a condition) or timed out set to go ahead, and null
744
+ * when it timed out set to hold.
745
+ */
746
+ check(id: string, data?: Record<string, unknown>, overrides?: CheckOverrides, opts?: {
747
+ timeoutMs?: number;
748
+ idempotencyKey?: string;
749
+ }): Promise<CheckResult>;
655
750
  /** Creates a task and waits for people to answer it. */
656
751
  verify(params: CreateTask, opts?: {
657
752
  timeoutMs?: number;
@@ -665,4 +760,4 @@ declare class Plures {
665
760
  static verifyWebhook(rawBody: string, signatureHeader: string | null, secret: string, toleranceSec?: number): Promise<WebhookEvent>;
666
761
  }
667
762
 
668
- export { type AgentMap, type App, type Balance, type Consensus, type CreateTask, type Escalation, type GitHubLink, type MapCheckpoint, type MapDraft, type MapKey, type MapNode, type Mix, type Option, Plures, PluresError, type Release, type Result, type Route, type Routing, type RoutingRule, type RoutingUpdate, type Rubric, type RubricParams, type Sample, type StoredAgentMap, type Task, type TaskStatus, type TeamTally, type TeamVote, type TestMix, VERSION, type WebhookEvent, Plures as default };
763
+ export { type AgentMap, type App, type Balance, type CheckOverrides, type CheckResult, type Consensus, type CreateTask, type Escalation, type GitHubLink, type MapChanges, type MapCheckpoint, type MapDraft, type MapKey, type MapNode, type Mix, type Option, Plures, PluresError, type Release, type Result, type Route, type Routing, type RoutingRule, type RoutingUpdate, type Rubric, type RubricParams, type Sample, type StoredAgentMap, type Task, type TaskStatus, type TeamTally, type TeamVote, type TestMix, VERSION, type WebhookEvent, Plures as default };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  PluresError,
4
4
  VERSION,
5
5
  index_default
6
- } from "./chunk-7OMKME5Z.js";
6
+ } from "./chunk-Z75ZTXHZ.js";
7
7
  export {
8
8
  Plures,
9
9
  PluresError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "plures",
3
- "version": "1.14.0",
3
+ "version": "1.16.0",
4
4
  "description": "Human judgment as an API for AI agents. Send a decision to ID verified people and get their consensus back.",
5
5
  "keywords": [
6
6
  "human-in-the-loop",