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 +3 -1
- package/dist/{chunk-7OMKME5Z.js → chunk-Z75ZTXHZ.js} +31 -2
- package/dist/cli.cjs +127 -5
- package/dist/cli.js +97 -4
- package/dist/index.cjs +31 -2
- package/dist/index.d.cts +99 -4
- package/dist/index.d.ts +99 -4
- package/dist/index.js +1 -1
- package/package.json +1 -1
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)
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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, "
|
|
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-
|
|
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
|
|
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, "
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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