@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.
Files changed (46) hide show
  1. package/dist/.super-pi-check-inputs.json +2 -2
  2. package/dist/client/.super-pi-client-sources.json +7 -7
  3. package/dist/client/assets/{CodeMirrorEditor-B2AXs2YB.js → CodeMirrorEditor-bNGSS1rQ.js} +2 -2
  4. package/dist/client/assets/{dist-CjyJqaHj.js → dist-BNFxxYvs.js} +1 -1
  5. package/dist/client/assets/{dist-DOQGcum7.js → dist-BZ-HTc7j.js} +1 -1
  6. package/dist/client/assets/{dist-DOuFuHYr.js → dist-BZB9Z8C-.js} +1 -1
  7. package/dist/client/assets/{dist-CWcEOVNV.js → dist-BdNKDCbA.js} +1 -1
  8. package/dist/client/assets/{dist-D11ifrNu.js → dist-Bea-jKEE.js} +1 -1
  9. package/dist/client/assets/{dist-CRpnXCSn.js → dist-Bf28S0tQ.js} +1 -1
  10. package/dist/client/assets/{dist-CrYrGCRr.js → dist-BkzoxIX4.js} +1 -1
  11. package/dist/client/assets/{dist-D899ixlt.js → dist-Boojk5ek.js} +1 -1
  12. package/dist/client/assets/{dist-GQ99vtKx.js → dist-BuO7_0WE.js} +1 -1
  13. package/dist/client/assets/{dist-Wq0CGa1P.js → dist-BwJvT1Pb.js} +1 -1
  14. package/dist/client/assets/{dist-4hqZVn6B.js → dist-C1MR_bG_.js} +1 -1
  15. package/dist/client/assets/{dist-CvUxw4HU.js → dist-C1W6z7MQ.js} +1 -1
  16. package/dist/client/assets/{dist-CAFUK38E.js → dist-CIxJmM-_.js} +1 -1
  17. package/dist/client/assets/{dist-Dp9eqvWE.js → dist-CROgPs2p.js} +1 -1
  18. package/dist/client/assets/{dist-CcAEnfzB.js → dist-CwC6b2Cl.js} +1 -1
  19. package/dist/client/assets/{dist-BqlvAjyT.js → dist-D-1R5iHi.js} +1 -1
  20. package/dist/client/assets/{dist-CoqrRbfm.js → dist-D8wvXcz3.js} +1 -1
  21. package/dist/client/assets/{dist-Ip0mRDF1.js → dist-DAs0bmNx.js} +1 -1
  22. package/dist/client/assets/{dist-B0G3TOfE.js → dist-DXsLC1J6.js} +1 -1
  23. package/dist/client/assets/{dist-DC4KdKrw.js → dist-D_E1E9X4.js} +1 -1
  24. package/dist/client/assets/{dist-Cxo9Qf8Y.js → dist-RFhXNQyY.js} +1 -1
  25. package/dist/client/assets/{dist--hp7cqEH.js → dist-U-IA_cdZ.js} +1 -1
  26. package/dist/client/assets/{index-CCnIvjP6.css → index-4kjGwKOr.css} +1 -1
  27. package/dist/client/assets/index-LL00oFAG.js +99 -0
  28. package/dist/client/index.html +2 -2
  29. package/dist/server/application/provider-service.d.ts +51 -2
  30. package/dist/server/application/provider-service.js +166 -11
  31. package/dist/server/application/run-service.js +14 -7
  32. package/dist/server/command-handler.js +21 -0
  33. package/dist/server/http-server.js +8 -0
  34. package/dist/server/index.js +2 -0
  35. package/dist/server/pi-session-events.d.ts +6 -1
  36. package/dist/server/providers.d.ts +101 -0
  37. package/dist/server/providers.js +241 -35
  38. package/dist/server/runtime.d.ts +1 -1
  39. package/dist/server/runtime.js +42 -20
  40. package/dist/server/store.d.ts +15 -0
  41. package/dist/server/store.js +34 -0
  42. package/dist/server/terminal.d.ts +51 -0
  43. package/dist/server/terminal.js +81 -12
  44. package/dist/server/types.d.ts +38 -0
  45. package/package.json +3 -1
  46. package/dist/client/assets/index-B0TMoUBp.js +0 -99
@@ -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 defaultShell(env = process.env) {
126
- const override = env.SUPER_PI_TERMINAL_SHELL?.trim();
127
- if (override) {
128
- // Split on whitespace so an override can carry its own flags (`"C:\Program Files\Git\bin\bash.exe" -l`
129
- // has both a spaced path and a flag). A quoted first token is honoured.
130
- const parts = override.match(/"[^"]+"|\S+/g) ?? [];
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.
@@ -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.122",
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": {