paper-mono 0.62.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/CHANGELOG.md +21 -0
- package/DEPENDENCIES.json +143 -0
- package/LICENSE +21 -0
- package/README.md +138 -0
- package/SAFETY.md +27 -0
- package/THIRD_PARTY_NOTICES.md +244 -0
- package/bin/chunks/chunk-BGKLQ5Y6.js +55 -0
- package/bin/chunks/chunk-I4P43IZS.js +171 -0
- package/bin/chunks/chunk-JCO37BXY.js +31 -0
- package/bin/chunks/chunk-UHVY2TIH.js +3264 -0
- package/bin/chunks/chunk-ZX4GFXSY.js +37 -0
- package/bin/chunks/doctor-7DE4ASQO.js +355 -0
- package/bin/chunks/main-NQIGPQXK.js +23401 -0
- package/bin/chunks/owner-commands-MWXW2KMM.js +403 -0
- package/bin/chunks/tool-contract-ZWFZSYL4.js +32 -0
- package/bin/paper.js +258 -0
- package/dist/modes/interactive/theme/dark.json +85 -0
- package/dist/modes/interactive/theme/light.json +84 -0
- package/docs/cli.md +122 -0
- package/docs/paper-mcp.md +33 -0
- package/docs/runtime.md +54 -0
- package/package.json +79 -0
- package/tool-walk/fixtures.json +584 -0
- package/tool-walk/mono-safety-card.json +920 -0
- package/tool-walk/safety-fixtures.json +329 -0
- package/tools.contract.json +1748 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import {
|
|
2
|
+
invalidPaperArgument,
|
|
3
|
+
safeArgumentForDiagnostic
|
|
4
|
+
} from "./chunk-JCO37BXY.js";
|
|
5
|
+
import "./chunk-ZX4GFXSY.js";
|
|
6
|
+
|
|
7
|
+
// src/owner-commands.ts
|
|
8
|
+
import { writeFile } from "node:fs/promises";
|
|
9
|
+
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
10
|
+
var PAPER_MCP_URL = "http://127.0.0.1:29979/mcp";
|
|
11
|
+
var PAPER_MCP_TIMEOUT_MS = 3e4;
|
|
12
|
+
var PAPER_NODE_PLACEHOLDER = "replace-with-node-id";
|
|
13
|
+
var PAPER_NO_SCREENSHOT_SELECTION = "No Paper node is selected. Select exactly one node in Paper or pass --node <node-id>.";
|
|
14
|
+
var PAPER_AMBIGUOUS_SCREENSHOT_SELECTION = "Multiple Paper nodes are selected. Select exactly one node in Paper or pass --node <node-id>.";
|
|
15
|
+
var OWNER_DESKTOP_TOOL_NAMES = Object.freeze({
|
|
16
|
+
paper_get_basic_info: "get_basic_info",
|
|
17
|
+
paper_get_selection: "get_selection",
|
|
18
|
+
paper_get_node_info: "get_node_info",
|
|
19
|
+
paper_get_children: "get_children",
|
|
20
|
+
paper_get_tree_summary: "get_tree_summary",
|
|
21
|
+
paper_get_screenshot: "get_screenshot",
|
|
22
|
+
paper_get_jsx: "get_jsx"
|
|
23
|
+
});
|
|
24
|
+
var SUPPORTED_IMAGE_MIME_TYPES = /* @__PURE__ */ new Set(["image/png", "image/jpeg", "image/webp"]);
|
|
25
|
+
var STRICT_BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
26
|
+
var ownerRequestId = 1;
|
|
27
|
+
var READ_COMMANDS = /* @__PURE__ */ new Set(["show", "info", "selection", "tree", "node", "screenshot"]);
|
|
28
|
+
var READ_COMMAND_HELP = {
|
|
29
|
+
show: "Usage: paper show [--json]\nShow basic information about the open Paper document.\n",
|
|
30
|
+
info: "Usage: paper info [--json]\nAlias for `paper show`.\n",
|
|
31
|
+
selection: "Usage: paper selection [--json]\nShow the current Paper selection.\n",
|
|
32
|
+
tree: "Usage: paper tree [--json]\nShow a bounded summary of the open document tree.\n",
|
|
33
|
+
node: "Usage: paper node <nodeId> [--children | --jsx] [--json]\nInspect one Paper node.\n",
|
|
34
|
+
screenshot: "Usage: paper screenshot --out <file> [--node <nodeId>] [--json]\nSave a node or single-selection screenshot using Paper's returned image format.\n"
|
|
35
|
+
};
|
|
36
|
+
function providerError(result) {
|
|
37
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) return void 0;
|
|
38
|
+
const record = result;
|
|
39
|
+
if (record.isError !== true || !Array.isArray(record.content)) return void 0;
|
|
40
|
+
const text = record.content.filter(
|
|
41
|
+
(part) => Boolean(part) && typeof part === "object" && part.type === "text" && typeof part.text === "string"
|
|
42
|
+
).map((part) => part.text).join("\n");
|
|
43
|
+
return text || "Paper MCP reported an error.";
|
|
44
|
+
}
|
|
45
|
+
async function callPaperOwnerMcp(toolName, args, signal, timeoutMs = PAPER_MCP_TIMEOUT_MS) {
|
|
46
|
+
const desktopToolName = OWNER_DESKTOP_TOOL_NAMES[toolName];
|
|
47
|
+
if (!desktopToolName) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
error: `Unsupported deterministic Paper tool: ${toolName}`,
|
|
51
|
+
failure: "contract",
|
|
52
|
+
desktopToolName: toolName
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const id = ownerRequestId++;
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
58
|
+
const abort = () => controller.abort();
|
|
59
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
60
|
+
let endpointReached = false;
|
|
61
|
+
try {
|
|
62
|
+
const response = await fetch(PAPER_MCP_URL, {
|
|
63
|
+
method: "POST",
|
|
64
|
+
headers: { "Content-Type": "application/json", Accept: "application/json, text/event-stream" },
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
jsonrpc: "2.0",
|
|
67
|
+
method: "tools/call",
|
|
68
|
+
params: { name: desktopToolName, arguments: args },
|
|
69
|
+
id
|
|
70
|
+
}),
|
|
71
|
+
signal: controller.signal
|
|
72
|
+
});
|
|
73
|
+
endpointReached = true;
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
error: `Paper MCP returned ${response.status} ${response.statusText}`,
|
|
78
|
+
failure: "contract",
|
|
79
|
+
desktopToolName
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const body = await response.text();
|
|
83
|
+
const dataMatch = body.match(/^data: (.+)$/m);
|
|
84
|
+
const rpc = JSON.parse(dataMatch?.[1] ?? body);
|
|
85
|
+
if (rpc.jsonrpc !== "2.0" || rpc.id !== id) {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
error: `Paper MCP returned an invalid JSON-RPC envelope for ${desktopToolName}.`,
|
|
89
|
+
failure: "contract",
|
|
90
|
+
desktopToolName
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
if (rpc.error) {
|
|
94
|
+
return {
|
|
95
|
+
ok: false,
|
|
96
|
+
error: `Paper MCP error calling ${desktopToolName}: ${rpc.error.message ?? "unknown error"}`,
|
|
97
|
+
failure: "contract",
|
|
98
|
+
desktopToolName
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const reportedError = providerError(rpc.result);
|
|
102
|
+
if (reportedError) {
|
|
103
|
+
return {
|
|
104
|
+
ok: false,
|
|
105
|
+
error: `Paper MCP tool ${desktopToolName} failed: ${reportedError}`,
|
|
106
|
+
failure: "contract",
|
|
107
|
+
desktopToolName
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return { ok: true, result: rpc.result, desktopToolName };
|
|
111
|
+
} catch (error) {
|
|
112
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
113
|
+
return endpointReached ? {
|
|
114
|
+
ok: false,
|
|
115
|
+
error: `Paper MCP contract error calling ${desktopToolName}: ${message}`,
|
|
116
|
+
failure: "contract",
|
|
117
|
+
desktopToolName
|
|
118
|
+
} : {
|
|
119
|
+
ok: false,
|
|
120
|
+
error: `Paper Desktop is not running. Open a file in Paper Desktop to start the MCP server (${PAPER_MCP_URL}).`,
|
|
121
|
+
failure: "unreachable",
|
|
122
|
+
desktopToolName
|
|
123
|
+
};
|
|
124
|
+
} finally {
|
|
125
|
+
clearTimeout(timeout);
|
|
126
|
+
signal?.removeEventListener("abort", abort);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function assertPaperResult(toolName, result) {
|
|
130
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) {
|
|
131
|
+
throw new TypeError(`Paper ${toolName} returned an invalid result envelope.`);
|
|
132
|
+
}
|
|
133
|
+
const record = result;
|
|
134
|
+
if (!Array.isArray(record.content) || record.content.length === 0) {
|
|
135
|
+
throw new TypeError(`Paper ${toolName} returned invalid or empty content.`);
|
|
136
|
+
}
|
|
137
|
+
for (const item of record.content) {
|
|
138
|
+
if (!item || typeof item !== "object") {
|
|
139
|
+
throw new TypeError(`Paper ${toolName} returned a malformed content part.`);
|
|
140
|
+
}
|
|
141
|
+
const entry = item;
|
|
142
|
+
if (entry.type === "text" && typeof entry.text === "string") continue;
|
|
143
|
+
if (entry.type === "image" && typeof entry.data === "string" && typeof entry.mimeType === "string" && SUPPORTED_IMAGE_MIME_TYPES.has(entry.mimeType)) {
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
throw new TypeError(`Paper ${toolName} returned an unsupported content part.`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
function paperContentParts(result) {
|
|
150
|
+
if (!result || typeof result !== "object") return [];
|
|
151
|
+
const content = result.content;
|
|
152
|
+
if (!Array.isArray(content)) return [];
|
|
153
|
+
const parts = [];
|
|
154
|
+
for (const item of content) {
|
|
155
|
+
if (!item || typeof item !== "object") continue;
|
|
156
|
+
const entry = item;
|
|
157
|
+
if (entry.type === "text" && typeof entry.text === "string") {
|
|
158
|
+
parts.push({ type: "text", text: entry.text });
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (entry.type === "image" && typeof entry.data === "string" && typeof entry.mimeType === "string") {
|
|
162
|
+
parts.push({
|
|
163
|
+
type: "image",
|
|
164
|
+
data: entry.data,
|
|
165
|
+
mimeType: entry.mimeType
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return parts;
|
|
170
|
+
}
|
|
171
|
+
function paperText(result) {
|
|
172
|
+
const text = paperContentParts(result).filter((part) => part.type === "text").map((part) => part.text).join("\n");
|
|
173
|
+
return text || JSON.stringify(result, null, 2);
|
|
174
|
+
}
|
|
175
|
+
function selectedNodeIds(result) {
|
|
176
|
+
let selection;
|
|
177
|
+
try {
|
|
178
|
+
selection = JSON.parse(paperText(result));
|
|
179
|
+
} catch {
|
|
180
|
+
throw new TypeError("Paper get_selection returned invalid JSON for screenshot targeting.");
|
|
181
|
+
}
|
|
182
|
+
if (!selection || typeof selection !== "object" || Array.isArray(selection)) {
|
|
183
|
+
throw new TypeError("Paper get_selection returned an invalid screenshot-selection payload.");
|
|
184
|
+
}
|
|
185
|
+
const nodes = selection.selectedNodes;
|
|
186
|
+
if (!Array.isArray(nodes)) {
|
|
187
|
+
throw new TypeError("Paper get_selection returned no selectedNodes array for screenshot targeting.");
|
|
188
|
+
}
|
|
189
|
+
return nodes.map((node) => {
|
|
190
|
+
const nodeId = typeof node === "string" ? node : node && typeof node === "object" && !Array.isArray(node) ? node.id ?? node.nodeId : void 0;
|
|
191
|
+
if (typeof nodeId !== "string" || !nodeId.trim()) {
|
|
192
|
+
throw new TypeError("Paper get_selection returned a selected node without a valid ID.");
|
|
193
|
+
}
|
|
194
|
+
return nodeId;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
function stripJsonFlag(args) {
|
|
198
|
+
let json = false;
|
|
199
|
+
const remaining = [];
|
|
200
|
+
for (const arg of args) {
|
|
201
|
+
if (arg !== "--json") {
|
|
202
|
+
remaining.push(arg);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (json) throw new TypeError("--json may be provided only once.");
|
|
206
|
+
json = true;
|
|
207
|
+
}
|
|
208
|
+
return { args: remaining, json };
|
|
209
|
+
}
|
|
210
|
+
function assertNoArguments(command, args) {
|
|
211
|
+
if (args.length > 0) {
|
|
212
|
+
invalidPaperArgument(`${command} does not accept '${safeArgumentForDiagnostic(args[0] ?? "")}'.`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function parseNodeArgs(args) {
|
|
216
|
+
let nodeId;
|
|
217
|
+
let children = false;
|
|
218
|
+
let jsx = false;
|
|
219
|
+
for (const arg of args) {
|
|
220
|
+
if (arg === "--children") {
|
|
221
|
+
if (children) throw new TypeError("--children may be provided only once.");
|
|
222
|
+
children = true;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (arg === "--jsx") {
|
|
226
|
+
if (jsx) throw new TypeError("--jsx may be provided only once.");
|
|
227
|
+
jsx = true;
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (arg.startsWith("-")) {
|
|
231
|
+
invalidPaperArgument(`node does not accept '${safeArgumentForDiagnostic(arg)}'.`);
|
|
232
|
+
}
|
|
233
|
+
if (nodeId) throw new TypeError("node accepts exactly one <nodeId>.");
|
|
234
|
+
nodeId = arg;
|
|
235
|
+
}
|
|
236
|
+
if (!nodeId) throw new TypeError("node requires a <nodeId> (see `paper tree`).");
|
|
237
|
+
if (nodeId === PAPER_NODE_PLACEHOLDER) {
|
|
238
|
+
throw new TypeError("Replace 'replace-with-node-id' with a real Paper node ID before running this command.");
|
|
239
|
+
}
|
|
240
|
+
if (children && jsx) throw new TypeError("--children and --jsx are mutually exclusive.");
|
|
241
|
+
return {
|
|
242
|
+
nodeId,
|
|
243
|
+
toolName: jsx ? "paper_get_jsx" : children ? "paper_get_children" : "paper_get_node_info"
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function parseScreenshotArgs(args) {
|
|
247
|
+
let out;
|
|
248
|
+
let nodeId;
|
|
249
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
250
|
+
const arg = args[index];
|
|
251
|
+
if (!arg) continue;
|
|
252
|
+
const [flag, inlineValue] = arg.includes("=") ? arg.split(/=(.*)/s, 2) : [arg, void 0];
|
|
253
|
+
if (flag !== "--out" && flag !== "--node") {
|
|
254
|
+
invalidPaperArgument(`screenshot does not accept '${safeArgumentForDiagnostic(arg)}'.`);
|
|
255
|
+
}
|
|
256
|
+
const value = inlineValue ?? args[index + 1];
|
|
257
|
+
if (!inlineValue) index += 1;
|
|
258
|
+
if (!value || value.startsWith("--")) throw new TypeError(`${flag} requires a value.`);
|
|
259
|
+
if (flag === "--out") {
|
|
260
|
+
if (out) throw new TypeError("--out may be provided only once.");
|
|
261
|
+
out = value;
|
|
262
|
+
} else {
|
|
263
|
+
if (nodeId) throw new TypeError("--node may be provided only once.");
|
|
264
|
+
nodeId = value;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (!out) throw new TypeError("screenshot requires --out <file>.");
|
|
268
|
+
if (nodeId === PAPER_NODE_PLACEHOLDER) {
|
|
269
|
+
throw new TypeError("Replace 'replace-with-node-id' with a real Paper node ID before running this command.");
|
|
270
|
+
}
|
|
271
|
+
return { out: resolve(out), ...nodeId ? { nodeId } : {} };
|
|
272
|
+
}
|
|
273
|
+
function screenshotOutputPath(requestedOut, mimeType) {
|
|
274
|
+
const expectedExtension = mimeType === "image/png" ? ".png" : mimeType === "image/jpeg" ? ".jpg" : mimeType === "image/webp" ? ".webp" : void 0;
|
|
275
|
+
if (!expectedExtension) throw new TypeError(`Paper returned unsupported screenshot type ${mimeType}.`);
|
|
276
|
+
const providedExtension = extname(requestedOut).toLowerCase();
|
|
277
|
+
if (!providedExtension) return `${requestedOut}${expectedExtension}`;
|
|
278
|
+
const acceptedExtensions = expectedExtension === ".jpg" ? /* @__PURE__ */ new Set([".jpg", ".jpeg"]) : /* @__PURE__ */ new Set([expectedExtension]);
|
|
279
|
+
if (acceptedExtensions.has(providedExtension)) return requestedOut;
|
|
280
|
+
const suggested = join(dirname(requestedOut), `${basename(requestedOut, providedExtension)}${expectedExtension}`);
|
|
281
|
+
throw new TypeError(
|
|
282
|
+
`Paper returned ${mimeType}, but --out uses '${providedExtension}'. No file was written; retry with --out ${suggested}.`
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
function verifiedImageBytes(image) {
|
|
286
|
+
if (!image.data || image.data.length % 4 !== 0 || !STRICT_BASE64.test(image.data)) {
|
|
287
|
+
throw new TypeError("Paper returned malformed screenshot base64; no file was written.");
|
|
288
|
+
}
|
|
289
|
+
const bytes = Buffer.from(image.data, "base64");
|
|
290
|
+
if (bytes.toString("base64") !== image.data) {
|
|
291
|
+
throw new TypeError("Paper returned non-canonical screenshot base64; no file was written.");
|
|
292
|
+
}
|
|
293
|
+
const actualMimeType = bytes.length >= 8 && bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])) ? "image/png" : bytes.length >= 5 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255 && bytes.at(-2) === 255 && bytes.at(-1) === 217 ? "image/jpeg" : bytes.length >= 12 && bytes.subarray(0, 4).toString("ascii") === "RIFF" && bytes.subarray(8, 12).toString("ascii") === "WEBP" ? "image/webp" : void 0;
|
|
294
|
+
if (!actualMimeType)
|
|
295
|
+
throw new TypeError("Paper returned bytes that are not a supported screenshot image; no file was written.");
|
|
296
|
+
if (actualMimeType !== image.mimeType) {
|
|
297
|
+
throw new TypeError(
|
|
298
|
+
`Paper labeled screenshot bytes as ${image.mimeType}, but they are ${actualMimeType}; no file was written.`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
return bytes;
|
|
302
|
+
}
|
|
303
|
+
async function requirePaperResult(callTool, toolName, args) {
|
|
304
|
+
const response = await callTool(toolName, args);
|
|
305
|
+
if (!response.ok) throw new TypeError(response.error);
|
|
306
|
+
assertPaperResult(toolName, response.result);
|
|
307
|
+
return response.result;
|
|
308
|
+
}
|
|
309
|
+
function writeReadResult(result, json, writeOutput) {
|
|
310
|
+
writeOutput(json ? `${JSON.stringify({ result }, null, 2)}
|
|
311
|
+
` : `${paperText(result)}
|
|
312
|
+
`);
|
|
313
|
+
}
|
|
314
|
+
async function handleOwnerReadCommand(argv, io = {}) {
|
|
315
|
+
const command = argv[0];
|
|
316
|
+
if (!command || !READ_COMMANDS.has(command)) return false;
|
|
317
|
+
const callTool = io.callTool ?? callPaperOwnerMcp;
|
|
318
|
+
const writeOutput = io.writeOutput ?? ((value) => process.stdout.write(value));
|
|
319
|
+
const writeScreenshot = io.writeScreenshot ?? (async (path, data) => writeFile(path, data, { flag: "wx" }));
|
|
320
|
+
const helpArgs = argv.slice(1).filter((arg) => arg === "--help" || arg === "-h");
|
|
321
|
+
if (helpArgs.length > 0) {
|
|
322
|
+
if (argv.length !== 2 || helpArgs.length !== 1) {
|
|
323
|
+
throw new TypeError(`${command} help does not accept other arguments.`);
|
|
324
|
+
}
|
|
325
|
+
writeOutput(READ_COMMAND_HELP[command] ?? "");
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
const parsed = stripJsonFlag(argv.slice(1));
|
|
329
|
+
if (command === "show" || command === "info") {
|
|
330
|
+
assertNoArguments(command, parsed.args);
|
|
331
|
+
writeReadResult(await requirePaperResult(callTool, "paper_get_basic_info", {}), parsed.json, writeOutput);
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
if (command === "selection") {
|
|
335
|
+
assertNoArguments(command, parsed.args);
|
|
336
|
+
writeReadResult(await requirePaperResult(callTool, "paper_get_selection", {}), parsed.json, writeOutput);
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
if (command === "tree") {
|
|
340
|
+
assertNoArguments(command, parsed.args);
|
|
341
|
+
const basicInfo = await requirePaperResult(callTool, "paper_get_basic_info", {});
|
|
342
|
+
let rootNodeId;
|
|
343
|
+
try {
|
|
344
|
+
rootNodeId = JSON.parse(paperText(basicInfo)).rootNodeId;
|
|
345
|
+
} catch {
|
|
346
|
+
throw new TypeError("Paper get_basic_info returned invalid JSON for the tree summary.");
|
|
347
|
+
}
|
|
348
|
+
if (typeof rootNodeId !== "string" || !rootNodeId.trim()) {
|
|
349
|
+
throw new TypeError("Paper get_basic_info returned no rootNodeId for the tree summary.");
|
|
350
|
+
}
|
|
351
|
+
const result2 = await requirePaperResult(callTool, "paper_get_tree_summary", { nodeId: rootNodeId });
|
|
352
|
+
writeReadResult(result2, parsed.json, writeOutput);
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
if (command === "node") {
|
|
356
|
+
const node = parseNodeArgs(parsed.args);
|
|
357
|
+
const result2 = await requirePaperResult(callTool, node.toolName, { nodeId: node.nodeId });
|
|
358
|
+
writeReadResult(result2, parsed.json, writeOutput);
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
const screenshot = parseScreenshotArgs(parsed.args);
|
|
362
|
+
let screenshotNodeId = screenshot.nodeId;
|
|
363
|
+
if (!screenshotNodeId) {
|
|
364
|
+
const selection = await requirePaperResult(callTool, "paper_get_selection", {});
|
|
365
|
+
const nodeIds = selectedNodeIds(selection);
|
|
366
|
+
if (nodeIds.length === 0) throw new TypeError(PAPER_NO_SCREENSHOT_SELECTION);
|
|
367
|
+
if (nodeIds.length !== 1) throw new TypeError(PAPER_AMBIGUOUS_SCREENSHOT_SELECTION);
|
|
368
|
+
screenshotNodeId = nodeIds[0];
|
|
369
|
+
}
|
|
370
|
+
const result = await requirePaperResult(callTool, "paper_get_screenshot", { nodeId: screenshotNodeId });
|
|
371
|
+
const images = paperContentParts(result).filter(
|
|
372
|
+
(part) => part.type === "image"
|
|
373
|
+
);
|
|
374
|
+
if (images.length === 0) {
|
|
375
|
+
throw new TypeError(`Paper returned no screenshot image: ${paperText(result).slice(0, 200)}`);
|
|
376
|
+
}
|
|
377
|
+
if (images.length !== 1) {
|
|
378
|
+
throw new TypeError(
|
|
379
|
+
"Paper returned an ambiguous screenshot response; expected exactly one image and wrote no file."
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
const image = images[0];
|
|
383
|
+
const imageBytes = verifiedImageBytes(image);
|
|
384
|
+
const outputPath = screenshotOutputPath(screenshot.out, image.mimeType);
|
|
385
|
+
try {
|
|
386
|
+
await writeScreenshot(outputPath, imageBytes);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
|
|
389
|
+
if (code === "EEXIST") {
|
|
390
|
+
throw new TypeError(`Screenshot output already exists; no file was overwritten: ${outputPath}`);
|
|
391
|
+
}
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
writeOutput(
|
|
395
|
+
parsed.json ? `${JSON.stringify({ out: outputPath, mimeType: image.mimeType }, null, 2)}
|
|
396
|
+
` : `${image.mimeType} -> ${outputPath}
|
|
397
|
+
`
|
|
398
|
+
);
|
|
399
|
+
return true;
|
|
400
|
+
}
|
|
401
|
+
export {
|
|
402
|
+
handleOwnerReadCommand
|
|
403
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PAPER_DOMAIN_TOOL_NAMES,
|
|
3
|
+
PAPER_TOOL_CONTRACT_HASH,
|
|
4
|
+
PAPER_TOOL_CONTRACT_PATH,
|
|
5
|
+
PAPER_TOOL_SCHEMA_HASHES,
|
|
6
|
+
assertPaperContractFixturesExist,
|
|
7
|
+
assertPaperToolContractHandshake,
|
|
8
|
+
extractPaperPromptToolIds,
|
|
9
|
+
paperDomainToolIdsFromActive,
|
|
10
|
+
paperLoadedToolContract,
|
|
11
|
+
paperToolContract,
|
|
12
|
+
renderPaperToolInventoryXml,
|
|
13
|
+
resolvePaperAgentHome,
|
|
14
|
+
resolvePaperContractRef
|
|
15
|
+
} from "./chunk-UHVY2TIH.js";
|
|
16
|
+
import "./chunk-BGKLQ5Y6.js";
|
|
17
|
+
import "./chunk-ZX4GFXSY.js";
|
|
18
|
+
export {
|
|
19
|
+
PAPER_DOMAIN_TOOL_NAMES,
|
|
20
|
+
PAPER_TOOL_CONTRACT_HASH,
|
|
21
|
+
PAPER_TOOL_CONTRACT_PATH,
|
|
22
|
+
PAPER_TOOL_SCHEMA_HASHES,
|
|
23
|
+
assertPaperContractFixturesExist,
|
|
24
|
+
assertPaperToolContractHandshake,
|
|
25
|
+
extractPaperPromptToolIds,
|
|
26
|
+
paperDomainToolIdsFromActive,
|
|
27
|
+
paperLoadedToolContract,
|
|
28
|
+
paperToolContract,
|
|
29
|
+
renderPaperToolInventoryXml,
|
|
30
|
+
resolvePaperAgentHome,
|
|
31
|
+
resolvePaperContractRef
|
|
32
|
+
};
|
package/bin/paper.js
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
PAPER_BRIEF_APPROVAL_PREFIX,
|
|
4
|
+
PAPER_IMPLEMENTATION_BRIEF_SCHEMA_VERSION,
|
|
5
|
+
PAPER_TOOL_IDS,
|
|
6
|
+
PAPER_TOOL_SAFETY_CLASSIFICATIONS,
|
|
7
|
+
STANDARD_TOOL_IDS,
|
|
8
|
+
printHelp,
|
|
9
|
+
resolvePaperWorkflowMode
|
|
10
|
+
} from "./chunks/chunk-I4P43IZS.js";
|
|
11
|
+
import {
|
|
12
|
+
APP_NAME,
|
|
13
|
+
BUNDLED_PACKAGE_VERSIONS,
|
|
14
|
+
IDENTITY_DOMAIN,
|
|
15
|
+
PACKAGE_DISTRIBUTION,
|
|
16
|
+
VERSION,
|
|
17
|
+
applyPaperPrivacyDefaults,
|
|
18
|
+
getAgentDir
|
|
19
|
+
} from "./chunks/chunk-BGKLQ5Y6.js";
|
|
20
|
+
import {
|
|
21
|
+
invalidPaperArgument,
|
|
22
|
+
safeArgumentForDiagnostic
|
|
23
|
+
} from "./chunks/chunk-JCO37BXY.js";
|
|
24
|
+
import "./chunks/chunk-ZX4GFXSY.js";
|
|
25
|
+
|
|
26
|
+
// src/owner-cli.ts
|
|
27
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
28
|
+
import { dirname, join } from "node:path";
|
|
29
|
+
import { fileURLToPath } from "node:url";
|
|
30
|
+
var STANDARD_LOCKED_TOOL_IDS = /* @__PURE__ */ new Set(["read", "grep", "find", "ls"]);
|
|
31
|
+
var OWNER_READ_COMMANDS = /* @__PURE__ */ new Set(["show", "info", "selection", "tree", "node", "screenshot"]);
|
|
32
|
+
function resolveModulePath(specifier) {
|
|
33
|
+
return fileURLToPath(import.meta.resolve(specifier));
|
|
34
|
+
}
|
|
35
|
+
function readDependencyVersion(specifier) {
|
|
36
|
+
let current = dirname(resolveModulePath(specifier));
|
|
37
|
+
for (let depth = 0; depth < 10; depth += 1) {
|
|
38
|
+
const manifestPath = join(current, "package.json");
|
|
39
|
+
if (existsSync(manifestPath)) {
|
|
40
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
41
|
+
if (manifest.name === specifier) return manifest.version ?? "unknown";
|
|
42
|
+
}
|
|
43
|
+
const parent = dirname(current);
|
|
44
|
+
if (parent === current) break;
|
|
45
|
+
current = parent;
|
|
46
|
+
}
|
|
47
|
+
return "unknown";
|
|
48
|
+
}
|
|
49
|
+
function resolveReportedModule(specifier, bundlePath, packageLoadErrors) {
|
|
50
|
+
if (PACKAGE_DISTRIBUTION === "public-bundle") return bundlePath;
|
|
51
|
+
try {
|
|
52
|
+
return resolveModulePath(specifier);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
55
|
+
packageLoadErrors.push(`${specifier}: ${message}`);
|
|
56
|
+
return "unresolved";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function readReportedVersion(specifier, packageLoadErrors) {
|
|
60
|
+
if (PACKAGE_DISTRIBUTION !== "public-bundle") return readDependencyVersion(specifier);
|
|
61
|
+
const version = BUNDLED_PACKAGE_VERSIONS[specifier];
|
|
62
|
+
if (version) return version;
|
|
63
|
+
packageLoadErrors.push(`${specifier}: missing bundled package version`);
|
|
64
|
+
return "unknown";
|
|
65
|
+
}
|
|
66
|
+
async function createAboutJson(options = {}) {
|
|
67
|
+
const {
|
|
68
|
+
PAPER_TOOL_CONTRACT_HASH,
|
|
69
|
+
PAPER_TOOL_CONTRACT_PATH,
|
|
70
|
+
PAPER_TOOL_SCHEMA_HASHES,
|
|
71
|
+
paperToolContract,
|
|
72
|
+
resolvePaperAgentHome
|
|
73
|
+
} = await import("./chunks/tool-contract-ZWFZSYL4.js");
|
|
74
|
+
const packageLoadErrors = [];
|
|
75
|
+
const paperEntry = fileURLToPath(import.meta.url);
|
|
76
|
+
const monoForkRuntime = resolveReportedModule("@creative-int/mono/fork-runtime", paperEntry, packageLoadErrors);
|
|
77
|
+
const piCodingRuntime = resolveReportedModule("@creative-int/pi-coding-runtime", paperEntry, packageLoadErrors);
|
|
78
|
+
const registeredToolIds = paperToolContract.tools.map((tool) => tool.id);
|
|
79
|
+
if (registeredToolIds.join("\n") !== PAPER_TOOL_IDS.join("\n")) {
|
|
80
|
+
packageLoadErrors.push("workflow contract tool order does not match tools.contract.json");
|
|
81
|
+
}
|
|
82
|
+
const approvedCapabilityToolIds = [...STANDARD_TOOL_IDS, ...registeredToolIds];
|
|
83
|
+
const defaultLockedToolIds = approvedCapabilityToolIds.filter(
|
|
84
|
+
(toolName) => STANDARD_LOCKED_TOOL_IDS.has(toolName) || PAPER_TOOL_SAFETY_CLASSIFICATIONS[toolName] === "read-only" || PAPER_TOOL_SAFETY_CLASSIFICATIONS[toolName] === "dry-run-or-live-mutation"
|
|
85
|
+
);
|
|
86
|
+
const runtimeVersions = {
|
|
87
|
+
node: process.version,
|
|
88
|
+
paperMono: VERSION,
|
|
89
|
+
mono: readReportedVersion("@creative-int/mono", packageLoadErrors),
|
|
90
|
+
piCodingRuntime: readReportedVersion("@creative-int/pi-coding-runtime", packageLoadErrors)
|
|
91
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
name: APP_NAME,
|
|
94
|
+
specialist: paperToolContract.specialist,
|
|
95
|
+
ownerCommand: paperToolContract.ownerCommand,
|
|
96
|
+
domain: IDENTITY_DOMAIN,
|
|
97
|
+
version: VERSION,
|
|
98
|
+
distribution: PACKAGE_DISTRIBUTION,
|
|
99
|
+
runtime: "@creative-int/mono/fork-runtime",
|
|
100
|
+
resolvedBinary: process.argv[1] ?? "paper",
|
|
101
|
+
agentHome: resolvePaperAgentHome(getAgentDir()),
|
|
102
|
+
contractPath: PAPER_TOOL_CONTRACT_PATH,
|
|
103
|
+
contractHash: PAPER_TOOL_CONTRACT_HASH,
|
|
104
|
+
toolSchemaHashes: PAPER_TOOL_SCHEMA_HASHES,
|
|
105
|
+
registeredToolIds,
|
|
106
|
+
promptToolIds: registeredToolIds,
|
|
107
|
+
defaultLockedToolIds,
|
|
108
|
+
approvedCapabilityToolIds,
|
|
109
|
+
bundledPackages: BUNDLED_PACKAGE_VERSIONS,
|
|
110
|
+
packageLoadErrors,
|
|
111
|
+
loadedModules: { paperEntry, monoForkRuntime, piCodingRuntime },
|
|
112
|
+
moduleProvenance: {
|
|
113
|
+
paperEntry: { kind: "resolved", path: paperEntry },
|
|
114
|
+
monoForkRuntime: {
|
|
115
|
+
kind: PACKAGE_DISTRIBUTION === "public-bundle" ? "bundled" : "resolved",
|
|
116
|
+
path: monoForkRuntime
|
|
117
|
+
},
|
|
118
|
+
piCodingRuntime: {
|
|
119
|
+
kind: PACKAGE_DISTRIBUTION === "public-bundle" ? "bundled" : "resolved",
|
|
120
|
+
path: piCodingRuntime
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
runtimeVersions,
|
|
124
|
+
briefGate: {
|
|
125
|
+
schemaVersion: PAPER_IMPLEMENTATION_BRIEF_SCHEMA_VERSION,
|
|
126
|
+
approvalPhrase: `${PAPER_BRIEF_APPROVAL_PREFIX} <brief-id>`,
|
|
127
|
+
mutationLockedByDefault: true,
|
|
128
|
+
workflowModes: ["reference-to-code", "audit-only", "copilot"],
|
|
129
|
+
selectedWorkflow: options.selectedWorkflow ?? "reference-to-code",
|
|
130
|
+
sessionAction: options.sessionAction ?? "new"
|
|
131
|
+
},
|
|
132
|
+
privacy: {
|
|
133
|
+
productTelemetry: false,
|
|
134
|
+
automaticVersionChecks: false,
|
|
135
|
+
productBackend: false
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function specialistAboutOptions(args) {
|
|
140
|
+
let auditOnly = false;
|
|
141
|
+
let copilot = false;
|
|
142
|
+
let sessionAction = "new";
|
|
143
|
+
for (const arg of args) {
|
|
144
|
+
if (arg === "--about-json") continue;
|
|
145
|
+
if (arg === "--audit-only") {
|
|
146
|
+
auditOnly = true;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (arg === "--copilot") {
|
|
150
|
+
copilot = true;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (arg === "--continue" || arg === "-c") {
|
|
154
|
+
if (sessionAction !== "new") invalidPaperArgument("--continue and --resume are mutually exclusive.");
|
|
155
|
+
sessionAction = "continue";
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (arg === "--resume" || arg === "-r") {
|
|
159
|
+
if (sessionAction !== "new") invalidPaperArgument("--continue and --resume are mutually exclusive.");
|
|
160
|
+
sessionAction = "resume";
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
invalidPaperArgument(`--about-json does not accept '${safeArgumentForDiagnostic(arg)}'.`);
|
|
164
|
+
}
|
|
165
|
+
if (auditOnly && copilot) {
|
|
166
|
+
invalidPaperArgument("--audit-only and --copilot are mutually exclusive Paper workflow modes.");
|
|
167
|
+
}
|
|
168
|
+
return { selectedWorkflow: resolvePaperWorkflowMode(auditOnly, copilot), sessionAction };
|
|
169
|
+
}
|
|
170
|
+
function printDoctorHelp() {
|
|
171
|
+
process.stdout.write(
|
|
172
|
+
"Usage: paper doctor [--json]\nCheck the 27-tool contract and live Paper Desktop reachability.\n"
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
function assertOnly(args, accepted, command) {
|
|
176
|
+
if (args.length !== accepted.length || args.some((arg, index) => arg !== accepted[index])) {
|
|
177
|
+
const rejected = args.find((arg, index) => arg !== accepted[index]) ?? "extra arguments";
|
|
178
|
+
invalidPaperArgument(`${command} does not accept '${safeArgumentForDiagnostic(rejected)}'.`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function argumentsBeforeOptionTerminator(args) {
|
|
182
|
+
const terminator = args.indexOf("--");
|
|
183
|
+
return terminator < 0 ? args : args.slice(0, terminator);
|
|
184
|
+
}
|
|
185
|
+
async function runSpecialist(args) {
|
|
186
|
+
const [{ EnvHttpProxyAgent, setGlobalDispatcher }, { main: runSpecialistMain }] = await Promise.all([
|
|
187
|
+
import("undici"),
|
|
188
|
+
import("./chunks/main-NQIGPQXK.js")
|
|
189
|
+
]);
|
|
190
|
+
setGlobalDispatcher(new EnvHttpProxyAgent());
|
|
191
|
+
await runSpecialistMain(args);
|
|
192
|
+
}
|
|
193
|
+
async function main(args) {
|
|
194
|
+
applyPaperPrivacyDefaults();
|
|
195
|
+
const isMonoRoute = args[0] === "mono";
|
|
196
|
+
const normalizedArgs = isMonoRoute ? args.slice(1) : [...args];
|
|
197
|
+
if (normalizedArgs[0] === "help") normalizedArgs[0] = "--help";
|
|
198
|
+
const optionArgs = argumentsBeforeOptionTerminator(normalizedArgs);
|
|
199
|
+
if (normalizedArgs[0] === "--help" || normalizedArgs[0] === "-h") {
|
|
200
|
+
assertOnly(normalizedArgs, [normalizedArgs[0] ?? "--help"], "help");
|
|
201
|
+
printHelp();
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (optionArgs.includes("--about-json")) {
|
|
205
|
+
let aboutOptions = {};
|
|
206
|
+
if (isMonoRoute) aboutOptions = specialistAboutOptions(normalizedArgs);
|
|
207
|
+
else assertOnly(normalizedArgs, ["--about-json"], "--about-json");
|
|
208
|
+
process.stdout.write(`${JSON.stringify(await createAboutJson(aboutOptions))}
|
|
209
|
+
`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const versionArgument = optionArgs.find((arg) => arg === "--version" || arg === "-v");
|
|
213
|
+
if (versionArgument) {
|
|
214
|
+
assertOnly(normalizedArgs, [versionArgument], "--version");
|
|
215
|
+
process.stdout.write(`${VERSION}
|
|
216
|
+
`);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (normalizedArgs[0] === "doctor") {
|
|
220
|
+
if (normalizedArgs[1] === "--help" || normalizedArgs[1] === "-h") {
|
|
221
|
+
assertOnly(normalizedArgs, ["doctor", normalizedArgs[1]], "doctor");
|
|
222
|
+
printDoctorHelp();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (normalizedArgs.length > 2 || normalizedArgs[1] !== void 0 && normalizedArgs[1] !== "--json") {
|
|
226
|
+
const rejected = normalizedArgs[1] ?? normalizedArgs[2] ?? "extra arguments";
|
|
227
|
+
invalidPaperArgument(`doctor does not accept '${safeArgumentForDiagnostic(rejected)}'.`);
|
|
228
|
+
}
|
|
229
|
+
const { createPaperToolDoctorReport, paperDoctorExitCode, renderPaperToolDoctorReport } = await import("./chunks/doctor-7DE4ASQO.js");
|
|
230
|
+
const report = await createPaperToolDoctorReport();
|
|
231
|
+
process.stdout.write(
|
|
232
|
+
normalizedArgs[1] === "--json" ? `${JSON.stringify(report, null, 2)}
|
|
233
|
+
` : renderPaperToolDoctorReport(report)
|
|
234
|
+
);
|
|
235
|
+
process.exitCode = paperDoctorExitCode(report);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
if (!isMonoRoute && OWNER_READ_COMMANDS.has(normalizedArgs[0] ?? "")) {
|
|
239
|
+
const { handleOwnerReadCommand } = await import("./chunks/owner-commands-MWXW2KMM.js");
|
|
240
|
+
if (await handleOwnerReadCommand(normalizedArgs)) return;
|
|
241
|
+
}
|
|
242
|
+
if (normalizedArgs[0] === "page") {
|
|
243
|
+
throw new TypeError(
|
|
244
|
+
"Direct page control is disabled. Start `paper mono --copilot`, inspect the target, approve its exact brief, and use the gated Paper page tools."
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
await runSpecialist(normalizedArgs);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/public-cli.ts
|
|
251
|
+
process.title = "paper";
|
|
252
|
+
process.title = APP_NAME;
|
|
253
|
+
void main(process.argv.slice(2)).catch((error) => {
|
|
254
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
255
|
+
process.stderr.write(`paper: ${message}
|
|
256
|
+
`);
|
|
257
|
+
process.exitCode = 1;
|
|
258
|
+
});
|