pi-studio 0.5.57 → 0.5.59
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 +15 -0
- package/client/studio-client.js +573 -7
- package/client/studio.css +552 -1
- package/index.ts +139 -0
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -4078,6 +4078,86 @@ async function preprocessStudioMermaidForPdf(markdown: string, workDir: string):
|
|
|
4078
4078
|
};
|
|
4079
4079
|
}
|
|
4080
4080
|
|
|
4081
|
+
interface StudioClipboardCommand {
|
|
4082
|
+
command: string;
|
|
4083
|
+
args: string[];
|
|
4084
|
+
label: string;
|
|
4085
|
+
}
|
|
4086
|
+
|
|
4087
|
+
function getStudioClipboardCommands(): StudioClipboardCommand[] {
|
|
4088
|
+
if (process.platform === "darwin") {
|
|
4089
|
+
return [{ command: "pbcopy", args: [], label: "pbcopy" }];
|
|
4090
|
+
}
|
|
4091
|
+
if (process.platform === "win32") {
|
|
4092
|
+
return [{ command: "cmd.exe", args: ["/c", "clip"], label: "clip" }];
|
|
4093
|
+
}
|
|
4094
|
+
return [
|
|
4095
|
+
{ command: "wl-copy", args: [], label: "wl-copy" },
|
|
4096
|
+
{ command: "xclip", args: ["-selection", "clipboard"], label: "xclip" },
|
|
4097
|
+
{ command: "xsel", args: ["--clipboard", "--input"], label: "xsel" },
|
|
4098
|
+
];
|
|
4099
|
+
}
|
|
4100
|
+
|
|
4101
|
+
function writeStudioClipboardWithCommand(spec: StudioClipboardCommand, text: string): Promise<void> {
|
|
4102
|
+
return new Promise((resolve, reject) => {
|
|
4103
|
+
const child = spawn(spec.command, spec.args, { stdio: ["pipe", "ignore", "pipe"] });
|
|
4104
|
+
const stderrChunks: Buffer[] = [];
|
|
4105
|
+
let settled = false;
|
|
4106
|
+
const timer = setTimeout(() => {
|
|
4107
|
+
if (settled) return;
|
|
4108
|
+
settled = true;
|
|
4109
|
+
try {
|
|
4110
|
+
child.kill();
|
|
4111
|
+
} catch {
|
|
4112
|
+
// Ignore kill failures.
|
|
4113
|
+
}
|
|
4114
|
+
reject(new Error(`${spec.label} timed out.`));
|
|
4115
|
+
}, 3000);
|
|
4116
|
+
|
|
4117
|
+
const fail = (error: Error) => {
|
|
4118
|
+
if (settled) return;
|
|
4119
|
+
settled = true;
|
|
4120
|
+
clearTimeout(timer);
|
|
4121
|
+
reject(error);
|
|
4122
|
+
};
|
|
4123
|
+
|
|
4124
|
+
child.stderr.on("data", (chunk: Buffer | string) => {
|
|
4125
|
+
stderrChunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
|
|
4126
|
+
});
|
|
4127
|
+
|
|
4128
|
+
child.once("error", (error) => {
|
|
4129
|
+
fail(error instanceof Error ? error : new Error(String(error)));
|
|
4130
|
+
});
|
|
4131
|
+
|
|
4132
|
+
child.once("close", (code) => {
|
|
4133
|
+
if (settled) return;
|
|
4134
|
+
settled = true;
|
|
4135
|
+
clearTimeout(timer);
|
|
4136
|
+
if (code === 0) {
|
|
4137
|
+
resolve();
|
|
4138
|
+
return;
|
|
4139
|
+
}
|
|
4140
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf-8").trim();
|
|
4141
|
+
reject(new Error(`${spec.label} exited with code ${code}${stderr ? `: ${stderr}` : ""}`));
|
|
4142
|
+
});
|
|
4143
|
+
|
|
4144
|
+
child.stdin.end(text, "utf-8");
|
|
4145
|
+
});
|
|
4146
|
+
}
|
|
4147
|
+
|
|
4148
|
+
async function writeStudioSystemClipboard(text: string): Promise<{ ok: true; method: string } | { ok: false; error: string }> {
|
|
4149
|
+
const errors: string[] = [];
|
|
4150
|
+
for (const spec of getStudioClipboardCommands()) {
|
|
4151
|
+
try {
|
|
4152
|
+
await writeStudioClipboardWithCommand(spec, text);
|
|
4153
|
+
return { ok: true, method: spec.label };
|
|
4154
|
+
} catch (error) {
|
|
4155
|
+
errors.push(`${spec.label}: ${error instanceof Error ? error.message : String(error)}`);
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
return { ok: false, error: errors.join("; ") || "No system clipboard command is available." };
|
|
4159
|
+
}
|
|
4160
|
+
|
|
4081
4161
|
async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolean, resourcePath?: string, sourcePath?: string): Promise<string> {
|
|
4082
4162
|
const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
|
|
4083
4163
|
const markdownWithoutHtmlComments = isLatex ? markdown : stripStudioMarkdownHtmlComments(markdown);
|
|
@@ -6182,6 +6262,7 @@ ${cssVarsBlock}
|
|
|
6182
6262
|
<div class="scratchpad-actions">
|
|
6183
6263
|
<button id="reviewNotesAddBtn" type="button" title="Create a new local comment on the current editor line.">Line comment</button>
|
|
6184
6264
|
<button id="reviewNotesInlineAllBtn" type="button" title="Toggle inline annotations for all non-empty comments.">All inline: Off</button>
|
|
6265
|
+
<button id="reviewNotesDeleteAllBtn" type="button" title="Delete all local comments for this document or draft.">Delete all</button>
|
|
6185
6266
|
<button id="reviewNotesDoneBtn" type="button" title="Hide the comments rail.">Hide</button>
|
|
6186
6267
|
</div>
|
|
6187
6268
|
</div>
|
|
@@ -7902,6 +7983,49 @@ export default function (pi: ExtensionAPI) {
|
|
|
7902
7983
|
respondJson(res, 200, { ok: true });
|
|
7903
7984
|
};
|
|
7904
7985
|
|
|
7986
|
+
const handleClipboardRequest = async (req: IncomingMessage, res: ServerResponse) => {
|
|
7987
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
7988
|
+
if (method !== "POST") {
|
|
7989
|
+
res.setHeader("Allow", "POST");
|
|
7990
|
+
respondJson(res, 405, { ok: false, error: "Method not allowed. Use POST." });
|
|
7991
|
+
return;
|
|
7992
|
+
}
|
|
7993
|
+
|
|
7994
|
+
let rawBody = "";
|
|
7995
|
+
try {
|
|
7996
|
+
rawBody = await readRequestBody(req, REQUEST_BODY_MAX_BYTES);
|
|
7997
|
+
} catch (error) {
|
|
7998
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7999
|
+
const status = message.includes("exceeds") ? 413 : 400;
|
|
8000
|
+
respondJson(res, status, { ok: false, error: message });
|
|
8001
|
+
return;
|
|
8002
|
+
}
|
|
8003
|
+
|
|
8004
|
+
let parsedBody: unknown;
|
|
8005
|
+
try {
|
|
8006
|
+
parsedBody = rawBody ? JSON.parse(rawBody) : {};
|
|
8007
|
+
} catch {
|
|
8008
|
+
respondJson(res, 400, { ok: false, error: "Invalid JSON body." });
|
|
8009
|
+
return;
|
|
8010
|
+
}
|
|
8011
|
+
|
|
8012
|
+
const text =
|
|
8013
|
+
parsedBody && typeof parsedBody === "object" && typeof (parsedBody as { text?: unknown }).text === "string"
|
|
8014
|
+
? (parsedBody as { text: string }).text
|
|
8015
|
+
: null;
|
|
8016
|
+
if (text === null) {
|
|
8017
|
+
respondJson(res, 400, { ok: false, error: "Missing clipboard text in request body." });
|
|
8018
|
+
return;
|
|
8019
|
+
}
|
|
8020
|
+
|
|
8021
|
+
const result = await writeStudioSystemClipboard(text);
|
|
8022
|
+
if (result.ok) {
|
|
8023
|
+
respondJson(res, 200, { ok: true, method: result.method });
|
|
8024
|
+
return;
|
|
8025
|
+
}
|
|
8026
|
+
respondJson(res, 500, { ok: false, error: result.error });
|
|
8027
|
+
};
|
|
8028
|
+
|
|
7905
8029
|
const handleReviewNotesRequest = async (req: IncomingMessage, res: ServerResponse, requestUrl: URL) => {
|
|
7906
8030
|
const method = (req.method ?? "GET").toUpperCase();
|
|
7907
8031
|
if (method === "GET") {
|
|
@@ -8240,6 +8364,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
8240
8364
|
return;
|
|
8241
8365
|
}
|
|
8242
8366
|
|
|
8367
|
+
if (requestUrl.pathname === "/clipboard") {
|
|
8368
|
+
const token = requestUrl.searchParams.get("token") ?? "";
|
|
8369
|
+
if (token !== serverState.token) {
|
|
8370
|
+
respondJson(res, 403, { ok: false, error: "Invalid or expired studio token. Re-run /studio." });
|
|
8371
|
+
return;
|
|
8372
|
+
}
|
|
8373
|
+
void handleClipboardRequest(req, res).catch((error) => {
|
|
8374
|
+
respondJson(res, 500, {
|
|
8375
|
+
ok: false,
|
|
8376
|
+
error: `Clipboard write failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
8377
|
+
});
|
|
8378
|
+
});
|
|
8379
|
+
return;
|
|
8380
|
+
}
|
|
8381
|
+
|
|
8243
8382
|
if (requestUrl.pathname === "/render-preview") {
|
|
8244
8383
|
const token = requestUrl.searchParams.get("token") ?? "";
|
|
8245
8384
|
if (token !== serverState.token) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.59",
|
|
4
4
|
"description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, prompt/response history, and live Markdown/LaTeX/code preview",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|