pi-studio 0.9.44 → 0.9.45
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 +5 -0
- package/README.md +2 -2
- package/package.json +1 -1
- package/shared/studio-browser-launcher.js +93 -11
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,11 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.45] — 2026-08-20
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Open Studio in Muxy’s built-in browser when launched from a Muxy pane, using Muxy’s pane-local environment and CLI without global shell hooks, while retaining the bounded cmux and system-browser fallbacks.
|
|
11
|
+
|
|
7
12
|
## [0.9.44] — 2026-08-14
|
|
8
13
|
|
|
9
14
|
### Added
|
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ _The video shows an earlier version of the Studio interface. The basic workflow
|
|
|
63
63
|
|
|
64
64
|
| Command | Description |
|
|
65
65
|
|---|---|
|
|
66
|
-
| `/studio` | Open in cmux when available, otherwise the system browser, with the last assistant response (fallback: blank) |
|
|
66
|
+
| `/studio` | Open in Muxy or cmux when available, otherwise the system browser, with the last assistant response (fallback: blank) |
|
|
67
67
|
| `/studio <path>` | Open with file preloaded |
|
|
68
68
|
| `/studio --last` | Force last response |
|
|
69
69
|
| `/studio --blank` | Force blank editor |
|
|
@@ -150,7 +150,7 @@ Studio only passes icon-pack arguments when a diagram actually references `lucid
|
|
|
150
150
|
## Notes
|
|
151
151
|
|
|
152
152
|
- Local-only server (`127.0.0.1`) with tokenized Studio URLs.
|
|
153
|
-
- When Pi runs inside cmux, Studio opens
|
|
153
|
+
- When Pi runs inside Muxy or cmux, Studio opens in that terminal app’s built-in browser. Muxy is detected from its pane/socket environment without installing global hooks; cmux targets and focuses the caller’s workspace. If the detected terminal browser is unavailable, disabled, or declines the request, Studio falls back once to the system browser.
|
|
154
154
|
- 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. The SSH hint repeats the full URL so it is visible even if your terminal only shows the latest notification. Open that URL through the tunnel, preserving the `?token=...` parameter. If SSH is not auto-detected, use `/studio --no-browser`; for stable forwarding, use `/studio --port <port>` or combine them, e.g. `/studio --no-browser --port 3417`.
|
|
155
155
|
- 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.
|
|
156
156
|
- Studio is designed as a complement to terminal pi, not a replacement.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.45",
|
|
4
4
|
"description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, active quiz, prompt/response history, live previews, and tmux-backed REPL/literate REPL workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -1,6 +1,40 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
|
|
3
|
-
export const
|
|
3
|
+
export const STUDIO_TERMINAL_BROWSER_OPEN_TIMEOUT_MS = 5_000;
|
|
4
|
+
export const STUDIO_MUXY_CLI_TIMEOUT_SECONDS = 4;
|
|
5
|
+
// Retain the original exported name for callers that imported the cmux-only helper.
|
|
6
|
+
export const STUDIO_CMUX_BROWSER_OPEN_TIMEOUT_MS = STUDIO_TERMINAL_BROWSER_OPEN_TIMEOUT_MS;
|
|
7
|
+
|
|
8
|
+
/** @param {NodeJS.ProcessEnv | Record<string, string | undefined>} env */
|
|
9
|
+
function isStudioNestedZedTerminal(env) {
|
|
10
|
+
const zedTerm = String(env.ZED_TERM ?? "").trim().toLowerCase();
|
|
11
|
+
return zedTerm === "1" || zedTerm === "true";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Detect whether the current process is running inside Muxy itself rather than
|
|
16
|
+
* a nested application terminal that inherited Muxy's environment.
|
|
17
|
+
*
|
|
18
|
+
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>} [env]
|
|
19
|
+
* @returns {boolean}
|
|
20
|
+
*/
|
|
21
|
+
export function isStudioMuxySession(env = process.env) {
|
|
22
|
+
const paneId = String(env.MUXY_PANE_ID ?? "").trim();
|
|
23
|
+
const socketPath = String(env.MUXY_SOCKET_PATH ?? "").trim();
|
|
24
|
+
return Boolean(paneId && socketPath && !isStudioNestedZedTerminal(env));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Build the Muxy CLI invocation for opening Studio in its built-in browser.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} target
|
|
31
|
+
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>} [env]
|
|
32
|
+
* @returns {{ command: string, args: string[] } | undefined}
|
|
33
|
+
*/
|
|
34
|
+
export function getStudioMuxyBrowserOpenCommand(target, env = process.env) {
|
|
35
|
+
if (!isStudioMuxySession(env)) return undefined;
|
|
36
|
+
return { command: "muxy", args: ["browser", "open", target] };
|
|
37
|
+
}
|
|
4
38
|
|
|
5
39
|
/**
|
|
6
40
|
* Detect whether the current process is running inside cmux.
|
|
@@ -9,6 +43,7 @@ export const STUDIO_CMUX_BROWSER_OPEN_TIMEOUT_MS = 5_000;
|
|
|
9
43
|
* @returns {boolean}
|
|
10
44
|
*/
|
|
11
45
|
export function isStudioCmuxSession(env = process.env) {
|
|
46
|
+
if (isStudioNestedZedTerminal(env)) return false;
|
|
12
47
|
const workspaceId = String(env.CMUX_WORKSPACE_ID ?? "").trim();
|
|
13
48
|
const termProgram = String(env.TERM_PROGRAM ?? "").trim().toLowerCase();
|
|
14
49
|
const term = String(env.TERM ?? "").trim().toLowerCase();
|
|
@@ -73,24 +108,23 @@ function spawnDetachedBrowser(openCommand, spawnProcess) {
|
|
|
73
108
|
}
|
|
74
109
|
|
|
75
110
|
/**
|
|
76
|
-
* Try
|
|
111
|
+
* Try a terminal application's bounded browser-open command.
|
|
77
112
|
*
|
|
78
|
-
* @param {string}
|
|
113
|
+
* @param {{ command: string, args: string[] } | undefined} openCommand
|
|
79
114
|
* @param {{
|
|
80
|
-
* env?: NodeJS.ProcessEnv | Record<string, string | undefined>,
|
|
81
115
|
* spawnProcess?: typeof spawn,
|
|
82
116
|
* timeoutMs?: number,
|
|
117
|
+
* spawnEnv?: NodeJS.ProcessEnv | Record<string, string | undefined>,
|
|
83
118
|
* }} [options]
|
|
84
119
|
* @returns {Promise<boolean>}
|
|
85
120
|
*/
|
|
86
|
-
|
|
87
|
-
const openCommand = getStudioCmuxBrowserOpenCommand(target, options.env ?? process.env);
|
|
121
|
+
async function tryOpenStudioUrlWithTerminalBrowser(openCommand, options = {}) {
|
|
88
122
|
if (!openCommand) return false;
|
|
89
123
|
|
|
90
124
|
const spawnProcess = options.spawnProcess ?? spawn;
|
|
91
125
|
const timeoutMs = Number.isFinite(options.timeoutMs) && options.timeoutMs >= 0
|
|
92
126
|
? options.timeoutMs
|
|
93
|
-
:
|
|
127
|
+
: STUDIO_TERMINAL_BROWSER_OPEN_TIMEOUT_MS;
|
|
94
128
|
|
|
95
129
|
return await new Promise((resolve) => {
|
|
96
130
|
let settled = false;
|
|
@@ -108,7 +142,10 @@ export async function tryOpenStudioUrlInCmuxBrowser(target, options = {}) {
|
|
|
108
142
|
timeout.unref?.();
|
|
109
143
|
|
|
110
144
|
try {
|
|
111
|
-
|
|
145
|
+
const spawnOptions = options.spawnEnv
|
|
146
|
+
? { stdio: "ignore", env: options.spawnEnv }
|
|
147
|
+
: { stdio: "ignore" };
|
|
148
|
+
child = spawnProcess(openCommand.command, openCommand.args, spawnOptions);
|
|
112
149
|
} catch {
|
|
113
150
|
finish(false);
|
|
114
151
|
return;
|
|
@@ -119,7 +156,47 @@ export async function tryOpenStudioUrlInCmuxBrowser(target, options = {}) {
|
|
|
119
156
|
}
|
|
120
157
|
|
|
121
158
|
/**
|
|
122
|
-
*
|
|
159
|
+
* Try to open Studio in Muxy's built-in browser.
|
|
160
|
+
*
|
|
161
|
+
* @param {string} target
|
|
162
|
+
* @param {{
|
|
163
|
+
* env?: NodeJS.ProcessEnv | Record<string, string | undefined>,
|
|
164
|
+
* spawnProcess?: typeof spawn,
|
|
165
|
+
* timeoutMs?: number,
|
|
166
|
+
* }} [options]
|
|
167
|
+
* @returns {Promise<boolean>}
|
|
168
|
+
*/
|
|
169
|
+
export async function tryOpenStudioUrlInMuxyBrowser(target, options = {}) {
|
|
170
|
+
const env = options.env ?? process.env;
|
|
171
|
+
const openCommand = getStudioMuxyBrowserOpenCommand(target, env);
|
|
172
|
+
return await tryOpenStudioUrlWithTerminalBrowser(openCommand, {
|
|
173
|
+
...options,
|
|
174
|
+
spawnEnv: {
|
|
175
|
+
...env,
|
|
176
|
+
MUXY_CLI_TIMEOUT: String(STUDIO_MUXY_CLI_TIMEOUT_SECONDS),
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Try to open Studio in a focused cmux browser surface.
|
|
183
|
+
*
|
|
184
|
+
* @param {string} target
|
|
185
|
+
* @param {{
|
|
186
|
+
* env?: NodeJS.ProcessEnv | Record<string, string | undefined>,
|
|
187
|
+
* spawnProcess?: typeof spawn,
|
|
188
|
+
* timeoutMs?: number,
|
|
189
|
+
* }} [options]
|
|
190
|
+
* @returns {Promise<boolean>}
|
|
191
|
+
*/
|
|
192
|
+
export async function tryOpenStudioUrlInCmuxBrowser(target, options = {}) {
|
|
193
|
+
const openCommand = getStudioCmuxBrowserOpenCommand(target, options.env ?? process.env);
|
|
194
|
+
return await tryOpenStudioUrlWithTerminalBrowser(openCommand, options);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Open Studio in the caller's supported terminal browser when available,
|
|
199
|
+
* falling back once to the system browser.
|
|
123
200
|
*
|
|
124
201
|
* @param {string} target
|
|
125
202
|
* @param {{
|
|
@@ -128,10 +205,15 @@ export async function tryOpenStudioUrlInCmuxBrowser(target, options = {}) {
|
|
|
128
205
|
* spawnProcess?: typeof spawn,
|
|
129
206
|
* timeoutMs?: number,
|
|
130
207
|
* }} [options]
|
|
131
|
-
* @returns {Promise<"cmux" | "system">}
|
|
208
|
+
* @returns {Promise<"muxy" | "cmux" | "system">}
|
|
132
209
|
*/
|
|
133
210
|
export async function openStudioUrlInBrowser(target, options = {}) {
|
|
134
|
-
|
|
211
|
+
const env = options.env ?? process.env;
|
|
212
|
+
if (isStudioMuxySession(env)) {
|
|
213
|
+
if (await tryOpenStudioUrlInMuxyBrowser(target, options)) return "muxy";
|
|
214
|
+
} else if (isStudioCmuxSession(env)) {
|
|
215
|
+
if (await tryOpenStudioUrlInCmuxBrowser(target, options)) return "cmux";
|
|
216
|
+
}
|
|
135
217
|
|
|
136
218
|
const openCommand = getStudioDefaultBrowserOpenCommand(target, options.platform ?? process.platform);
|
|
137
219
|
await spawnDetachedBrowser(openCommand, options.spawnProcess ?? spawn);
|