plotcoder-board 0.1.37 → 0.1.38
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 +1 -1
- package/package.json +1 -1
- package/scripts/plotcoder-mcp-server.mjs +50 -2
package/README.md
CHANGED
|
@@ -107,4 +107,4 @@ next round into a test of `claim_account` instead of the door it meant to test.
|
|
|
107
107
|
|
|
108
108
|
## Status
|
|
109
109
|
|
|
110
|
-
Version 0.1.
|
|
110
|
+
Version 0.1.38. A project of boards; sign in with your email and a password from the PlotCoder mark and your projects follow you to every device, share one with another writer by email and write it together live, or stay signed out and work on this device as before. Pages sit beside the wall: a scene's text lives on its card, measures it, paginates to the industry's rules, prints, goes out and comes in as Fountain or Final Draft, and goes out as Markdown or plain text for a collaborator in Google Docs. It installs as a progressive web app and opens offline; plotcoder.com serves over HTTPS. The wall, beats, card length, groups, arrows, pan and zoom, save and open, and the agent surface are in use.
|
package/package.json
CHANGED
|
@@ -835,7 +835,7 @@ function changeNote() {
|
|
|
835
835
|
const parts = [];
|
|
836
836
|
if (change.gone.length || change.came.length) {
|
|
837
837
|
parts.push(
|
|
838
|
-
`the wall now asks ${change.asks} question${change.asks === 1 ? "" : "s"}${change.gone.length ? ` (gone: ${change.gone.map((finding) => `[${finding.kind}] ${finding.text}`).join(" ")})` : ""}${change.came.length ? ` (new: ${change.came.map((finding) => `[${finding.kind}] ${finding.text}`).join(" ")})` : ""}`,
|
|
838
|
+
`the wall now asks ${change.asks} question${change.asks === 1 ? "" : "s"}${change.gone.length ? ` (gone: ${change.gone.map((finding) => `[${finding.kind}] ${finding.text.replace(/\.$/, "")}`).join(" ")})` : ""}${change.came.length ? ` (new: ${change.came.map((finding) => `[${finding.kind}] ${finding.text.replace(/\.$/, "")}`).join(" ")})` : ""}`,
|
|
839
839
|
);
|
|
840
840
|
}
|
|
841
841
|
if (change.leftAfter !== change.leftBefore) parts.push(`left, for now: ${change.leftAfter} (was ${change.leftBefore})`);
|
|
@@ -1982,7 +1982,6 @@ server.registerTool(
|
|
|
1982
1982
|
const poses = organizePoses(state, { onlyIds: args.noteIds });
|
|
1983
1983
|
if (poses.length === 0) return ok("Nothing to organize: no cards in scope.");
|
|
1984
1984
|
const { changed, live } = await commit({ type: "apply_poses", poses });
|
|
1985
|
-
if (!changed) return ok("Nothing moved.");
|
|
1986
1985
|
const rows = new Set(poses.map((pose) => pose.y)).size;
|
|
1987
1986
|
const beats = state.notes.filter(
|
|
1988
1987
|
(note) => note.rank === "beat" && poses.some((pose) => pose.id === note.id),
|
|
@@ -2004,6 +2003,8 @@ server.registerTool(
|
|
|
2004
2003
|
const shape = beats
|
|
2005
2004
|
? `${opening ? `an opening row of ${opening} card(s) before the first beat, then ` : ""}${beats} row(s), one per beat${wrappedUnder.length ? `; ${wrappedUnder.map((item) => `the row of "${item.beat.headline}" wraps under from "${item.first.headline}", indented under the row's first scene, never under the beat`).join(", ")}` : ""}`
|
|
2006
2005
|
: `${rows} row(s) five cards wide — no beats yet, so nothing sets the rows; set_rank the turns and organize again for a row per beat`;
|
|
2006
|
+
// Nothing moved says what already stands (round fifteen, entry 20): after a rank change the agent asked whether a beat row still held.
|
|
2007
|
+
if (!changed) return ok(`Nothing moved: the ${poses.length} card(s) already lie along the arrows in ${shape}.`, poses);
|
|
2007
2008
|
return ok(`Organized ${poses.length} card(s) along the arrows into ${shape}${where(live)}.`, poses);
|
|
2008
2009
|
},
|
|
2009
2010
|
);
|
|
@@ -3512,6 +3513,53 @@ function describeBoards(project, boards, changedAt = null) {
|
|
|
3512
3513
|
.join("\n");
|
|
3513
3514
|
}
|
|
3514
3515
|
|
|
3516
|
+
/**
|
|
3517
|
+
* The project as a whole (round fifteen, entry 14; the handover's first
|
|
3518
|
+
* call): every board's questions in one reading, with what each board
|
|
3519
|
+
* leaves open, the folds that pay off on another board, and the project's
|
|
3520
|
+
* length. Each board is read as read_wall reads it — the same checks, the
|
|
3521
|
+
* same cross-board context — so this never says something read_wall would
|
|
3522
|
+
* not; it only says it for every board at once.
|
|
3523
|
+
*/
|
|
3524
|
+
server.registerTool(
|
|
3525
|
+
"read_project",
|
|
3526
|
+
{
|
|
3527
|
+
title: "Read the project",
|
|
3528
|
+
description:
|
|
3529
|
+
"Every board's reading in one call: for each board in the writer's order, its logline, its runtime, the questions it asks and what it leaves open by the writer's word — the same checks read_wall runs, board by board — then the folds that pay off on another board and the project's length. The records — cards, ids, cast, places — are list_board's, board by board; open_board and read_wall for one board in full.",
|
|
3530
|
+
inputSchema: {},
|
|
3531
|
+
},
|
|
3532
|
+
async () => {
|
|
3533
|
+
const { project, boards, live, base } = await readProject();
|
|
3534
|
+
const states = new Map(project.boards.map((meta) => [meta.id, isBoardState(boards[meta.id]) ? normalizeState(boards[meta.id]) : emptyState()]));
|
|
3535
|
+
const boardsNow = Object.fromEntries(states);
|
|
3536
|
+
const lines = [
|
|
3537
|
+
`PlotCoder project "${project.name}"${project.nameOpen ? ` — its name is open, by the writer's word: "${project.nameOpen}"` : ""} (${door(live, base)})`,
|
|
3538
|
+
`premise: ${project.premiseOpen ? `open, by the writer's word — "${project.premiseOpen}"` : project.premise ? `"${project.premise}"` : "(not set)"}`,
|
|
3539
|
+
];
|
|
3540
|
+
let asked = 0;
|
|
3541
|
+
for (const [index, meta] of project.boards.entries()) {
|
|
3542
|
+
const state = states.get(meta.id);
|
|
3543
|
+
const reading = readWall(state, { elsewhere: Object.keys(castElsewhere(project, boardsNow, meta.id)), laterBoards: laterBoards(project, boardsNow), paidBy: landingsOn(project, boardsNow, meta.id).paid });
|
|
3544
|
+
asked += reading.findings.length;
|
|
3545
|
+
const open = reading.open.length + reading.openFields.length + (meta.nameOpen ? 1 : 0);
|
|
3546
|
+
const headline = (id) => `"${state.notes.find((note) => note.id === id)?.headline ?? id}"`;
|
|
3547
|
+
lines.push(
|
|
3548
|
+
`${index + 1}. "${meta.name}"${meta.nameOpen ? ` (name open: "${meta.nameOpen}")` : ""}${meta.id === project.activeBoardId ? " (open)" : ""} — ${state.notes.length} card${state.notes.length === 1 ? "" : "s"}, ${reading.beats.length} beat${reading.beats.length === 1 ? "" : "s"}, about ${formatPages(boardEighths(state))} of ${formatPages(state.targetEighths)} pages; logline: ${state.loglineOpen ? `open — "${state.loglineOpen}"` : state.logline ? `"${state.logline}"` : "(none yet)"}`,
|
|
3549
|
+
...(reading.findings.length ? reading.findings.map((finding) => ` - [${finding.kind}] ${finding.text}`) : [` (asks nothing${reading.left.length ? `; ${reading.left.length} left by the writer` : ""}${state.notes.length ? "" : ": no cards yet"})`]),
|
|
3550
|
+
...(open ? [` open by the writer's word: ${[reading.open.length ? `${reading.open.length} card${reading.open.length === 1 ? "" : "s"}` : "", reading.openFields.length ? `${reading.openFields.length} field${reading.openFields.length === 1 ? "" : "s"}` : "", meta.nameOpen ? "the board's name" : ""].filter(Boolean).join(", ")} — read_wall there lists them`] : []),
|
|
3551
|
+
...reading.threads.filter((thread) => thread.startOpen || thread.endOpen).map((thread) => ` thread "${thread.name}" — ${thread.startOpen ? "starts nowhere yet" : ""}${thread.startOpen && thread.endOpen ? ", " : ""}${thread.endOpen ? "ends nowhere yet" : ""}`),
|
|
3552
|
+
...reading.later.map((item) => ` "${headline(item.id)}" is folded and pays off later, on "${boardById(project, item.boardId)?.name ?? item.boardId}"${item.noteId ? `, at "${boardsNow[item.boardId]?.notes?.find((note) => note.id === item.noteId)?.headline ?? item.noteId}"` : ", no scene there claimed yet"}`),
|
|
3553
|
+
);
|
|
3554
|
+
}
|
|
3555
|
+
const pages = [...states.values()].reduce((sum, state) => sum + boardEighths(state), 0);
|
|
3556
|
+
const target = [...states.values()].reduce((sum, state) => sum + state.targetEighths, 0);
|
|
3557
|
+
const cards = [...states.values()].reduce((sum, state) => sum + state.notes.length, 0);
|
|
3558
|
+
lines.push(`the whole project: ${cards} card${cards === 1 ? "" : "s"}, about ${formatPages(pages)} of ${formatPages(target)} pages across ${project.boards.length} board${project.boards.length === 1 ? "" : "s"}; ${asked} question${asked === 1 ? "" : "s"} in all (each board's runtime is an estimate unless every scene is written)`);
|
|
3559
|
+
return ok(lines.join("\n"), { project: { id: project.id, name: project.name }, boards: project.boards.map((meta) => meta.id), asked });
|
|
3560
|
+
},
|
|
3561
|
+
);
|
|
3562
|
+
|
|
3515
3563
|
server.registerTool(
|
|
3516
3564
|
"list_boards",
|
|
3517
3565
|
{
|