@evandu/super-pi 0.1.122 → 0.1.124
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/dist/.super-pi-check-inputs.json +2 -2
- package/dist/client/.super-pi-client-sources.json +7 -7
- package/dist/client/assets/{CodeMirrorEditor-B2AXs2YB.js → CodeMirrorEditor-bNGSS1rQ.js} +2 -2
- package/dist/client/assets/{dist-CjyJqaHj.js → dist-BNFxxYvs.js} +1 -1
- package/dist/client/assets/{dist-DOQGcum7.js → dist-BZ-HTc7j.js} +1 -1
- package/dist/client/assets/{dist-DOuFuHYr.js → dist-BZB9Z8C-.js} +1 -1
- package/dist/client/assets/{dist-CWcEOVNV.js → dist-BdNKDCbA.js} +1 -1
- package/dist/client/assets/{dist-D11ifrNu.js → dist-Bea-jKEE.js} +1 -1
- package/dist/client/assets/{dist-CRpnXCSn.js → dist-Bf28S0tQ.js} +1 -1
- package/dist/client/assets/{dist-CrYrGCRr.js → dist-BkzoxIX4.js} +1 -1
- package/dist/client/assets/{dist-D899ixlt.js → dist-Boojk5ek.js} +1 -1
- package/dist/client/assets/{dist-GQ99vtKx.js → dist-BuO7_0WE.js} +1 -1
- package/dist/client/assets/{dist-Wq0CGa1P.js → dist-BwJvT1Pb.js} +1 -1
- package/dist/client/assets/{dist-4hqZVn6B.js → dist-C1MR_bG_.js} +1 -1
- package/dist/client/assets/{dist-CvUxw4HU.js → dist-C1W6z7MQ.js} +1 -1
- package/dist/client/assets/{dist-CAFUK38E.js → dist-CIxJmM-_.js} +1 -1
- package/dist/client/assets/{dist-Dp9eqvWE.js → dist-CROgPs2p.js} +1 -1
- package/dist/client/assets/{dist-CcAEnfzB.js → dist-CwC6b2Cl.js} +1 -1
- package/dist/client/assets/{dist-BqlvAjyT.js → dist-D-1R5iHi.js} +1 -1
- package/dist/client/assets/{dist-CoqrRbfm.js → dist-D8wvXcz3.js} +1 -1
- package/dist/client/assets/{dist-Ip0mRDF1.js → dist-DAs0bmNx.js} +1 -1
- package/dist/client/assets/{dist-B0G3TOfE.js → dist-DXsLC1J6.js} +1 -1
- package/dist/client/assets/{dist-DC4KdKrw.js → dist-D_E1E9X4.js} +1 -1
- package/dist/client/assets/{dist-Cxo9Qf8Y.js → dist-RFhXNQyY.js} +1 -1
- package/dist/client/assets/{dist--hp7cqEH.js → dist-U-IA_cdZ.js} +1 -1
- package/dist/client/assets/{index-CCnIvjP6.css → index-4kjGwKOr.css} +1 -1
- package/dist/client/assets/index-LL00oFAG.js +99 -0
- package/dist/client/index.html +2 -2
- package/dist/server/application/provider-service.d.ts +51 -2
- package/dist/server/application/provider-service.js +166 -11
- package/dist/server/application/run-service.js +14 -7
- package/dist/server/command-handler.js +21 -0
- package/dist/server/http-server.js +8 -0
- package/dist/server/index.js +2 -0
- package/dist/server/pi-session-events.d.ts +6 -1
- package/dist/server/providers.d.ts +101 -0
- package/dist/server/providers.js +241 -35
- package/dist/server/runtime.d.ts +1 -1
- package/dist/server/runtime.js +42 -20
- package/dist/server/store.d.ts +15 -0
- package/dist/server/store.js +34 -0
- package/dist/server/terminal.d.ts +51 -0
- package/dist/server/terminal.js +81 -12
- package/dist/server/types.d.ts +38 -0
- package/package.json +3 -1
- package/dist/client/assets/index-B0TMoUBp.js +0 -99
package/dist/server/terminal.js
CHANGED
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
* only way to get them. The cost is a native module, and it is paid only by operators who set
|
|
50
50
|
*/
|
|
51
51
|
import { createRequire } from "node:module";
|
|
52
|
-
import { existsSync } from "node:fs";
|
|
52
|
+
import { existsSync, accessSync, constants } from "node:fs";
|
|
53
|
+
import { delimiter, join } from "node:path";
|
|
53
54
|
import { isLocalOrPrivateAddress } from "./http-server.js";
|
|
54
55
|
let ptyModule;
|
|
55
56
|
let ptyLoadFailure;
|
|
@@ -122,21 +123,69 @@ const MINIMUM_PTY_VERSION = "1.2.0-beta.15";
|
|
|
122
123
|
* The ARGS differ by family and are not an arbitrary detail: `-l` is a bash/sh login flag, and passing it
|
|
123
124
|
* to PowerShell would make the shell refuse to start.
|
|
124
125
|
*/
|
|
125
|
-
function
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const file = (parts.shift() ?? "").replace(/^"|"$/g, "");
|
|
132
|
-
if (file)
|
|
133
|
-
return { file, args: parts };
|
|
134
|
-
}
|
|
126
|
+
export function parseShellCommand(command) {
|
|
127
|
+
const parts = command.match(/"[^"\\]*(?:\\.[^"\\]*)*"|'[^']*'|\S+/g) ?? [];
|
|
128
|
+
const file = (parts.shift() ?? "").replace(/^("|' )|("|' )$/g, "").replace(/^['"]|['"]$/g, "");
|
|
129
|
+
return file ? { file, args: parts.map((part) => part.replace(/^['"]|['"]$/g, "")) } : undefined;
|
|
130
|
+
}
|
|
131
|
+
function defaultPlatformShell(env = process.env) {
|
|
135
132
|
if (process.platform === "win32") {
|
|
136
133
|
return { file: env.COMSPEC?.toLowerCase().includes("pwsh") ? "pwsh.exe" : "powershell.exe", args: [] };
|
|
137
134
|
}
|
|
138
135
|
return { file: env.SHELL || "/bin/bash", args: ["-l"] };
|
|
139
136
|
}
|
|
137
|
+
export function defaultShell(env = process.env, configured) {
|
|
138
|
+
if (configured?.file)
|
|
139
|
+
return { file: configured.file, args: [...configured.args] };
|
|
140
|
+
const override = env.SUPER_PI_TERMINAL_SHELL?.trim();
|
|
141
|
+
if (override)
|
|
142
|
+
return parseShellCommand(override) ?? defaultPlatformShell(env);
|
|
143
|
+
return defaultPlatformShell(env);
|
|
144
|
+
}
|
|
145
|
+
export function shellSource(env = process.env, configured) {
|
|
146
|
+
return configured?.file ? "设置" : env.SUPER_PI_TERMINAL_SHELL?.trim() ? "环境变量" : "平台默认";
|
|
147
|
+
}
|
|
148
|
+
function commandOnPath(file, env) {
|
|
149
|
+
if (file.includes("/") || file.includes("\\"))
|
|
150
|
+
return existsSync(file);
|
|
151
|
+
for (const dir of (env.PATH ?? "").split(delimiter)) {
|
|
152
|
+
const names = process.platform === "win32" ? [file, ...(env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").map((ext) => file.endsWith(ext.toLowerCase()) ? file : file + ext.toLowerCase())] : [file];
|
|
153
|
+
if (names.some((name) => { try {
|
|
154
|
+
accessSync(join(dir, name), constants.X_OK);
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return false;
|
|
159
|
+
} }))
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
export function validateShellCommand(command, env = process.env) {
|
|
165
|
+
const parsed = parseShellCommand(command);
|
|
166
|
+
if (!parsed)
|
|
167
|
+
return "请输入要启动的 shell 命令。";
|
|
168
|
+
if (!commandOnPath(parsed.file, env))
|
|
169
|
+
return `找不到 ${parsed.file}:既不在 PATH 上,也不是一个存在的路径`;
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
export function terminalShellCandidates(env = process.env) {
|
|
173
|
+
const items = [];
|
|
174
|
+
if (process.platform === "win32") {
|
|
175
|
+
items.push({ file: "powershell.exe", args: [], label: "PowerShell" }, { file: "cmd.exe", args: [], label: "命令提示符" });
|
|
176
|
+
const git = "C:\\Program Files\\Git\\bin\\bash.exe";
|
|
177
|
+
if (existsSync(git))
|
|
178
|
+
items.push({ file: git, args: [], label: "Git Bash" });
|
|
179
|
+
if (commandOnPath("wsl.exe", env))
|
|
180
|
+
items.push({ file: "wsl.exe", args: ["-e", "bash"], label: "WSL Bash" });
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
for (const file of [env.SHELL, "bash", "zsh", "fish"])
|
|
184
|
+
if (file && commandOnPath(file, env) && !items.some((item) => item.file === file))
|
|
185
|
+
items.push({ file, args: ["-l"], label: file });
|
|
186
|
+
}
|
|
187
|
+
return items;
|
|
188
|
+
}
|
|
140
189
|
// ---------------------------------------------------------------------------------------------
|
|
141
190
|
// Bounds
|
|
142
191
|
// ---------------------------------------------------------------------------------------------
|
|
@@ -183,12 +232,32 @@ export class TerminalService {
|
|
|
183
232
|
workspaceFor;
|
|
184
233
|
/** Whether a command from this peer is allowed to touch a shell at all. */
|
|
185
234
|
isTrustedPeer;
|
|
235
|
+
configuredShell;
|
|
236
|
+
saveConfiguredShell;
|
|
186
237
|
constructor(options) {
|
|
187
238
|
this.workspaceFor = options.workspaceFor;
|
|
188
239
|
this.isTrustedPeer = options.isTrustedPeer;
|
|
240
|
+
this.configuredShell = options.configuredShell ?? (() => undefined);
|
|
241
|
+
this.saveConfiguredShell = options.saveConfiguredShell ?? (async () => { });
|
|
189
242
|
this.enabled = options.enabled ?? isTerminalEnabled();
|
|
190
243
|
}
|
|
191
244
|
enabled;
|
|
245
|
+
async setShell(command, remoteAddress) {
|
|
246
|
+
if (!this.enabled || !this.isTrustedPeer(remoteAddress))
|
|
247
|
+
return false;
|
|
248
|
+
if (command === null) {
|
|
249
|
+
await this.saveConfiguredShell(undefined);
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
const error = validateShellCommand(command);
|
|
253
|
+
if (error)
|
|
254
|
+
throw new Error(error);
|
|
255
|
+
const shell = parseShellCommand(command);
|
|
256
|
+
if (!shell)
|
|
257
|
+
throw new Error("请输入要启动的 shell 命令。");
|
|
258
|
+
await this.saveConfiguredShell(shell);
|
|
259
|
+
return true;
|
|
260
|
+
}
|
|
192
261
|
/**
|
|
193
262
|
* WHY THE PEER CHECK LIVES ON EVERY COMMAND AND NOT ONLY ON `open`.
|
|
194
263
|
*
|
|
@@ -243,7 +312,7 @@ export class TerminalService {
|
|
|
243
312
|
return;
|
|
244
313
|
}
|
|
245
314
|
const pty = loadPty();
|
|
246
|
-
const shell = defaultShell(process.env);
|
|
315
|
+
const shell = defaultShell(process.env, this.configuredShell());
|
|
247
316
|
const child = pty.spawn(shell.file, shell.args, {
|
|
248
317
|
// `xterm-256color` is what makes a shell enable colour and line editing; the client understands
|
|
249
318
|
// the same terminfo name, so the two ends agree on what the escape sequences mean.
|
package/dist/server/types.d.ts
CHANGED
|
@@ -405,6 +405,18 @@ export type PublicProvider = {
|
|
|
405
405
|
baseUrl: string;
|
|
406
406
|
api: string;
|
|
407
407
|
hasKey: boolean;
|
|
408
|
+
/**
|
|
409
|
+
* TRUE when the provider is one of Pi's own, with no entry in `models.json`.
|
|
410
|
+
*
|
|
411
|
+
* Its models come from Pi's catalogue rather than from a file, so the panel can show it and switch
|
|
412
|
+
* it off but cannot offer an address to edit: there is no file field to edit.
|
|
413
|
+
*/
|
|
414
|
+
builtin?: boolean;
|
|
415
|
+
/**
|
|
416
|
+
* TRUE when the provider is currently switched OFF: Pi does not offer it, and switching it back on
|
|
417
|
+
* restores the `models.json` entry and the credential exactly as they were.
|
|
418
|
+
*/
|
|
419
|
+
disabled?: boolean;
|
|
408
420
|
models: PublicProviderModel[];
|
|
409
421
|
};
|
|
410
422
|
export type DiscoveredModel = {
|
|
@@ -778,9 +790,14 @@ export type BackgroundTaskSummary = {
|
|
|
778
790
|
* separate "default provider" anywhere in this state: a provider is not a thing this app can be
|
|
779
791
|
* configured with, it is only the first half of a model id.
|
|
780
792
|
*/
|
|
793
|
+
export type TerminalShell = {
|
|
794
|
+
file: string;
|
|
795
|
+
args: string[];
|
|
796
|
+
};
|
|
781
797
|
export type AppState = {
|
|
782
798
|
modelFilterRules?: ModelFilterRule[];
|
|
783
799
|
defaultModel?: ModelSelection;
|
|
800
|
+
terminalShell?: TerminalShell;
|
|
784
801
|
workspaces: Workspace[];
|
|
785
802
|
conversations: Conversation[];
|
|
786
803
|
subagentRuns: SubagentRun[];
|
|
@@ -908,6 +925,9 @@ export type ClientCommandBody =
|
|
|
908
925
|
} | {
|
|
909
926
|
type: "terminal.close";
|
|
910
927
|
sessionId: string;
|
|
928
|
+
} | {
|
|
929
|
+
type: "terminal.shell.set";
|
|
930
|
+
command: string | null;
|
|
911
931
|
} | {
|
|
912
932
|
type: "model.rules.set";
|
|
913
933
|
rules: ModelFilterRule[];
|
|
@@ -945,6 +965,24 @@ export type ClientCommandBody =
|
|
|
945
965
|
type: "providers.remove";
|
|
946
966
|
providerId: string;
|
|
947
967
|
}
|
|
968
|
+
/**
|
|
969
|
+
* Turns a provider OFF: takes its `models.json` entry and its `auth.json` credential out of Pi's
|
|
970
|
+
* configuration, remembering both so `providers.enable` can put them back.
|
|
971
|
+
*
|
|
972
|
+
* THE ONLY THING PI HONOURS. A provider's models are offered exactly when its auth is configured
|
|
973
|
+
* (`ModelRuntime.getAvailable()` filters on that), and a built-in provider such as `kimi-coding`
|
|
974
|
+
* has no `models.json` entry to remove at all - so the credential is the lever for every kind of
|
|
975
|
+
* provider, and taking it out is what "off" means for Pi itself, not just for this app.
|
|
976
|
+
*/
|
|
977
|
+
| {
|
|
978
|
+
type: "providers.disable";
|
|
979
|
+
providerId: string;
|
|
980
|
+
}
|
|
981
|
+
/** Turns a previously disabled provider back on, restoring exactly what `disable` removed. */
|
|
982
|
+
| {
|
|
983
|
+
type: "providers.enable";
|
|
984
|
+
providerId: string;
|
|
985
|
+
}
|
|
948
986
|
/**
|
|
949
987
|
* Removes ONE model from a provider, leaving the provider and its other models alone.
|
|
950
988
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@evandu/super-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.124",
|
|
4
4
|
"description": "Web control plane and companion for Pi",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -126,6 +126,7 @@
|
|
|
126
126
|
"scripts/shared-chrome.test.mjs"
|
|
127
127
|
],
|
|
128
128
|
"check:browser": [
|
|
129
|
+
"scripts/interrupt-browser.test.mjs",
|
|
129
130
|
"scripts/revert-browser.test.mjs",
|
|
130
131
|
"scripts/extension-ui-browser.test.mjs",
|
|
131
132
|
"scripts/inline-rename-blur.test.mjs",
|
|
@@ -170,6 +171,7 @@
|
|
|
170
171
|
"scripts/optimistic-browser.test.mjs",
|
|
171
172
|
"scripts/active-memory-browser.test.mjs",
|
|
172
173
|
"scripts/knowledge-base-browser.test.mjs",
|
|
174
|
+
"scripts/terminal-browser.test.mjs",
|
|
173
175
|
"scripts/ux-polish-browser.test.mjs"
|
|
174
176
|
],
|
|
175
177
|
"dependencies": {
|