plures 1.11.0 → 1.13.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 +2 -1
- package/dist/{chunk-WPSUIAVZ.js → chunk-GIOZT6FB.js} +3 -1
- package/dist/cli.cjs +232 -8
- package/dist/cli.js +210 -8
- package/dist/index.cjs +3 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,8 +49,9 @@ if (event.type === "task.completed" && event.data.result?.agreement === 1) {
|
|
|
49
49
|
- `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
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.
|
|
51
51
|
- `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
|
+
- 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.
|
|
52
53
|
- `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.
|
|
53
|
-
- `plures.github.repos()`, `.connect(app, "owner/repo")`, `.turnOn(app)`, `.status(app)`, `.disconnect(app)`: Map with GitHub from code. Plures sets the repo's map key secret and opens a pull request adding the workflow; `turnOn` merges it. Installing the Plures GitHub App needs a browser once (`install_url`). From a terminal: `npx plures map github connect`, then `npx plures map github on
|
|
54
|
+
- `plures.github.repos()`, `.connect(app, "owner/repo")`, `.turnOn(app)`, `.run(app)` (map now), `.status(app)`, `.disconnect(app)`: Map with GitHub from code. Plures sets the repo's map key secret and opens a pull request adding the workflow; `turnOn` merges it. Installing the Plures GitHub App needs a browser once (`install_url`). From a terminal: `npx plures map github connect`, then `npx plures map github on`; `npx plures map github run` maps again now.
|
|
54
55
|
- `plures.quality.sample({ rubric, output, input? })`: send every output; a share is graded by people against the rubric, with personal details taken out first. `plures.quality.rubrics.create({ name, question, options, sample_rate })`, `.retrieve(name)` (score by day, this week against the four before, drift), `.list()`, `.update()`, `.del()`. `plures.quality.compare({ release, input, a, b })` and `plures.quality.release(name)` for blind release checks. `plures.quality.export(rubric)` returns graded samples for evals or fine tuning.
|
|
55
56
|
- `plures.testMix.set({ unanimous: 70, split: 20, tie: 5, expired: 5 })`, `.get()`, `.clear()`: with a test key, makes a share of test tasks split, tie or expire at random so you can test routing to your team without changing your agent. A task can override it with `metadata.test_mix`, and `metadata.test_outcome` beats both.
|
|
56
57
|
- `plures.routing.get()`, `plures.routing.set({ groups, rules, team })`: named teams of your own people and rules by category, like `{ groups: [{ name: "Finance", emails: ["ap@acme.com"] }], rules: [{ category: "refunds", route: "team", group: "Finance" }] }`. What you send replaces what's there; the rest stays.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
var VERSION = "1.
|
|
2
|
+
var VERSION = "1.13.0";
|
|
3
3
|
var PluresError = class extends Error {
|
|
4
4
|
constructor(message, status, type) {
|
|
5
5
|
super(message);
|
|
@@ -130,6 +130,8 @@ var Plures = class {
|
|
|
130
130
|
/** Connects a repo ("owner/name"): sets its map key secret and opens the pull request. */
|
|
131
131
|
connect: (app, repo) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/github`, { repo }),
|
|
132
132
|
/** Turns mapping on by merging the pull request. The repo's own rules (reviews, checks) still apply. */
|
|
133
|
+
/** Map now: runs the app's map workflow on GitHub. { update: true } first updates an older workflow. Watch it on the map page. */
|
|
134
|
+
run: (app, opts = {}) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/github/run`, opts),
|
|
133
135
|
turnOn: (app) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/github/merge`, {}),
|
|
134
136
|
/** Revokes the repo's map key. Delete the workflow file in the repo to stop runs. */
|
|
135
137
|
disconnect: (app) => this.req("DELETE", `/api/v1/apps/${encodeURIComponent(app)}/github`)
|
package/dist/cli.cjs
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key2 of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key2) && key2 !== except)
|
|
13
|
+
__defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
3
25
|
|
|
4
26
|
// src/cli.ts
|
|
5
27
|
var import_node_child_process = require("child_process");
|
|
@@ -8,7 +30,7 @@ var import_node_path = require("path");
|
|
|
8
30
|
var import_node_fs = require("fs");
|
|
9
31
|
|
|
10
32
|
// src/index.ts
|
|
11
|
-
var VERSION = "1.
|
|
33
|
+
var VERSION = "1.13.0";
|
|
12
34
|
var PluresError = class extends Error {
|
|
13
35
|
constructor(message, status, type) {
|
|
14
36
|
super(message);
|
|
@@ -139,6 +161,8 @@ var Plures = class {
|
|
|
139
161
|
/** Connects a repo ("owner/name"): sets its map key secret and opens the pull request. */
|
|
140
162
|
connect: (app2, repo) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app2)}/github`, { repo }),
|
|
141
163
|
/** Turns mapping on by merging the pull request. The repo's own rules (reviews, checks) still apply. */
|
|
164
|
+
/** Map now: runs the app's map workflow on GitHub. { update: true } first updates an older workflow. Watch it on the map page. */
|
|
165
|
+
run: (app2, opts = {}) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app2)}/github/run`, opts),
|
|
142
166
|
turnOn: (app2) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app2)}/github/merge`, {}),
|
|
143
167
|
/** Revokes the repo's map key. Delete the workflow file in the repo to stop runs. */
|
|
144
168
|
disconnect: (app2) => this.req("DELETE", `/api/v1/apps/${encodeURIComponent(app2)}/github`)
|
|
@@ -205,6 +229,178 @@ function timingSafeEqual(a, b) {
|
|
|
205
229
|
}
|
|
206
230
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
207
231
|
|
|
232
|
+
// src/term.ts
|
|
233
|
+
var out = process.stdout;
|
|
234
|
+
var fancy = Boolean(out.isTTY && !process.env.NO_COLOR && !process.env.CI && process.env.TERM !== "dumb");
|
|
235
|
+
var rgb = (r, g, b) => (s) => `\x1B[38;2;${r};${g};${b}m${s}\x1B[0m`;
|
|
236
|
+
var LANES = [rgb(91, 75, 255), rgb(74, 107, 250), rgb(50, 144, 238), rgb(25, 198, 230)];
|
|
237
|
+
var C = {
|
|
238
|
+
text: rgb(243, 244, 247),
|
|
239
|
+
dim: rgb(110, 116, 136),
|
|
240
|
+
mute: rgb(160, 166, 186),
|
|
241
|
+
coral: rgb(255, 142, 122),
|
|
242
|
+
violet: rgb(185, 180, 255),
|
|
243
|
+
cyan: rgb(127, 227, 244),
|
|
244
|
+
amber: rgb(245, 184, 74),
|
|
245
|
+
bold: (s) => `\x1B[1m${s}\x1B[22m`
|
|
246
|
+
};
|
|
247
|
+
var width = () => Math.max(40, Math.min(out.columns || 80, 110));
|
|
248
|
+
var visible = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
249
|
+
var cut = (s, n) => s.length > n ? `${s.slice(0, Math.max(1, n - 1))}\u2026` : s;
|
|
250
|
+
function banner(app2, where) {
|
|
251
|
+
const bar = LANES.map((c2, i) => c2("\u2501".repeat(8 - i))).join(" ");
|
|
252
|
+
out.write(`
|
|
253
|
+
${bar}
|
|
254
|
+
|
|
255
|
+
${C.bold(C.text("Plures"))} ${C.dim("map")} ${C.mute(app2)}
|
|
256
|
+
${C.dim(where)}
|
|
257
|
+
|
|
258
|
+
`);
|
|
259
|
+
}
|
|
260
|
+
function liveLine() {
|
|
261
|
+
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
262
|
+
let i = 0, count = 0, file = "", phase = "reading", timer = null;
|
|
263
|
+
const draw = () => {
|
|
264
|
+
const spin = LANES[Math.floor(i / 2) % 4](frames[i % frames.length]);
|
|
265
|
+
i++;
|
|
266
|
+
const left = phase === "drawing" ? ` ${spin} ${C.violet("Drawing the map")} ${C.dim(`from ${count} ${count === 1 ? "file" : "files"}`)}` : ` ${spin} ${C.bold(C.text(String(count).padStart(3)))} ${C.dim(count === 1 ? "file read " : "files read")} `;
|
|
267
|
+
const room = width() - visible(left).length - 2;
|
|
268
|
+
const f = phase === "reading" && file ? (() => {
|
|
269
|
+
const slash = file.lastIndexOf("/");
|
|
270
|
+
const s = cut(file, room);
|
|
271
|
+
return slash >= 0 && s === file ? `${C.dim(file.slice(0, slash + 1))}${C.text(file.slice(slash + 1))}` : C.text(s);
|
|
272
|
+
})() : "";
|
|
273
|
+
out.write(`\r\x1B[K${left}${f}`);
|
|
274
|
+
};
|
|
275
|
+
return {
|
|
276
|
+
start() {
|
|
277
|
+
out.write("\x1B[?25l");
|
|
278
|
+
draw();
|
|
279
|
+
timer = setInterval(draw, 80);
|
|
280
|
+
},
|
|
281
|
+
read(path) {
|
|
282
|
+
count++;
|
|
283
|
+
file = path;
|
|
284
|
+
},
|
|
285
|
+
drawing() {
|
|
286
|
+
phase = "drawing";
|
|
287
|
+
},
|
|
288
|
+
stop() {
|
|
289
|
+
if (timer) clearInterval(timer);
|
|
290
|
+
timer = null;
|
|
291
|
+
out.write("\r\x1B[K\x1B[?25h");
|
|
292
|
+
},
|
|
293
|
+
get count() {
|
|
294
|
+
return count;
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
if (fancy) for (const sig of ["SIGINT", "SIGTERM"]) process.once(sig, () => {
|
|
299
|
+
out.write("\x1B[?25h\n");
|
|
300
|
+
process.exit(130);
|
|
301
|
+
});
|
|
302
|
+
var ICON = { input: "\u25B8", model: "\u25CE", tool: "\u2315", decision: "\u25C7", action: "\u2197", data: "\u25A4", output: "\u21A6", human: "\u263A" };
|
|
303
|
+
function drawMap(m, url) {
|
|
304
|
+
const nodes = new Map(m.nodes.map((n) => [n.id, n]));
|
|
305
|
+
const cps = new Map(m.checkpoints.filter((c2) => c2.status !== "dismissed").map((c2) => [c2.at, c2]));
|
|
306
|
+
const W = width();
|
|
307
|
+
const nb = new Map(m.nodes.map((n) => [n.id, []]));
|
|
308
|
+
for (const e of m.edges) {
|
|
309
|
+
nb.get(e.from)?.push(e.to);
|
|
310
|
+
nb.get(e.to)?.push(e.from);
|
|
311
|
+
}
|
|
312
|
+
const seen = /* @__PURE__ */ new Set(), flows = [];
|
|
313
|
+
for (const n of m.nodes) {
|
|
314
|
+
if (seen.has(n.id)) continue;
|
|
315
|
+
const part = [], st = [n.id];
|
|
316
|
+
seen.add(n.id);
|
|
317
|
+
while (st.length) {
|
|
318
|
+
const v = st.pop();
|
|
319
|
+
part.push(v);
|
|
320
|
+
for (const w of nb.get(v) ?? []) if (!seen.has(w)) {
|
|
321
|
+
seen.add(w);
|
|
322
|
+
st.push(w);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
flows.push(m.nodes.map((x) => x.id).filter((id) => part.includes(id)));
|
|
326
|
+
}
|
|
327
|
+
const chip = (id) => {
|
|
328
|
+
const n = nodes.get(id), c2 = cps.get(id);
|
|
329
|
+
const label = cut(n.label, 34);
|
|
330
|
+
const tag = !c2 ? "" : c2.status === "added" ? ` ${C.cyan("\u2713 checked")}` : ` ${C.violet("\u25CC check suggested")}`;
|
|
331
|
+
if (n.kind === "action") {
|
|
332
|
+
if (c2?.status === "added") return `${C.cyan(ICON.action)} ${C.text(label)}${tag}`;
|
|
333
|
+
const risky = n.risk === "high" || n.risk === "medium";
|
|
334
|
+
return `${risky ? C.coral(ICON.action) : C.mute(ICON.action)} ${risky ? C.coral(label) : C.mute(label)}${tag}`;
|
|
335
|
+
}
|
|
336
|
+
return `${C.dim(ICON[n.kind] ?? "\xB7")} ${C.mute(label)}${tag}`;
|
|
337
|
+
};
|
|
338
|
+
out.write("\n");
|
|
339
|
+
for (const ids of flows.filter((f) => f.length > 1)) {
|
|
340
|
+
const set = new Set(ids);
|
|
341
|
+
const es = m.edges.filter((e) => set.has(e.from) && set.has(e.to));
|
|
342
|
+
const layer = new Map(ids.map((id) => [id, 0]));
|
|
343
|
+
const order = [], state = /* @__PURE__ */ new Map(), back = /* @__PURE__ */ new Set();
|
|
344
|
+
const visit = (v) => {
|
|
345
|
+
state.set(v, 1);
|
|
346
|
+
for (const e of es.filter((x) => x.from === v)) {
|
|
347
|
+
if (state.get(e.to) === 1) back.add(`${v}>${e.to}`);
|
|
348
|
+
else if (!state.get(e.to)) visit(e.to);
|
|
349
|
+
}
|
|
350
|
+
state.set(v, 2);
|
|
351
|
+
order.push(v);
|
|
352
|
+
};
|
|
353
|
+
for (const id of ids) if (!state.get(id)) visit(id);
|
|
354
|
+
for (const v of order.reverse()) for (const e of es) if (e.from === v && !back.has(`${v}>${e.to}`)) layer.set(e.to, Math.max(layer.get(e.to), layer.get(v) + 1));
|
|
355
|
+
const rows = [];
|
|
356
|
+
for (const id of ids) (rows[layer.get(id)] ??= []).push(id);
|
|
357
|
+
const root = rows[0]?.length === 1 ? rows[0][0] : null;
|
|
358
|
+
out.write(` ${LANES[flows.indexOf(ids) % 4]("\u25CF")} ${C.bold(C.text(cut(root ? nodes.get(root).label : "Flow", W - 6)))}
|
|
359
|
+
`);
|
|
360
|
+
rows.forEach((row, ri) => {
|
|
361
|
+
if (!row || ri === 0 && root) return;
|
|
362
|
+
const lines = [[]];
|
|
363
|
+
let used = 6;
|
|
364
|
+
for (const id of row) {
|
|
365
|
+
const w = visible(chip(id)).length + 4;
|
|
366
|
+
if (used + w > W && lines[lines.length - 1].length) {
|
|
367
|
+
lines.push([]);
|
|
368
|
+
used = 6;
|
|
369
|
+
}
|
|
370
|
+
lines[lines.length - 1].push(id);
|
|
371
|
+
used += w;
|
|
372
|
+
}
|
|
373
|
+
for (const ln of lines) out.write(` ${C.dim("\u2502")} ${ln.map((id) => chip(id)).join(C.dim(" \xB7 "))}
|
|
374
|
+
`);
|
|
375
|
+
});
|
|
376
|
+
out.write("\n");
|
|
377
|
+
}
|
|
378
|
+
const actions = m.nodes.filter((n) => n.kind === "action");
|
|
379
|
+
const on = actions.filter((a) => cps.get(a.id)?.status === "added").length;
|
|
380
|
+
const sugg = m.checkpoints.filter((c2) => c2.status === "suggested").length;
|
|
381
|
+
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."))}
|
|
382
|
+
`);
|
|
383
|
+
out.write(` ${C.mute(`${on} of ${actions.length} ${actions.length === 1 ? "action" : "actions"} with consequences ${on === 1 ? "has" : "have"} a human check.`)}
|
|
384
|
+
|
|
385
|
+
`);
|
|
386
|
+
out.write(` ${C.text("See it build:")} ${C.cyan(url)}
|
|
387
|
+
|
|
388
|
+
`);
|
|
389
|
+
}
|
|
390
|
+
async function openBrowser(url) {
|
|
391
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
392
|
+
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
393
|
+
try {
|
|
394
|
+
const p = spawn2(cmd, args, { stdio: "ignore", detached: true });
|
|
395
|
+
p.on("error", () => {
|
|
396
|
+
});
|
|
397
|
+
p.unref();
|
|
398
|
+
return true;
|
|
399
|
+
} catch {
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
208
404
|
// src/cli.ts
|
|
209
405
|
var argv = process.argv.slice(2);
|
|
210
406
|
var flag = (name) => {
|
|
@@ -215,6 +411,7 @@ var flag = (name) => {
|
|
|
215
411
|
return v;
|
|
216
412
|
};
|
|
217
413
|
var branch = flag("branch");
|
|
414
|
+
var noOpen = argv.includes("--no-open") ? (argv.splice(argv.indexOf("--no-open"), 1), true) : false;
|
|
218
415
|
var app = flag("app") ?? process.env.PLURES_APP ?? "default";
|
|
219
416
|
var key = flag("key") ?? process.env.PLURES_API_KEY;
|
|
220
417
|
var base = flag("base-url") ?? process.env.PLURES_BASE_URL ?? "https://plures.si";
|
|
@@ -242,14 +439,15 @@ async function upload(file) {
|
|
|
242
439
|
} catch {
|
|
243
440
|
die(`${file} isn't valid JSON.`);
|
|
244
441
|
}
|
|
245
|
-
console.log(dim(`Sending the map to Plures. No source code is sent.`));
|
|
442
|
+
if (!fancy) console.log(dim(`Sending the map to Plures. No source code is sent.`));
|
|
246
443
|
let m;
|
|
247
444
|
try {
|
|
248
445
|
m = await client().maps.upload(app, map2);
|
|
249
446
|
} catch (e) {
|
|
250
447
|
die(e instanceof PluresError ? `Plures said: ${e.message}` : String(e));
|
|
251
448
|
}
|
|
252
|
-
|
|
449
|
+
if (fancy) drawMap(m, m.url ?? `${base}/dashboard/apps/${m.app}/map`);
|
|
450
|
+
else report(m);
|
|
253
451
|
}
|
|
254
452
|
function report(m) {
|
|
255
453
|
const nodes = new Map(m.nodes.map((n) => [n.id, n]));
|
|
@@ -299,7 +497,12 @@ With plures-mcp connected, just ask: ${cyan("Map this app for Plures")}`);
|
|
|
299
497
|
childEnv.ANTHROPIC_API_KEY = run.token;
|
|
300
498
|
console.log(dim(run.free ? "Plures is mapping this repo. Your first map is free." : `Plures is mapping this repo, at most $${(run.max_cents / 100).toFixed(2)} from your credit.`));
|
|
301
499
|
}
|
|
302
|
-
|
|
500
|
+
const pageUrl = `${base}/dashboard/apps/${encodeURIComponent(app)}/map`;
|
|
501
|
+
if (fancy) {
|
|
502
|
+
banner(app, "Reading this repo on your machine with Claude Code. Only file names and the map go to Plures.");
|
|
503
|
+
if (key && !noOpen && await openBrowser(pageUrl)) console.log(` ${dim("Watch it live in your browser:")} ${cyan(pageUrl)}
|
|
504
|
+
`);
|
|
505
|
+
} 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.")}
|
|
303
506
|
`);
|
|
304
507
|
const prompt = `${guide}
|
|
305
508
|
|
|
@@ -327,6 +530,8 @@ Write the map to ${FILE} in the current directory now. Read the code; don't run
|
|
|
327
530
|
return r.startsWith("..") ? null : r.replace(/\\/g, "/");
|
|
328
531
|
};
|
|
329
532
|
let result = "";
|
|
533
|
+
const ticker = fancy ? liveLine() : null;
|
|
534
|
+
ticker?.start();
|
|
330
535
|
const code = await new Promise((resolve) => {
|
|
331
536
|
const p = (0, import_node_child_process.spawn)("claude", ["-p", prompt, "--output-format", "stream-json", "--verbose", "--allowedTools", "Read,Grep,Glob,Write,Bash(git rev-parse:*),Bash(git remote:*)"], { stdio: ["ignore", "pipe", "inherit"], env: childEnv });
|
|
332
537
|
let buf = "";
|
|
@@ -350,10 +555,12 @@ Write the map to ${FILE} in the current directory now. Read the code; don't run
|
|
|
350
555
|
if (!f) continue;
|
|
351
556
|
reads++;
|
|
352
557
|
queue.push({ path: f, tool: "Read" });
|
|
353
|
-
|
|
558
|
+
if (ticker) ticker.read(f);
|
|
559
|
+
else console.log(` ${dim(String(reads).padStart(3))} ${dim("reading")} ${f}`);
|
|
354
560
|
} else if (b.name === "Write" && String(b.input?.file_path ?? "").endsWith(FILE)) {
|
|
355
561
|
phase = "drawing";
|
|
356
|
-
|
|
562
|
+
if (ticker) ticker.drawing();
|
|
563
|
+
else console.log(`
|
|
357
564
|
${violet("drawing")} the map`);
|
|
358
565
|
}
|
|
359
566
|
}
|
|
@@ -363,9 +570,11 @@ Write the map to ${FILE} in the current directory now. Read the code; don't run
|
|
|
363
570
|
p.on("error", () => resolve(1));
|
|
364
571
|
});
|
|
365
572
|
clearInterval(tick);
|
|
366
|
-
|
|
573
|
+
ticker?.stop();
|
|
574
|
+
if (result && !fancy) console.log(`
|
|
367
575
|
${result.trim()}
|
|
368
576
|
`);
|
|
577
|
+
if (fancy) console.log(` ${cyan("\u2713")} ${reads} ${reads === 1 ? "file" : "files"} read. ${dim("Sending the map to Plures. No source code is sent.")}`);
|
|
369
578
|
const ok = code === 0 && (0, import_node_fs.existsSync)(FILE) && (0, import_node_fs.statSync)(FILE).mtimeMs !== before;
|
|
370
579
|
if (!ok) {
|
|
371
580
|
phase = "failed";
|
|
@@ -410,6 +619,20 @@ Turn it on: ${cyan("npx plures map github on")}${app !== "default" ? ` --app ${a
|
|
|
410
619
|
};
|
|
411
620
|
try {
|
|
412
621
|
if (!action || action === "status") return show(await p.github.status(app));
|
|
622
|
+
if (action === "run") {
|
|
623
|
+
let r;
|
|
624
|
+
try {
|
|
625
|
+
r = await p.github.run(app);
|
|
626
|
+
} catch (e) {
|
|
627
|
+
if (!(e instanceof PluresError && e.type === "workflow_outdated")) throw e;
|
|
628
|
+
console.log(dim("This repo's map workflow is from before Map now. Updating it, which merges a small change to the workflow file."));
|
|
629
|
+
r = await p.github.run(app, { update: true });
|
|
630
|
+
}
|
|
631
|
+
const url = `${base}/dashboard/apps/${encodeURIComponent(app)}/map`;
|
|
632
|
+
console.log(`${bold("Mapping now on GitHub.")} Watch it build: ${cyan(url)}`);
|
|
633
|
+
if (fancy && !noOpen) await openBrowser(url);
|
|
634
|
+
return void r;
|
|
635
|
+
}
|
|
413
636
|
if (action === "on") {
|
|
414
637
|
const r = await p.github.turnOn(app);
|
|
415
638
|
console.log(r.already_merged ? "Mapping was already on." : `${bold("Mapping is on.")} The first run is starting on GitHub.`);
|
|
@@ -431,7 +654,7 @@ Turn it on: ${cyan("npx plures map github on")}${app !== "default" ? ` --app ${a
|
|
|
431
654
|
It adds one workflow file. Review it, then turn mapping on with ${cyan("npx plures map github on")}${app !== "default" ? ` --app ${app}` : ""}, or merge it on GitHub.`);
|
|
432
655
|
return;
|
|
433
656
|
}
|
|
434
|
-
die("npx plures map github [connect [owner/repo] | on | off]");
|
|
657
|
+
die("npx plures map github [connect [owner/repo] | on | run | off]");
|
|
435
658
|
} catch (e) {
|
|
436
659
|
if (e instanceof PluresError) die(e.message);
|
|
437
660
|
throw e;
|
|
@@ -448,6 +671,7 @@ async function main() {
|
|
|
448
671
|
npx plures map workflow add a GitHub Action that maps on every push
|
|
449
672
|
npx plures map github connect let Plures add that Action by pull request
|
|
450
673
|
npx plures map github on merge it, which turns mapping on
|
|
674
|
+
npx plures map github run map now on GitHub, and watch it build
|
|
451
675
|
|
|
452
676
|
Docs: https://plures.si/docs`);
|
|
453
677
|
return;
|
package/dist/cli.js
CHANGED
|
@@ -3,13 +3,187 @@ import {
|
|
|
3
3
|
Plures,
|
|
4
4
|
PluresError,
|
|
5
5
|
VERSION
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-GIOZT6FB.js";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { spawnSync, spawn } from "child_process";
|
|
10
10
|
import { randomBytes } from "crypto";
|
|
11
11
|
import { relative, isAbsolute } from "path";
|
|
12
12
|
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
13
|
+
|
|
14
|
+
// src/term.ts
|
|
15
|
+
var out = process.stdout;
|
|
16
|
+
var fancy = Boolean(out.isTTY && !process.env.NO_COLOR && !process.env.CI && process.env.TERM !== "dumb");
|
|
17
|
+
var rgb = (r, g, b) => (s) => `\x1B[38;2;${r};${g};${b}m${s}\x1B[0m`;
|
|
18
|
+
var LANES = [rgb(91, 75, 255), rgb(74, 107, 250), rgb(50, 144, 238), rgb(25, 198, 230)];
|
|
19
|
+
var C = {
|
|
20
|
+
text: rgb(243, 244, 247),
|
|
21
|
+
dim: rgb(110, 116, 136),
|
|
22
|
+
mute: rgb(160, 166, 186),
|
|
23
|
+
coral: rgb(255, 142, 122),
|
|
24
|
+
violet: rgb(185, 180, 255),
|
|
25
|
+
cyan: rgb(127, 227, 244),
|
|
26
|
+
amber: rgb(245, 184, 74),
|
|
27
|
+
bold: (s) => `\x1B[1m${s}\x1B[22m`
|
|
28
|
+
};
|
|
29
|
+
var width = () => Math.max(40, Math.min(out.columns || 80, 110));
|
|
30
|
+
var visible = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
31
|
+
var cut = (s, n) => s.length > n ? `${s.slice(0, Math.max(1, n - 1))}\u2026` : s;
|
|
32
|
+
function banner(app2, where) {
|
|
33
|
+
const bar = LANES.map((c2, i) => c2("\u2501".repeat(8 - i))).join(" ");
|
|
34
|
+
out.write(`
|
|
35
|
+
${bar}
|
|
36
|
+
|
|
37
|
+
${C.bold(C.text("Plures"))} ${C.dim("map")} ${C.mute(app2)}
|
|
38
|
+
${C.dim(where)}
|
|
39
|
+
|
|
40
|
+
`);
|
|
41
|
+
}
|
|
42
|
+
function liveLine() {
|
|
43
|
+
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
44
|
+
let i = 0, count = 0, file = "", phase = "reading", timer = null;
|
|
45
|
+
const draw = () => {
|
|
46
|
+
const spin = LANES[Math.floor(i / 2) % 4](frames[i % frames.length]);
|
|
47
|
+
i++;
|
|
48
|
+
const left = phase === "drawing" ? ` ${spin} ${C.violet("Drawing the map")} ${C.dim(`from ${count} ${count === 1 ? "file" : "files"}`)}` : ` ${spin} ${C.bold(C.text(String(count).padStart(3)))} ${C.dim(count === 1 ? "file read " : "files read")} `;
|
|
49
|
+
const room = width() - visible(left).length - 2;
|
|
50
|
+
const f = phase === "reading" && file ? (() => {
|
|
51
|
+
const slash = file.lastIndexOf("/");
|
|
52
|
+
const s = cut(file, room);
|
|
53
|
+
return slash >= 0 && s === file ? `${C.dim(file.slice(0, slash + 1))}${C.text(file.slice(slash + 1))}` : C.text(s);
|
|
54
|
+
})() : "";
|
|
55
|
+
out.write(`\r\x1B[K${left}${f}`);
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
start() {
|
|
59
|
+
out.write("\x1B[?25l");
|
|
60
|
+
draw();
|
|
61
|
+
timer = setInterval(draw, 80);
|
|
62
|
+
},
|
|
63
|
+
read(path) {
|
|
64
|
+
count++;
|
|
65
|
+
file = path;
|
|
66
|
+
},
|
|
67
|
+
drawing() {
|
|
68
|
+
phase = "drawing";
|
|
69
|
+
},
|
|
70
|
+
stop() {
|
|
71
|
+
if (timer) clearInterval(timer);
|
|
72
|
+
timer = null;
|
|
73
|
+
out.write("\r\x1B[K\x1B[?25h");
|
|
74
|
+
},
|
|
75
|
+
get count() {
|
|
76
|
+
return count;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
if (fancy) for (const sig of ["SIGINT", "SIGTERM"]) process.once(sig, () => {
|
|
81
|
+
out.write("\x1B[?25h\n");
|
|
82
|
+
process.exit(130);
|
|
83
|
+
});
|
|
84
|
+
var ICON = { input: "\u25B8", model: "\u25CE", tool: "\u2315", decision: "\u25C7", action: "\u2197", data: "\u25A4", output: "\u21A6", human: "\u263A" };
|
|
85
|
+
function drawMap(m, url) {
|
|
86
|
+
const nodes = new Map(m.nodes.map((n) => [n.id, n]));
|
|
87
|
+
const cps = new Map(m.checkpoints.filter((c2) => c2.status !== "dismissed").map((c2) => [c2.at, c2]));
|
|
88
|
+
const W = width();
|
|
89
|
+
const nb = new Map(m.nodes.map((n) => [n.id, []]));
|
|
90
|
+
for (const e of m.edges) {
|
|
91
|
+
nb.get(e.from)?.push(e.to);
|
|
92
|
+
nb.get(e.to)?.push(e.from);
|
|
93
|
+
}
|
|
94
|
+
const seen = /* @__PURE__ */ new Set(), flows = [];
|
|
95
|
+
for (const n of m.nodes) {
|
|
96
|
+
if (seen.has(n.id)) continue;
|
|
97
|
+
const part = [], st = [n.id];
|
|
98
|
+
seen.add(n.id);
|
|
99
|
+
while (st.length) {
|
|
100
|
+
const v = st.pop();
|
|
101
|
+
part.push(v);
|
|
102
|
+
for (const w of nb.get(v) ?? []) if (!seen.has(w)) {
|
|
103
|
+
seen.add(w);
|
|
104
|
+
st.push(w);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
flows.push(m.nodes.map((x) => x.id).filter((id) => part.includes(id)));
|
|
108
|
+
}
|
|
109
|
+
const chip = (id) => {
|
|
110
|
+
const n = nodes.get(id), c2 = cps.get(id);
|
|
111
|
+
const label = cut(n.label, 34);
|
|
112
|
+
const tag = !c2 ? "" : c2.status === "added" ? ` ${C.cyan("\u2713 checked")}` : ` ${C.violet("\u25CC check suggested")}`;
|
|
113
|
+
if (n.kind === "action") {
|
|
114
|
+
if (c2?.status === "added") return `${C.cyan(ICON.action)} ${C.text(label)}${tag}`;
|
|
115
|
+
const risky = n.risk === "high" || n.risk === "medium";
|
|
116
|
+
return `${risky ? C.coral(ICON.action) : C.mute(ICON.action)} ${risky ? C.coral(label) : C.mute(label)}${tag}`;
|
|
117
|
+
}
|
|
118
|
+
return `${C.dim(ICON[n.kind] ?? "\xB7")} ${C.mute(label)}${tag}`;
|
|
119
|
+
};
|
|
120
|
+
out.write("\n");
|
|
121
|
+
for (const ids of flows.filter((f) => f.length > 1)) {
|
|
122
|
+
const set = new Set(ids);
|
|
123
|
+
const es = m.edges.filter((e) => set.has(e.from) && set.has(e.to));
|
|
124
|
+
const layer = new Map(ids.map((id) => [id, 0]));
|
|
125
|
+
const order = [], state = /* @__PURE__ */ new Map(), back = /* @__PURE__ */ new Set();
|
|
126
|
+
const visit = (v) => {
|
|
127
|
+
state.set(v, 1);
|
|
128
|
+
for (const e of es.filter((x) => x.from === v)) {
|
|
129
|
+
if (state.get(e.to) === 1) back.add(`${v}>${e.to}`);
|
|
130
|
+
else if (!state.get(e.to)) visit(e.to);
|
|
131
|
+
}
|
|
132
|
+
state.set(v, 2);
|
|
133
|
+
order.push(v);
|
|
134
|
+
};
|
|
135
|
+
for (const id of ids) if (!state.get(id)) visit(id);
|
|
136
|
+
for (const v of order.reverse()) for (const e of es) if (e.from === v && !back.has(`${v}>${e.to}`)) layer.set(e.to, Math.max(layer.get(e.to), layer.get(v) + 1));
|
|
137
|
+
const rows = [];
|
|
138
|
+
for (const id of ids) (rows[layer.get(id)] ??= []).push(id);
|
|
139
|
+
const root = rows[0]?.length === 1 ? rows[0][0] : null;
|
|
140
|
+
out.write(` ${LANES[flows.indexOf(ids) % 4]("\u25CF")} ${C.bold(C.text(cut(root ? nodes.get(root).label : "Flow", W - 6)))}
|
|
141
|
+
`);
|
|
142
|
+
rows.forEach((row, ri) => {
|
|
143
|
+
if (!row || ri === 0 && root) return;
|
|
144
|
+
const lines = [[]];
|
|
145
|
+
let used = 6;
|
|
146
|
+
for (const id of row) {
|
|
147
|
+
const w = visible(chip(id)).length + 4;
|
|
148
|
+
if (used + w > W && lines[lines.length - 1].length) {
|
|
149
|
+
lines.push([]);
|
|
150
|
+
used = 6;
|
|
151
|
+
}
|
|
152
|
+
lines[lines.length - 1].push(id);
|
|
153
|
+
used += w;
|
|
154
|
+
}
|
|
155
|
+
for (const ln of lines) out.write(` ${C.dim("\u2502")} ${ln.map((id) => chip(id)).join(C.dim(" \xB7 "))}
|
|
156
|
+
`);
|
|
157
|
+
});
|
|
158
|
+
out.write("\n");
|
|
159
|
+
}
|
|
160
|
+
const actions = m.nodes.filter((n) => n.kind === "action");
|
|
161
|
+
const on = actions.filter((a) => cps.get(a.id)?.status === "added").length;
|
|
162
|
+
const sugg = m.checkpoints.filter((c2) => c2.status === "suggested").length;
|
|
163
|
+
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."))}
|
|
164
|
+
`);
|
|
165
|
+
out.write(` ${C.mute(`${on} of ${actions.length} ${actions.length === 1 ? "action" : "actions"} with consequences ${on === 1 ? "has" : "have"} a human check.`)}
|
|
166
|
+
|
|
167
|
+
`);
|
|
168
|
+
out.write(` ${C.text("See it build:")} ${C.cyan(url)}
|
|
169
|
+
|
|
170
|
+
`);
|
|
171
|
+
}
|
|
172
|
+
async function openBrowser(url) {
|
|
173
|
+
const { spawn: spawn2 } = await import("child_process");
|
|
174
|
+
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
175
|
+
try {
|
|
176
|
+
const p = spawn2(cmd, args, { stdio: "ignore", detached: true });
|
|
177
|
+
p.on("error", () => {
|
|
178
|
+
});
|
|
179
|
+
p.unref();
|
|
180
|
+
return true;
|
|
181
|
+
} catch {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/cli.ts
|
|
13
187
|
var argv = process.argv.slice(2);
|
|
14
188
|
var flag = (name) => {
|
|
15
189
|
const i = argv.indexOf(`--${name}`);
|
|
@@ -19,6 +193,7 @@ var flag = (name) => {
|
|
|
19
193
|
return v;
|
|
20
194
|
};
|
|
21
195
|
var branch = flag("branch");
|
|
196
|
+
var noOpen = argv.includes("--no-open") ? (argv.splice(argv.indexOf("--no-open"), 1), true) : false;
|
|
22
197
|
var app = flag("app") ?? process.env.PLURES_APP ?? "default";
|
|
23
198
|
var key = flag("key") ?? process.env.PLURES_API_KEY;
|
|
24
199
|
var base = flag("base-url") ?? process.env.PLURES_BASE_URL ?? "https://plures.si";
|
|
@@ -46,14 +221,15 @@ async function upload(file) {
|
|
|
46
221
|
} catch {
|
|
47
222
|
die(`${file} isn't valid JSON.`);
|
|
48
223
|
}
|
|
49
|
-
console.log(dim(`Sending the map to Plures. No source code is sent.`));
|
|
224
|
+
if (!fancy) console.log(dim(`Sending the map to Plures. No source code is sent.`));
|
|
50
225
|
let m;
|
|
51
226
|
try {
|
|
52
227
|
m = await client().maps.upload(app, map2);
|
|
53
228
|
} catch (e) {
|
|
54
229
|
die(e instanceof PluresError ? `Plures said: ${e.message}` : String(e));
|
|
55
230
|
}
|
|
56
|
-
|
|
231
|
+
if (fancy) drawMap(m, m.url ?? `${base}/dashboard/apps/${m.app}/map`);
|
|
232
|
+
else report(m);
|
|
57
233
|
}
|
|
58
234
|
function report(m) {
|
|
59
235
|
const nodes = new Map(m.nodes.map((n) => [n.id, n]));
|
|
@@ -103,7 +279,12 @@ With plures-mcp connected, just ask: ${cyan("Map this app for Plures")}`);
|
|
|
103
279
|
childEnv.ANTHROPIC_API_KEY = run.token;
|
|
104
280
|
console.log(dim(run.free ? "Plures is mapping this repo. Your first map is free." : `Plures is mapping this repo, at most $${(run.max_cents / 100).toFixed(2)} from your credit.`));
|
|
105
281
|
}
|
|
106
|
-
|
|
282
|
+
const pageUrl = `${base}/dashboard/apps/${encodeURIComponent(app)}/map`;
|
|
283
|
+
if (fancy) {
|
|
284
|
+
banner(app, "Reading this repo on your machine with Claude Code. Only file names and the map go to Plures.");
|
|
285
|
+
if (key && !noOpen && await openBrowser(pageUrl)) console.log(` ${dim("Watch it live in your browser:")} ${cyan(pageUrl)}
|
|
286
|
+
`);
|
|
287
|
+
} 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.")}
|
|
107
288
|
`);
|
|
108
289
|
const prompt = `${guide}
|
|
109
290
|
|
|
@@ -131,6 +312,8 @@ Write the map to ${FILE} in the current directory now. Read the code; don't run
|
|
|
131
312
|
return r.startsWith("..") ? null : r.replace(/\\/g, "/");
|
|
132
313
|
};
|
|
133
314
|
let result = "";
|
|
315
|
+
const ticker = fancy ? liveLine() : null;
|
|
316
|
+
ticker?.start();
|
|
134
317
|
const code = await new Promise((resolve) => {
|
|
135
318
|
const p = spawn("claude", ["-p", prompt, "--output-format", "stream-json", "--verbose", "--allowedTools", "Read,Grep,Glob,Write,Bash(git rev-parse:*),Bash(git remote:*)"], { stdio: ["ignore", "pipe", "inherit"], env: childEnv });
|
|
136
319
|
let buf = "";
|
|
@@ -154,10 +337,12 @@ Write the map to ${FILE} in the current directory now. Read the code; don't run
|
|
|
154
337
|
if (!f) continue;
|
|
155
338
|
reads++;
|
|
156
339
|
queue.push({ path: f, tool: "Read" });
|
|
157
|
-
|
|
340
|
+
if (ticker) ticker.read(f);
|
|
341
|
+
else console.log(` ${dim(String(reads).padStart(3))} ${dim("reading")} ${f}`);
|
|
158
342
|
} else if (b.name === "Write" && String(b.input?.file_path ?? "").endsWith(FILE)) {
|
|
159
343
|
phase = "drawing";
|
|
160
|
-
|
|
344
|
+
if (ticker) ticker.drawing();
|
|
345
|
+
else console.log(`
|
|
161
346
|
${violet("drawing")} the map`);
|
|
162
347
|
}
|
|
163
348
|
}
|
|
@@ -167,9 +352,11 @@ Write the map to ${FILE} in the current directory now. Read the code; don't run
|
|
|
167
352
|
p.on("error", () => resolve(1));
|
|
168
353
|
});
|
|
169
354
|
clearInterval(tick);
|
|
170
|
-
|
|
355
|
+
ticker?.stop();
|
|
356
|
+
if (result && !fancy) console.log(`
|
|
171
357
|
${result.trim()}
|
|
172
358
|
`);
|
|
359
|
+
if (fancy) console.log(` ${cyan("\u2713")} ${reads} ${reads === 1 ? "file" : "files"} read. ${dim("Sending the map to Plures. No source code is sent.")}`);
|
|
173
360
|
const ok = code === 0 && existsSync(FILE) && statSync(FILE).mtimeMs !== before;
|
|
174
361
|
if (!ok) {
|
|
175
362
|
phase = "failed";
|
|
@@ -214,6 +401,20 @@ Turn it on: ${cyan("npx plures map github on")}${app !== "default" ? ` --app ${a
|
|
|
214
401
|
};
|
|
215
402
|
try {
|
|
216
403
|
if (!action || action === "status") return show(await p.github.status(app));
|
|
404
|
+
if (action === "run") {
|
|
405
|
+
let r;
|
|
406
|
+
try {
|
|
407
|
+
r = await p.github.run(app);
|
|
408
|
+
} catch (e) {
|
|
409
|
+
if (!(e instanceof PluresError && e.type === "workflow_outdated")) throw e;
|
|
410
|
+
console.log(dim("This repo's map workflow is from before Map now. Updating it, which merges a small change to the workflow file."));
|
|
411
|
+
r = await p.github.run(app, { update: true });
|
|
412
|
+
}
|
|
413
|
+
const url = `${base}/dashboard/apps/${encodeURIComponent(app)}/map`;
|
|
414
|
+
console.log(`${bold("Mapping now on GitHub.")} Watch it build: ${cyan(url)}`);
|
|
415
|
+
if (fancy && !noOpen) await openBrowser(url);
|
|
416
|
+
return void r;
|
|
417
|
+
}
|
|
217
418
|
if (action === "on") {
|
|
218
419
|
const r = await p.github.turnOn(app);
|
|
219
420
|
console.log(r.already_merged ? "Mapping was already on." : `${bold("Mapping is on.")} The first run is starting on GitHub.`);
|
|
@@ -235,7 +436,7 @@ Turn it on: ${cyan("npx plures map github on")}${app !== "default" ? ` --app ${a
|
|
|
235
436
|
It adds one workflow file. Review it, then turn mapping on with ${cyan("npx plures map github on")}${app !== "default" ? ` --app ${app}` : ""}, or merge it on GitHub.`);
|
|
236
437
|
return;
|
|
237
438
|
}
|
|
238
|
-
die("npx plures map github [connect [owner/repo] | on | off]");
|
|
439
|
+
die("npx plures map github [connect [owner/repo] | on | run | off]");
|
|
239
440
|
} catch (e) {
|
|
240
441
|
if (e instanceof PluresError) die(e.message);
|
|
241
442
|
throw e;
|
|
@@ -252,6 +453,7 @@ async function main() {
|
|
|
252
453
|
npx plures map workflow add a GitHub Action that maps on every push
|
|
253
454
|
npx plures map github connect let Plures add that Action by pull request
|
|
254
455
|
npx plures map github on merge it, which turns mapping on
|
|
456
|
+
npx plures map github run map now on GitHub, and watch it build
|
|
255
457
|
|
|
256
458
|
Docs: https://plures.si/docs`);
|
|
257
459
|
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.13.0";
|
|
30
30
|
var PluresError = class extends Error {
|
|
31
31
|
constructor(message, status, type) {
|
|
32
32
|
super(message);
|
|
@@ -157,6 +157,8 @@ var Plures = class {
|
|
|
157
157
|
/** Connects a repo ("owner/name"): sets its map key secret and opens the pull request. */
|
|
158
158
|
connect: (app, repo) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/github`, { repo }),
|
|
159
159
|
/** Turns mapping on by merging the pull request. The repo's own rules (reviews, checks) still apply. */
|
|
160
|
+
/** Map now: runs the app's map workflow on GitHub. { update: true } first updates an older workflow. Watch it on the map page. */
|
|
161
|
+
run: (app, opts = {}) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/github/run`, opts),
|
|
160
162
|
turnOn: (app) => this.req("POST", `/api/v1/apps/${encodeURIComponent(app)}/github/merge`, {}),
|
|
161
163
|
/** Revokes the repo's map key. Delete the workflow file in the repo to stop runs. */
|
|
162
164
|
disconnect: (app) => this.req("DELETE", `/api/v1/apps/${encodeURIComponent(app)}/github`)
|
package/dist/index.d.cts
CHANGED
|
@@ -350,7 +350,7 @@ type WebhookEvent = {
|
|
|
350
350
|
created_at: string;
|
|
351
351
|
data: Task;
|
|
352
352
|
};
|
|
353
|
-
declare const VERSION = "1.
|
|
353
|
+
declare const VERSION = "1.13.0";
|
|
354
354
|
type GitHubLink = {
|
|
355
355
|
object: "github_link";
|
|
356
356
|
app: string;
|
|
@@ -550,6 +550,15 @@ declare class Plures {
|
|
|
550
550
|
/** Connects a repo ("owner/name"): sets its map key secret and opens the pull request. */
|
|
551
551
|
connect: (app: string, repo: string) => Promise<GitHubLink>;
|
|
552
552
|
/** Turns mapping on by merging the pull request. The repo's own rules (reviews, checks) still apply. */
|
|
553
|
+
/** Map now: runs the app's map workflow on GitHub. { update: true } first updates an older workflow. Watch it on the map page. */
|
|
554
|
+
run: (app: string, opts?: {
|
|
555
|
+
update?: boolean;
|
|
556
|
+
}) => Promise<{
|
|
557
|
+
object: "map_run_request";
|
|
558
|
+
app: string;
|
|
559
|
+
started: true;
|
|
560
|
+
updated_workflow: boolean;
|
|
561
|
+
}>;
|
|
553
562
|
turnOn: (app: string) => Promise<{
|
|
554
563
|
object: "github_merge";
|
|
555
564
|
app: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -350,7 +350,7 @@ type WebhookEvent = {
|
|
|
350
350
|
created_at: string;
|
|
351
351
|
data: Task;
|
|
352
352
|
};
|
|
353
|
-
declare const VERSION = "1.
|
|
353
|
+
declare const VERSION = "1.13.0";
|
|
354
354
|
type GitHubLink = {
|
|
355
355
|
object: "github_link";
|
|
356
356
|
app: string;
|
|
@@ -550,6 +550,15 @@ declare class Plures {
|
|
|
550
550
|
/** Connects a repo ("owner/name"): sets its map key secret and opens the pull request. */
|
|
551
551
|
connect: (app: string, repo: string) => Promise<GitHubLink>;
|
|
552
552
|
/** Turns mapping on by merging the pull request. The repo's own rules (reviews, checks) still apply. */
|
|
553
|
+
/** Map now: runs the app's map workflow on GitHub. { update: true } first updates an older workflow. Watch it on the map page. */
|
|
554
|
+
run: (app: string, opts?: {
|
|
555
|
+
update?: boolean;
|
|
556
|
+
}) => Promise<{
|
|
557
|
+
object: "map_run_request";
|
|
558
|
+
app: string;
|
|
559
|
+
started: true;
|
|
560
|
+
updated_workflow: boolean;
|
|
561
|
+
}>;
|
|
553
562
|
turnOn: (app: string) => Promise<{
|
|
554
563
|
object: "github_merge";
|
|
555
564
|
app: string;
|
package/dist/index.js
CHANGED