@evandu/super-pi 0.1.123 → 0.1.125

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-CK5tZxIo.js} +2 -2
  4. package/dist/client/assets/{dist-CRpnXCSn.js → dist-5O-klFrP.js} +1 -1
  5. package/dist/client/assets/{dist-D11ifrNu.js → dist-B8X48E9G.js} +1 -1
  6. package/dist/client/assets/{dist-Wq0CGa1P.js → dist-BC3_yB3K.js} +1 -1
  7. package/dist/client/assets/{dist-CrYrGCRr.js → dist-BDAJ6COK.js} +1 -1
  8. package/dist/client/assets/{dist-DOQGcum7.js → dist-BDULvJIt.js} +1 -1
  9. package/dist/client/assets/{dist-DOuFuHYr.js → dist-BR5dccfj.js} +1 -1
  10. package/dist/client/assets/{dist-CvUxw4HU.js → dist-BbK-QOz1.js} +1 -1
  11. package/dist/client/assets/{dist-CoqrRbfm.js → dist-Ck1wVFJ1.js} +1 -1
  12. package/dist/client/assets/{dist-B0G3TOfE.js → dist-CrnIVt5K.js} +1 -1
  13. package/dist/client/assets/{dist-DC4KdKrw.js → dist-D87R6Wol.js} +1 -1
  14. package/dist/client/assets/{dist-D899ixlt.js → dist-D90mwiSm.js} +1 -1
  15. package/dist/client/assets/{dist-4hqZVn6B.js → dist-D9IZ5pHN.js} +1 -1
  16. package/dist/client/assets/{dist-Ip0mRDF1.js → dist-DIa1uLNH.js} +1 -1
  17. package/dist/client/assets/{dist-CWcEOVNV.js → dist-DR8q7BI-.js} +1 -1
  18. package/dist/client/assets/{dist-GQ99vtKx.js → dist-Did2mkbm.js} +1 -1
  19. package/dist/client/assets/{dist-CjyJqaHj.js → dist-DsObObmI.js} +1 -1
  20. package/dist/client/assets/{dist-BqlvAjyT.js → dist-DsY1TPps.js} +1 -1
  21. package/dist/client/assets/{dist--hp7cqEH.js → dist-MYmiV3U3.js} +1 -1
  22. package/dist/client/assets/{dist-Dp9eqvWE.js → dist-Om17wCc8.js} +1 -1
  23. package/dist/client/assets/{dist-Cxo9Qf8Y.js → dist-fEUHAgwX.js} +1 -1
  24. package/dist/client/assets/{dist-CcAEnfzB.js → dist-nSf0tcKa.js} +1 -1
  25. package/dist/client/assets/{dist-CAFUK38E.js → dist-r_bnlg1k.js} +1 -1
  26. package/dist/client/assets/{index-CCnIvjP6.css → index-CBPWeTvl.css} +1 -1
  27. package/dist/client/assets/index-qTPZ6eno.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 +106 -0
  37. package/dist/server/providers.js +247 -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 +43 -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,23 @@ export type PublicProvider = {
405
405
  baseUrl: string;
406
406
  api: string;
407
407
  hasKey: boolean;
408
+ /**
409
+ * TRUE when the key lives in `models.json` itself (Pi's `"apiKey": "$MY_KEY"` form) rather than
410
+ * in `auth.json`. It is a credential all the same, so the panel must not report it as missing.
411
+ */
412
+ keyInModelsJson?: boolean;
413
+ /**
414
+ * TRUE when the provider is one of Pi's own, with no entry in `models.json`.
415
+ *
416
+ * Its models come from Pi's catalogue rather than from a file, so the panel can show it and switch
417
+ * it off but cannot offer an address to edit: there is no file field to edit.
418
+ */
419
+ builtin?: boolean;
420
+ /**
421
+ * TRUE when the provider is currently switched OFF: Pi does not offer it, and switching it back on
422
+ * restores the `models.json` entry and the credential exactly as they were.
423
+ */
424
+ disabled?: boolean;
408
425
  models: PublicProviderModel[];
409
426
  };
410
427
  export type DiscoveredModel = {
@@ -778,9 +795,14 @@ export type BackgroundTaskSummary = {
778
795
  * separate "default provider" anywhere in this state: a provider is not a thing this app can be
779
796
  * configured with, it is only the first half of a model id.
780
797
  */
798
+ export type TerminalShell = {
799
+ file: string;
800
+ args: string[];
801
+ };
781
802
  export type AppState = {
782
803
  modelFilterRules?: ModelFilterRule[];
783
804
  defaultModel?: ModelSelection;
805
+ terminalShell?: TerminalShell;
784
806
  workspaces: Workspace[];
785
807
  conversations: Conversation[];
786
808
  subagentRuns: SubagentRun[];
@@ -908,6 +930,9 @@ export type ClientCommandBody =
908
930
  } | {
909
931
  type: "terminal.close";
910
932
  sessionId: string;
933
+ } | {
934
+ type: "terminal.shell.set";
935
+ command: string | null;
911
936
  } | {
912
937
  type: "model.rules.set";
913
938
  rules: ModelFilterRule[];
@@ -945,6 +970,24 @@ export type ClientCommandBody =
945
970
  type: "providers.remove";
946
971
  providerId: string;
947
972
  }
973
+ /**
974
+ * Turns a provider OFF: takes its `models.json` entry and its `auth.json` credential out of Pi's
975
+ * configuration, remembering both so `providers.enable` can put them back.
976
+ *
977
+ * THE ONLY THING PI HONOURS. A provider's models are offered exactly when its auth is configured
978
+ * (`ModelRuntime.getAvailable()` filters on that), and a built-in provider such as `kimi-coding`
979
+ * has no `models.json` entry to remove at all - so the credential is the lever for every kind of
980
+ * provider, and taking it out is what "off" means for Pi itself, not just for this app.
981
+ */
982
+ | {
983
+ type: "providers.disable";
984
+ providerId: string;
985
+ }
986
+ /** Turns a previously disabled provider back on, restoring exactly what `disable` removed. */
987
+ | {
988
+ type: "providers.enable";
989
+ providerId: string;
990
+ }
948
991
  /**
949
992
  * Removes ONE model from a provider, leaving the provider and its other models alone.
950
993
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evandu/super-pi",
3
- "version": "0.1.123",
3
+ "version": "0.1.125",
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": {