pi-studio 0.6.2 → 0.6.4
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 +12 -0
- package/README.md +1 -1
- package/client/studio.css +5 -5
- package/index.ts +32 -10
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.6.4] — 2026-04-29
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- Fixed `@`-selected quoted file paths such as `@"folder/file.md"` so Studio commands strip the `@` prefix and surrounding quotes before resolving the file.
|
|
11
|
+
- `/studio --status` now prints the full tokenized Studio URL, and SSH tunnel hints explicitly say to preserve the `?token=...` parameter.
|
|
12
|
+
|
|
13
|
+
## [0.6.3] — 2026-04-29
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Polished rendered code blocks so ordinary code text uses normal foreground, Python function definitions keep function-name highlighting, and fenced-block borders are softened to better match theme intent.
|
|
17
|
+
- Slightly sharpened footer metadata and shortcut text for better readability across darker custom themes.
|
|
18
|
+
|
|
7
19
|
## [0.6.2] — 2026-04-29
|
|
8
20
|
|
|
9
21
|
### Changed
|
package/README.md
CHANGED
|
@@ -75,7 +75,7 @@ pi -e https://github.com/omaclaren/pi-studio
|
|
|
75
75
|
## Notes
|
|
76
76
|
|
|
77
77
|
- Local-only server (`127.0.0.1`) with tokenized Studio URLs.
|
|
78
|
-
- For remote SSH sessions, keep Studio bound to localhost and use SSH local port forwarding; `/studio`
|
|
78
|
+
- For remote SSH sessions, keep Studio bound to localhost and use SSH local port forwarding; `/studio` and `/studio --status` print the full tokenized localhost URL. Open that URL through the tunnel, preserving the `?token=...` parameter.
|
|
79
79
|
- Full Studio is a singleton per Pi session: use `/studio` to open it, `/studio-replace` to explicitly replace it, and `/studio-editor-only` for extra editing/preview tabs that do not take over the full Studio session view.
|
|
80
80
|
- Studio is designed as a complement to terminal pi, not a replacement.
|
|
81
81
|
- Installing pi-studio makes the optional `pi-studio-dark` and `pi-studio-light` themes available in pi's theme selector; it does not change your active theme.
|
package/client/studio.css
CHANGED
|
@@ -1143,7 +1143,7 @@
|
|
|
1143
1143
|
}
|
|
1144
1144
|
|
|
1145
1145
|
.rendered-markdown pre code {
|
|
1146
|
-
color: var(--
|
|
1146
|
+
color: var(--text);
|
|
1147
1147
|
white-space: inherit;
|
|
1148
1148
|
}
|
|
1149
1149
|
|
|
@@ -1846,7 +1846,7 @@
|
|
|
1846
1846
|
footer {
|
|
1847
1847
|
border-top: 1px solid var(--panel-border);
|
|
1848
1848
|
padding: 8px 12px;
|
|
1849
|
-
color: var(--muted);
|
|
1849
|
+
color: var(--studio-footer-text, var(--muted));
|
|
1850
1850
|
font-size: 12px;
|
|
1851
1851
|
min-height: 32px;
|
|
1852
1852
|
background: var(--panel);
|
|
@@ -1903,7 +1903,7 @@
|
|
|
1903
1903
|
.footer-meta {
|
|
1904
1904
|
grid-area: meta;
|
|
1905
1905
|
justify-self: start;
|
|
1906
|
-
color: var(--muted);
|
|
1906
|
+
color: var(--studio-footer-text, var(--muted));
|
|
1907
1907
|
font-size: 11px;
|
|
1908
1908
|
text-align: left;
|
|
1909
1909
|
max-width: 100%;
|
|
@@ -1954,12 +1954,12 @@
|
|
|
1954
1954
|
grid-area: hint;
|
|
1955
1955
|
justify-self: end;
|
|
1956
1956
|
align-self: center;
|
|
1957
|
-
color: var(--muted);
|
|
1957
|
+
color: var(--studio-footer-text, var(--muted));
|
|
1958
1958
|
font-size: 11px;
|
|
1959
1959
|
white-space: nowrap;
|
|
1960
1960
|
text-align: right;
|
|
1961
1961
|
font-style: normal;
|
|
1962
|
-
opacity: 0.
|
|
1962
|
+
opacity: 0.86;
|
|
1963
1963
|
display: inline-flex;
|
|
1964
1964
|
align-items: center;
|
|
1965
1965
|
gap: 8px;
|
package/index.ts
CHANGED
|
@@ -1382,20 +1382,27 @@ function isValidRequestId(id: string): boolean {
|
|
|
1382
1382
|
return /^[a-zA-Z0-9_-]{1,120}$/.test(id);
|
|
1383
1383
|
}
|
|
1384
1384
|
|
|
1385
|
-
function
|
|
1386
|
-
const trimmed =
|
|
1387
|
-
if (!trimmed) return null;
|
|
1388
|
-
|
|
1385
|
+
function stripMatchingPathQuotes(value: string): string {
|
|
1386
|
+
const trimmed = value.trim();
|
|
1389
1387
|
if (
|
|
1390
1388
|
(trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length >= 2) ||
|
|
1391
1389
|
(trimmed.startsWith("'") && trimmed.endsWith("'") && trimmed.length >= 2)
|
|
1392
1390
|
) {
|
|
1393
1391
|
return trimmed.slice(1, -1).trim();
|
|
1394
1392
|
}
|
|
1395
|
-
|
|
1396
1393
|
return trimmed;
|
|
1397
1394
|
}
|
|
1398
1395
|
|
|
1396
|
+
function parsePathArgument(args: string): string | null {
|
|
1397
|
+
const trimmed = args.trim();
|
|
1398
|
+
if (!trimmed) return null;
|
|
1399
|
+
|
|
1400
|
+
const hasAtPrefix = trimmed.startsWith("@");
|
|
1401
|
+
const pathPart = hasAtPrefix ? trimmed.slice(1).trim() : trimmed;
|
|
1402
|
+
const unquoted = stripMatchingPathQuotes(pathPart);
|
|
1403
|
+
return hasAtPrefix ? `@${unquoted}` : unquoted;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1399
1406
|
function tokenizeStudioCommandArgs(input: string): { tokens: string[]; error?: string } {
|
|
1400
1407
|
const tokens: string[] = [];
|
|
1401
1408
|
let current = "";
|
|
@@ -1445,8 +1452,8 @@ function tokenizeStudioCommandArgs(input: string): { tokens: string[]; error?: s
|
|
|
1445
1452
|
|
|
1446
1453
|
function normalizePathInput(pathInput: string): string {
|
|
1447
1454
|
const trimmed = pathInput.trim();
|
|
1448
|
-
if (trimmed.startsWith("@")) return trimmed.slice(1).trim();
|
|
1449
|
-
return trimmed;
|
|
1455
|
+
if (trimmed.startsWith("@")) return stripMatchingPathQuotes(trimmed.slice(1).trim());
|
|
1456
|
+
return stripMatchingPathQuotes(trimmed);
|
|
1450
1457
|
}
|
|
1451
1458
|
|
|
1452
1459
|
function expandHome(pathInput: string): string {
|
|
@@ -4341,6 +4348,13 @@ async function writeStudioSystemClipboard(text: string): Promise<{ ok: true; met
|
|
|
4341
4348
|
return { ok: false, error: errors.join("; ") || "No system clipboard command is available." };
|
|
4342
4349
|
}
|
|
4343
4350
|
|
|
4351
|
+
function decorateStudioPandocSyntaxHtml(html: string): string {
|
|
4352
|
+
return html.replace(
|
|
4353
|
+
/(<span class="kw">def<\/span>)(\s*)([A-Za-z_][A-Za-z0-9_]*)(?=\s*\()/g,
|
|
4354
|
+
(_match, keyword: string, spacing: string, name: string) => `${keyword}${spacing}<span class="fu">${name}</span>`,
|
|
4355
|
+
);
|
|
4356
|
+
}
|
|
4357
|
+
|
|
4344
4358
|
async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolean, resourcePath?: string, sourcePath?: string): Promise<string> {
|
|
4345
4359
|
const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
|
|
4346
4360
|
const markdownWithoutHtmlComments = isLatex ? markdown : stripStudioMarkdownHtmlComments(markdown);
|
|
@@ -4421,6 +4435,7 @@ async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolea
|
|
|
4421
4435
|
} else {
|
|
4422
4436
|
html = decorateStudioPreviewPageBreakHtml(html);
|
|
4423
4437
|
}
|
|
4438
|
+
html = decorateStudioPandocSyntaxHtml(html);
|
|
4424
4439
|
succeed(stripMathMlAnnotationTags(html));
|
|
4425
4440
|
return;
|
|
4426
4441
|
}
|
|
@@ -6075,7 +6090,7 @@ function isSshSession(): boolean {
|
|
|
6075
6090
|
|
|
6076
6091
|
function buildStudioSshTunnelHint(port: number): string | null {
|
|
6077
6092
|
if (!isSshSession()) return null;
|
|
6078
|
-
return `SSH detected.
|
|
6093
|
+
return `SSH detected. Forward the remote Studio port, then open the full Studio URL above locally, including its ?token=... parameter: ssh -L ${port}:127.0.0.1:${port} <remote-host>. If you choose a different local port, change only the port in the URL; keep the token.`;
|
|
6079
6094
|
}
|
|
6080
6095
|
|
|
6081
6096
|
function resolveRequestedStudioDocumentFromUrl(
|
|
@@ -6210,6 +6225,8 @@ function buildThemeCssVars(style: StudioThemeStyle): Record<string, string> {
|
|
|
6210
6225
|
style.mode === "light" ? 0.13 : 0.18,
|
|
6211
6226
|
style.mode === "light" ? "rgba(15, 23, 42, 0.06)" : "rgba(255, 255, 255, 0.07)",
|
|
6212
6227
|
);
|
|
6228
|
+
const rawCodeBlockBorder = blendColors(style.palette.mdCodeBlockBorder, style.palette.panel2, style.mode === "light" ? 0.62 : 0.72);
|
|
6229
|
+
const codeBlockBorder = capBorderContrast(rawCodeBlockBorder, style.palette.panel2, style.mode === "light" ? 1.16 : 1.18);
|
|
6213
6230
|
const diffAddedBg = withAlpha(style.palette.ok, style.mode === "light" ? 0.10 : 0.14, "rgba(46, 160, 67, 0.12)");
|
|
6214
6231
|
const diffRemovedBg = withAlpha(style.palette.error, style.mode === "light" ? 0.10 : 0.14, "rgba(248, 81, 73, 0.12)");
|
|
6215
6232
|
const okSoft = withAlpha(style.palette.ok, style.mode === "light" ? 0.10 : 0.12, "rgba(115, 209, 61, 0.08)");
|
|
@@ -6228,6 +6245,7 @@ function buildThemeCssVars(style: StudioThemeStyle): Record<string, string> {
|
|
|
6228
6245
|
const scratchpadHeaderBg = style.mode === "light" ? lightSecondarySurface : style.palette.panel2;
|
|
6229
6246
|
const scratchpadBodyBg = style.mode === "light" ? lightPrimarySurface : style.palette.panel;
|
|
6230
6247
|
const infoText = blendColors(style.palette.text, style.palette.muted, style.mode === "light" ? 0.36 : 0.30);
|
|
6248
|
+
const footerText = blendColors(style.palette.text, style.palette.muted, style.mode === "light" ? 0.50 : 0.42);
|
|
6231
6249
|
const headerActionBg = style.mode === "light" ? lightPrimarySurface : "transparent";
|
|
6232
6250
|
const headerActionHoverBg = style.mode === "light" ? lightPrimarySurface : style.palette.panel2;
|
|
6233
6251
|
const headerActionBorder = style.mode === "light" ? controlBorder : "transparent";
|
|
@@ -6264,7 +6282,7 @@ function buildThemeCssVars(style: StudioThemeStyle): Record<string, string> {
|
|
|
6264
6282
|
"--md-link-url": style.palette.mdLinkUrl,
|
|
6265
6283
|
"--md-code": style.palette.mdCode,
|
|
6266
6284
|
"--md-codeblock": style.palette.mdCodeBlock,
|
|
6267
|
-
"--md-codeblock-border":
|
|
6285
|
+
"--md-codeblock-border": codeBlockBorder,
|
|
6268
6286
|
"--md-quote": style.palette.mdQuote,
|
|
6269
6287
|
"--md-quote-border": style.palette.mdQuoteBorder,
|
|
6270
6288
|
"--md-hr": style.palette.mdHr,
|
|
@@ -6298,6 +6316,7 @@ function buildThemeCssVars(style: StudioThemeStyle): Record<string, string> {
|
|
|
6298
6316
|
"--scratchpad-header-bg": scratchpadHeaderBg,
|
|
6299
6317
|
"--scratchpad-body-bg": scratchpadBodyBg,
|
|
6300
6318
|
"--studio-info-text": infoText,
|
|
6319
|
+
"--studio-footer-text": footerText,
|
|
6301
6320
|
"--studio-header-action-bg": headerActionBg,
|
|
6302
6321
|
"--studio-header-action-hover-bg": headerActionHoverBg,
|
|
6303
6322
|
"--studio-header-action-border": headerActionBorder,
|
|
@@ -9469,10 +9488,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
9469
9488
|
return;
|
|
9470
9489
|
}
|
|
9471
9490
|
const counts = getStudioClientCounts();
|
|
9491
|
+
const url = buildStudioUrl(serverState.port, serverState.token, "full");
|
|
9472
9492
|
ctx.ui.notify(
|
|
9473
|
-
`Studio running at
|
|
9493
|
+
`Studio running at ${url} (busy: ${isStudioBusy() ? "yes" : "no"}; full views: ${counts.full}; editor-only views: ${counts.editorOnly})`,
|
|
9474
9494
|
"info",
|
|
9475
9495
|
);
|
|
9496
|
+
const sshTunnelHint = buildStudioSshTunnelHint(serverState.port);
|
|
9497
|
+
if (sshTunnelHint) ctx.ui.notify(sshTunnelHint, "info");
|
|
9476
9498
|
return;
|
|
9477
9499
|
}
|
|
9478
9500
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
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",
|