@evandu/super-pi 0.1.81 → 0.1.83
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/README.md +174 -164
- package/bin/super-pi.js +59 -119
- package/dist/client/.super-pi-client-sources.json +5 -5
- package/dist/client/assets/index-DhOfFh0G.js +72 -0
- package/dist/client/assets/index-v2n_FYv_.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/application/run-service.js +49 -7
- package/dist/server/command-handler.d.ts +2 -4
- package/dist/server/command-handler.js +6 -8
- package/dist/server/http-server.js +23 -3
- package/dist/server/index.js +3 -7
- package/dist/server/runtime.js +49 -5
- package/dist/server/store.d.ts +24 -0
- package/dist/server/store.js +46 -0
- package/dist/server/terminal.d.ts +4 -6
- package/dist/server/terminal.js +9 -13
- package/dist/server/types.d.ts +15 -0
- package/dist/server/update.d.ts +13 -0
- package/dist/server/update.js +20 -0
- package/package.json +154 -151
- package/dist/client/assets/index-BXkoUfEn.js +0 -58
- package/dist/client/assets/index-BaL1YDWD.css +0 -1
package/README.md
CHANGED
|
@@ -1,164 +1,174 @@
|
|
|
1
|
-
# Super Pi
|
|
2
|
-
|
|
3
|
-
Super Pi is a cross-platform web control plane and companion for Pi (`@earendil-works/pi`).
|
|
4
|
-
|
|
5
|
-
It provides a rich Web UI featuring multi-workspace management, interactive agent chats, file browser, subagent delegation, real-time tool inspection, and extensible themes.
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Installation & Usage
|
|
10
|
-
|
|
11
|
-
### Method 1: Install as a Pi Plugin (Recommended)
|
|
12
|
-
|
|
13
|
-
Super Pi is designed to work seamlessly as an official/community extension for Pi:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
pi install npm:@evandu/super-pi
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Once installed, simply launch your Pi terminal as usual, and type:
|
|
20
|
-
|
|
21
|
-
```text
|
|
22
|
-
/super_pi
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
This will automatically start the background Super Pi server (port 3080) and open the Web UI in your default browser.
|
|
26
|
-
|
|
27
|
-
commands available inside pi:
|
|
28
|
-
- `/super_pi` — Open or launch Super Pi
|
|
29
|
-
- `/super_pi stop` — Stop the background Super Pi server
|
|
30
|
-
- `/super_pi restart` — Restart the Super Pi server
|
|
31
|
-
|
|
32
|
-
To update:
|
|
33
|
-
```bash
|
|
34
|
-
pi update @evandu/super-pi
|
|
35
|
-
# or update all Pi extensions:
|
|
36
|
-
pi update --extensions
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
### Method 2: Run directly via npx / Node
|
|
42
|
-
|
|
43
|
-
You can also run Super Pi standalone without Pi:
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
npx @evandu/super-pi start
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Or install globally:
|
|
50
|
-
|
|
51
|
-
```bash
|
|
52
|
-
npm install -g @evandu/super-pi
|
|
53
|
-
super-pi start # Start server and open browser
|
|
54
|
-
|
|
55
|
-
super-pi
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Super Pi
|
|
72
|
-
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
1
|
+
# Super Pi
|
|
2
|
+
|
|
3
|
+
Super Pi is a cross-platform web control plane and companion for Pi (`@earendil-works/pi`).
|
|
4
|
+
|
|
5
|
+
It provides a rich Web UI featuring multi-workspace management, interactive agent chats, file browser, subagent delegation, real-time tool inspection, and extensible themes.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation & Usage
|
|
10
|
+
|
|
11
|
+
### Method 1: Install as a Pi Plugin (Recommended)
|
|
12
|
+
|
|
13
|
+
Super Pi is designed to work seamlessly as an official/community extension for Pi:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pi install npm:@evandu/super-pi
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Once installed, simply launch your Pi terminal as usual, and type:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
/super_pi
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This will automatically start the background Super Pi server (port 3080) and open the Web UI in your default browser.
|
|
26
|
+
|
|
27
|
+
commands available inside pi:
|
|
28
|
+
- `/super_pi` — Open or launch Super Pi
|
|
29
|
+
- `/super_pi stop` — Stop the background Super Pi server
|
|
30
|
+
- `/super_pi restart` — Restart the Super Pi server
|
|
31
|
+
|
|
32
|
+
To update:
|
|
33
|
+
```bash
|
|
34
|
+
pi update @evandu/super-pi
|
|
35
|
+
# or update all Pi extensions:
|
|
36
|
+
pi update --extensions
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
### Method 2: Run directly via npx / Node
|
|
42
|
+
|
|
43
|
+
You can also run Super Pi standalone without Pi:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx @evandu/super-pi start
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or install globally:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
npm install -g @evandu/super-pi
|
|
53
|
+
super-pi start # Start server and open browser
|
|
54
|
+
super_pi start # The underscore spelling is also supported
|
|
55
|
+
super-pi stop # Stop the background server
|
|
56
|
+
super-pi restart # Restart the server
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
To start Super Pi automatically when the current user signs in, run:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
super_pi autostart enable
|
|
63
|
+
super_pi autostart status
|
|
64
|
+
super_pi autostart disable
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This registers a per-user startup entry on Windows, macOS, or Linux and does not require administrator access. The automatic process starts the local server without opening a browser. Use `--port` and `--host` with `autostart enable` when the server uses a non-default address.
|
|
68
|
+
|
|
69
|
+
Then visit `http://127.0.0.1:3080` in your browser (or use the printed Network URL `http://<lan-ip>:3080` for LAN access).
|
|
70
|
+
|
|
71
|
+
Super Pi listens on `0.0.0.0` by default. You can customize the host and port:
|
|
72
|
+
```bash
|
|
73
|
+
super-pi start --host 0.0.0.0 --port 3080
|
|
74
|
+
# Restrict to local loopback only:
|
|
75
|
+
super-pi start --host 127.0.0.1
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Active memory
|
|
81
|
+
|
|
82
|
+
Super Pi includes Hermes-style active memory. It saves only durable information, and keeps the three kinds of information separate:
|
|
83
|
+
|
|
84
|
+
- **User profile**: long-term preferences shared across workspaces.
|
|
85
|
+
- **Workspace facts**: repository conventions and environment facts, visible only in that workspace.
|
|
86
|
+
- **Skills**: validated reusable procedures and pitfalls, stored as Markdown and loaded on demand.
|
|
87
|
+
|
|
88
|
+
Open **Settings → 主动记忆** to inspect, edit, delete, or approve pending changes; the same panel controls the automatic review interval. Primary Agents can use `memory`, `skill_manage`, and `session_search`. Sub Agents can read relevant material but cannot write it.
|
|
89
|
+
|
|
90
|
+
### Composer references
|
|
91
|
+
|
|
92
|
+
Messages may include `@worktree:<name>` to attach the matching Git worktree path from the current workspace, or `@session:<id-or-title>` to attach the recent transcript of another session in the same workspace. These references are expanded into explicit context before the turn is sent to Pi, so an agent repairing a harness can inspect the session where it behaved poorly without switching away from the current session.
|
|
93
|
+
|
|
94
|
+
After the configured number of completed user turns (10 by default), Super Pi starts a separate, restricted review run. It can only use the three memory tools: it cannot read or modify project files, execute shell commands, or dispatch agents. New facts may be saved automatically; changing or deleting existing facts is staged for approval. A new message or stop cancels that conversation's review.
|
|
95
|
+
|
|
96
|
+
The feature is on by default. Set `SUPER_PI_MEMORY_ENABLED=0` to disable it, or set `SUPER_PI_MEMORY_REVIEW_INTERVAL=0` to keep manual memory while disabling automatic review. See [the design](docs/ACTIVE_MEMORY_DESIGN.md) for storage limits, lifecycle, and security boundaries.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Remote latency diagnosis
|
|
101
|
+
|
|
102
|
+
For a remote browser, enable phase tracing on the server for one test turn:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
SUPER_PI_TRACE_LATENCY=1 super-pi start
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The server writes `answer.begin`, `pi.spawned`, `prompt.sent`, `pi.first-record`, and `pi.settled` with elapsed milliseconds. This separates process/configuration startup from model time. Streaming is coalesced to one WebSocket update per 16 ms and rendered once per browser frame; structural events such as tool calls and state snapshots flush pending text first.
|
|
109
|
+
|
|
110
|
+
Use the output to compare a direct SSH Pi run with Super Pi. A long `answer.begin ? pi.spawned` indicates process startup or local configuration work; a long `pi.spawned ? prompt.sent` identifies pre-prompt validation/configuration; a long `prompt.sent ? pi.first-record` belongs to Pi/model/provider. If these phases are fast but the browser remains slow, inspect the network path between the browser and the server.
|
|
111
|
+
|
|
112
|
+
## Development
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Install dependencies
|
|
116
|
+
npm install
|
|
117
|
+
|
|
118
|
+
# Start development servers (frontend + backend hot-reload)
|
|
119
|
+
npm run dev
|
|
120
|
+
|
|
121
|
+
# Build for production
|
|
122
|
+
npm run build
|
|
123
|
+
|
|
124
|
+
# Run check suites
|
|
125
|
+
npm run check
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
## Web tools and terminal
|
|
136
|
+
|
|
137
|
+
### Add a sidebar tool
|
|
138
|
+
|
|
139
|
+
Open **Tools → 工具设置** and use **复制工具开发 Prompt** in the setup dialog. Ask the Super Pi agent to create the tool; its built-in `tool_register` submits the complete files and mounts the result globally, so the tool is available from every workspace without a separate server.
|
|
140
|
+
|
|
141
|
+
For a tool that needs local computation, include a Python script in the registration and declare its parameter schema. The script receives one JSON object on stdin and must print one JSON object on stdout. Load the built-in browser SDK from the mounted page:
|
|
142
|
+
|
|
143
|
+
```html
|
|
144
|
+
<script src="./super-pi-tool.js"></script>
|
|
145
|
+
<script>
|
|
146
|
+
async function calculate(params) {
|
|
147
|
+
const run = await SuperPiTool.run(params);
|
|
148
|
+
return await SuperPiTool.wait(run.id);
|
|
149
|
+
}
|
|
150
|
+
</script>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`SuperPiTool.run`, `get`, `wait`, and `cancel` are relative, tool-scoped APIs. A page should not call `/api/exec`, guess a tool id, start an HTTP server, or send shell commands. The server enforces the declared schema, timeout, cancellation, and Python process isolation.
|
|
154
|
+
|
|
155
|
+
Legacy URL bookmarks remain available for opening external HTTP/HTTPS pages, but they are not the mechanism for registering or running a Super Pi tool.
|
|
156
|
+
### Enable the terminal
|
|
157
|
+
|
|
158
|
+
The terminal is built in and starts with Super Pi. It is restricted to local and private-network connections.
|
|
159
|
+
|
|
160
|
+
PowerShell:
|
|
161
|
+
|
|
162
|
+
```powershell
|
|
163
|
+
npm start
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
macOS/Linux:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
npm start
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Select a workspace, then open the **Terminal** tab. The shell starts in that workspace. Windows uses PowerShell by default; set `SUPER_PI_TERMINAL_SHELL` to choose another shell. Terminal connections are limited to localhost and private-network addresses, and the browser cannot choose an arbitrary working directory.
|
|
173
|
+
|
|
174
|
+
See [docs/TERMINAL.md](docs/TERMINAL.md) for security details and troubleshooting.
|
package/bin/super-pi.js
CHANGED
|
@@ -1,128 +1,68 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import {
|
|
2
|
+
import { execFile, spawn } from "node:child_process";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { promisify } from "node:util";
|
|
4
8
|
|
|
5
|
-
const
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
const root = resolve(import.meta.dirname, "..");
|
|
11
|
+
const entry = resolve(root, "dist/server/index.js");
|
|
12
|
+
const prefix = root.match(/^(.*)[\\/]node_modules[\\/]/i)?.[1];
|
|
13
|
+
const platform = process.env.SUPER_PI_AUTOSTART_PLATFORM ?? process.platform;
|
|
14
|
+
const home = process.env.SUPER_PI_AUTOSTART_HOME ?? homedir();
|
|
15
|
+
const envPort = Number(process.env.SUPER_PI_PORT ?? 3080);
|
|
16
|
+
const defaultPort = Number.isInteger(envPort) && envPort > 0 ? envPort : 3080;
|
|
6
17
|
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
function options(args) {
|
|
19
|
+
const value = { port: defaultPort, host: process.env.SUPER_PI_HOST ?? "0.0.0.0", browser: true };
|
|
20
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
21
|
+
const arg = args[i];
|
|
22
|
+
if (arg === "--no-browser") value.browser = false;
|
|
23
|
+
else if (arg === "--browser") value.browser = true;
|
|
24
|
+
else if (arg === "--port") value.port = Number(args[++i]);
|
|
25
|
+
else if (arg.startsWith("--port=")) value.port = Number(arg.slice(7));
|
|
26
|
+
else if (arg === "--host") value.host = args[++i];
|
|
27
|
+
else if (arg.startsWith("--host=")) value.host = arg.slice(7);
|
|
28
|
+
else throw new Error(`Unknown option: ${arg}`);
|
|
18
29
|
}
|
|
30
|
+
if (!value.host || value.host === "undefined") throw new Error("Host must be provided after --host.");
|
|
31
|
+
if (!Number.isInteger(value.port) || value.port < 1 || value.port > 65535) throw new Error("Port must be an integer from 1 to 65535.");
|
|
32
|
+
return value;
|
|
19
33
|
}
|
|
20
34
|
|
|
21
|
-
async function
|
|
22
|
-
try {
|
|
23
|
-
const res = await fetch(`http://127.0.0.1:${port}/api/shutdown`, {
|
|
24
|
-
method: "POST",
|
|
25
|
-
signal: AbortSignal.timeout(3000),
|
|
26
|
-
});
|
|
27
|
-
return res.ok || res.status === 202;
|
|
28
|
-
} catch {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function openBrowser(url) {
|
|
34
|
-
const platform = process.platform;
|
|
35
|
-
if (platform === "darwin") spawn("open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
36
|
-
else if (platform === "win32") spawn("cmd.exe", ["/c", "start", "", url], { detached: true, stdio: "ignore" }).unref();
|
|
37
|
-
else spawn("xdg-open", [url], { detached: true, stdio: "ignore" }).unref();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function usage() {
|
|
41
|
-
console.error("Usage: super-pi [start|stop|restart|update]");
|
|
42
|
-
console.error(" start Open the web console (launch server if not running).");
|
|
43
|
-
console.error(" stop Shut the running server down.");
|
|
44
|
-
console.error(" restart Stop and start again, picking up the new code on disk.");
|
|
45
|
-
console.error(" update Update via npm to the latest @evandu/super-pi, then restart.");
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (sub === "stop") {
|
|
50
|
-
const running = await isRunning(port);
|
|
51
|
-
if (!running) { console.log(`Super Pi is not running on port ${port}`); process.exit(0); }
|
|
52
|
-
console.log(`Stopping Super Pi on port ${port}...`);
|
|
53
|
-
const ok = await stop(port);
|
|
54
|
-
console.log(ok ? "Shutdown requested." : "Shutdown request failed (server may need an access token).");
|
|
55
|
-
process.exit(0);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (sub === "update") {
|
|
59
|
-
// `npm install -g` picks up the latest published version, then we hand off to the same restart
|
|
60
|
-
// path the in-app button uses. Spawning detached so this terminal can return immediately and
|
|
61
|
-
// the user's shell isn't tied to the upgrade.
|
|
62
|
-
console.log("Updating @evandu/super-pi via npm...");
|
|
63
|
-
const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
64
|
-
const updater = spawn(npmCmd, ["install", "-g", "@evandu/super-pi"], {
|
|
65
|
-
stdio: "inherit",
|
|
66
|
-
shell: process.platform === "win32",
|
|
67
|
-
env: process.env,
|
|
68
|
-
});
|
|
69
|
-
updater.on("error", (error) => {
|
|
70
|
-
console.error(`npm install failed: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
71
|
-
process.exit(1);
|
|
72
|
-
});
|
|
73
|
-
updater.on("close", (code) => {
|
|
74
|
-
if (code !== 0) { console.error(`npm install exited with code ${code}`); process.exit(code ?? 1); }
|
|
75
|
-
console.log("Update installed. Restarting Super Pi...");
|
|
76
|
-
// The server itself uses scripts/restart-server.mjs for the same job; reuse it.
|
|
77
|
-
const restart = spawn(process.execPath, [resolve(import.meta.dirname, "../scripts/restart-server.mjs")], {
|
|
78
|
-
detached: true,
|
|
79
|
-
stdio: "ignore",
|
|
80
|
-
env: process.env,
|
|
81
|
-
});
|
|
82
|
-
restart.unref();
|
|
83
|
-
process.exit(0);
|
|
84
|
-
});
|
|
85
|
-
// The handler above is the only path out; the process keeps running so npm's output reaches
|
|
86
|
-
// this terminal.
|
|
87
|
-
// Keep the process alive until the updater's close handler exits it.
|
|
88
|
-
await new Promise(() => {});
|
|
35
|
+
async function running(port) {
|
|
36
|
+
try { const response = await fetch(`http://127.0.0.1:${port}/api/health`, { signal: AbortSignal.timeout(1000) }); return response.ok && (await response.json()).service === "super-pi"; } catch { return false; }
|
|
89
37
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const running = await isRunning(port);
|
|
104
|
-
if (!running) {
|
|
105
|
-
console.log("Starting Super Pi...");
|
|
106
|
-
const child = spawn(process.execPath, [entry, `--port=${port}`], {
|
|
107
|
-
detached: true,
|
|
108
|
-
stdio: "ignore",
|
|
109
|
-
env: process.env,
|
|
110
|
-
});
|
|
111
|
-
child.unref();
|
|
112
|
-
|
|
113
|
-
const deadline = Date.now() + 8000;
|
|
114
|
-
while (Date.now() < deadline) {
|
|
115
|
-
await new Promise((r) => setTimeout(r, 200));
|
|
116
|
-
if (await isRunning(port)) break;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (!(await isRunning(port))) {
|
|
120
|
-
console.error("Server did not respond within 8 seconds.");
|
|
121
|
-
process.exit(1);
|
|
122
|
-
}
|
|
123
|
-
console.log(`Super Pi is running at http://127.0.0.1:${port}`);
|
|
38
|
+
async function stop(port) { try { const headers = process.env.SUPER_PI_ACCESS_TOKEN ? { authorization: `Bearer ${process.env.SUPER_PI_ACCESS_TOKEN}` } : {}; const response = await fetch(`http://127.0.0.1:${port}/api/shutdown`, { method: "POST", headers, signal: AbortSignal.timeout(3000) }); return response.ok || response.status === 202; } catch { return false; } }
|
|
39
|
+
function openBrowser(url) { const command = platform === "darwin" ? "open" : platform === "win32" ? "cmd.exe" : "xdg-open"; const args = platform === "darwin" ? [url] : platform === "win32" ? ["/c", "start", "", url] : [url]; spawn(command, args, { detached: true, stdio: "ignore", windowsHide: true }).unref(); }
|
|
40
|
+
function usage() { console.log("Usage: super-pi|super_pi <start|stop|restart|update|autostart> [options]"); console.log(" autostart enable|disable|status Configure startup at sign-in"); console.log(" --port PORT Server port (default: 3080)"); console.log(" --host HOST Server host (default: 0.0.0.0)"); console.log(" --no-browser Do not open a browser"); }
|
|
41
|
+
function paths() { if (platform === "win32") return { task: "Super Pi" }; if (platform === "darwin") return { file: join(home, "Library", "LaunchAgents", "com.evandu.super-pi.plist") }; return { file: join(process.env.XDG_CONFIG_HOME ?? join(home, ".config"), "systemd", "user", "super-pi.service") }; }
|
|
42
|
+
async function system(command, args) { if (process.env.SUPER_PI_AUTOSTART_DRY_RUN === "1") { console.log(`[dry-run] ${command} ${args.join(" ")}`); return; } await execFileAsync(command, args, { windowsHide: true }); }
|
|
43
|
+
async function enable(value) {
|
|
44
|
+
const target = paths();
|
|
45
|
+
if (platform === "win32") { const command = `"${process.execPath}" "${entry}" --host "${value.host}" --port ${value.port} --no-browser`; await system("schtasks.exe", ["/Create", "/TN", target.task, "/SC", "ONLOGON", "/TR", command, "/RL", "LIMITED", "/F"]); }
|
|
46
|
+
else if (platform === "darwin") { const content = `<?xml version="1.0" encoding="UTF-8"?><plist version="1.0"><dict><key>Label</key><string>com.evandu.super-pi</string><key>ProgramArguments</key><array><string>${process.execPath}</string><string>${entry}</string><string>--host</string><string>${value.host}</string><string>--port</string><string>${value.port}</string></array><key>RunAtLoad</key><true/><key>KeepAlive</key><true/></dict></plist>`; if (process.env.SUPER_PI_AUTOSTART_DRY_RUN !== "1") { await mkdir(dirname(target.file), { recursive: true }); await writeFile(target.file, content); } else console.log(`[dry-run] write ${target.file}\n${content}`); await system("launchctl", ["bootout", `gui/${process.getuid?.() ?? "501"}`, target.file]).catch(() => {}); await system("launchctl", ["bootstrap", `gui/${process.getuid?.() ?? "501"}`, target.file]); }
|
|
47
|
+
else { const content = `[Unit]\nDescription=Super Pi\nAfter=default.target\n\n[Service]\nExecStart=${process.execPath} ${entry} --host ${value.host} --port ${value.port}\nRestart=on-failure\n\n[Install]\nWantedBy=default.target\n`; if (process.env.SUPER_PI_AUTOSTART_DRY_RUN !== "1") { await mkdir(dirname(target.file), { recursive: true }); await writeFile(target.file, content); } else console.log(`[dry-run] write ${target.file}\n${content}`); await system("systemctl", ["--user", "daemon-reload"]); await system("systemctl", ["--user", "enable", "--now", "super-pi.service"]); }
|
|
48
|
+
console.log("Automatic startup enabled.");
|
|
124
49
|
}
|
|
50
|
+
async function disable() { const target = paths(); if (platform === "win32") await system("schtasks.exe", ["/Delete", "/TN", target.task, "/F"]).catch(() => {}); else if (platform === "darwin") { await system("launchctl", ["bootout", `gui/${process.getuid?.() ?? "501"}`, target.file]).catch(() => {}); if (process.env.SUPER_PI_AUTOSTART_DRY_RUN !== "1") await rm(target.file, { force: true }); } else { await system("systemctl", ["--user", "disable", "--now", "super-pi.service"]).catch(() => {}); if (process.env.SUPER_PI_AUTOSTART_DRY_RUN !== "1") await rm(target.file, { force: true }); } console.log("Automatic startup disabled."); }
|
|
51
|
+
async function status() { const target = paths(); let enabled = false; if (platform === "win32") { try { await execFileAsync("schtasks.exe", ["/Query", "/TN", target.task]); enabled = true; } catch {} } else enabled = existsSync(target.file); console.log(enabled ? "Automatic startup is enabled." : "Automatic startup is disabled."); }
|
|
52
|
+
async function start(value) { if (await running(value.port)) return; const child = spawn(process.execPath, [entry, `--port=${value.port}`, `--host=${value.host}`], { detached: true, stdio: "ignore", env: process.env, windowsHide: true }); child.unref(); const until = Date.now() + 8000; while (Date.now() < until && !(await running(value.port))) await new Promise((resolve) => setTimeout(resolve, 200)); if (!(await running(value.port))) throw new Error("Server did not respond within 8 seconds."); }
|
|
125
53
|
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
54
|
+
const [raw, ...rest] = process.argv.slice(2);
|
|
55
|
+
const implicitStart = raw?.startsWith("-") ?? false;
|
|
56
|
+
const command = implicitStart ? "start" : raw?.toLowerCase();
|
|
57
|
+
const args = implicitStart ? [raw, ...rest] : rest;
|
|
58
|
+
try {
|
|
59
|
+
if (command === "help" || command === "--help" || command === "-h") { usage(); process.exit(0); }
|
|
60
|
+
if (!command) { const value = options(args); await start(value); const url = `http://127.0.0.1:${value.port}`; if (value.browser) openBrowser(url); console.log(value.browser ? `Opened ${url}` : `Super Pi is running at ${url}`); process.exit(0); }
|
|
61
|
+
if (command === "autostart" || command === "startup") { const action = args.shift()?.toLowerCase() ?? "status"; const value = options(args); if (action === "enable") await enable(value); else if (action === "disable") await disable(); else if (action === "status") await status(); else throw new Error("Autostart action must be enable, disable, or status."); process.exit(0); }
|
|
62
|
+
if (!["start", "stop", "restart", "update"].includes(command)) throw new Error(`Unknown command: ${command}`);
|
|
63
|
+
if (command === "update") { const npm = platform === "win32" ? "npm.cmd" : "npm"; const args = prefix ? ["install", "--prefix", prefix, "@evandu/super-pi"] : ["install", "-g", "@evandu/super-pi"]; const child = spawn(npm, args, { stdio: "inherit", shell: platform === "win32", env: process.env }); const code = await new Promise((resolve) => child.on("close", resolve)); if (code !== 0) process.exit(code ?? 1); }
|
|
64
|
+
const value = options(args);
|
|
65
|
+
if (command === "stop") { console.log(await running(value.port) ? (await stop(value.port) ? `Shutdown requested on port ${value.port}.` : "Shutdown request failed.") : `Super Pi is not running on port ${value.port}.`); process.exit(0); }
|
|
66
|
+
if ((command === "restart" || command === "update") && await running(value.port)) { await stop(value.port); await new Promise((resolve) => setTimeout(resolve, 600)); }
|
|
67
|
+
await start(value); const url = `http://127.0.0.1:${value.port}`; if (value.browser) openBrowser(url); console.log(value.browser ? `Opened ${url}` : `Super Pi is running at ${url}`);
|
|
68
|
+
} catch (error) { console.error(error instanceof Error ? error.message : String(error)); usage(); process.exit(1); }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"hash": "
|
|
2
|
+
"hash": "59d7e55dddc214a9c6ca40ce240cf5d420c82882d29d3d5dbbf344884ed9e30e",
|
|
3
3
|
"files": {
|
|
4
|
-
"src/client/App.tsx": "
|
|
4
|
+
"src/client/App.tsx": "65bec0788a20d51690adaa247bbf56e22183093eeee8d07555fec7eb4c94ec0a",
|
|
5
5
|
"src/client/FilesPanel.tsx": "17c3e7ef10914a8836c209bc47a51ad06d73025feb755ee8f68bba8f79ce6d16",
|
|
6
6
|
"src/client/GitPanel.tsx": "c44f3c73936af73f436ec6a8cd9ee50f5367dd6887f881fa133ec101fb99bbac",
|
|
7
7
|
"src/client/HelpWindow.tsx": "807a3e7119e958701bcfcef2a3cb66d6ec05bca1df96e4639c5ae77d8cb79975",
|
|
@@ -9,17 +9,17 @@
|
|
|
9
9
|
"src/client/MemoryPanel.tsx": "f0475b526e3380407a82ccaa3f9229fe62c091122f1e452797f3ce3316074cee",
|
|
10
10
|
"src/client/SlashCommandMenu.tsx": "47d4051df75507c15c894cff590f538a0223f3a365aa5f1b90925d86670972d6",
|
|
11
11
|
"src/client/Tabs.tsx": "6e48f318b1fe4678f438c6e2a41eeb49e89ee9c67b981f7d6154cd7060a048ab",
|
|
12
|
-
"src/client/TerminalPanel.tsx": "
|
|
12
|
+
"src/client/TerminalPanel.tsx": "0437ac55a24823bf24548569f383dd29480ab2340ed789c0e7e8336e5af54e28",
|
|
13
13
|
"src/client/WorkflowCard.tsx": "c363f9b3b612745f91fb46ec16d188fde0f36af7e406499d27eb552793e9fe22",
|
|
14
14
|
"src/client/main.tsx": "d6325cec79ebc225e4972c33ad9b4266fa1eab5f962fec36ed4c539124a8fcab",
|
|
15
15
|
"src/client/markdown.ts": "4848429b351ec1030dd8e108f3d6ff3f93f2b558c2c27a310a5290af130186a6",
|
|
16
16
|
"src/client/slash-commands.ts": "a202263497e3ca023e40f3462e04f858cfbddb5ed19a40d188bd5363f3762621",
|
|
17
|
-
"src/client/styles.css": "
|
|
17
|
+
"src/client/styles.css": "04962f62cea0978471e597d0cf5a2bd1b364e2e598dcb37afa8fe2e14dce6328",
|
|
18
18
|
"src/client/terminal-emulator.ts": "ffcc4f5fbddaf25cebcc9e03dbe5d7d0ad2c21586f5d169e57c04c82e2308b3b",
|
|
19
19
|
"src/client/theme.tsx": "d01885d69a3b363a8bcbbd0c6bfbc4ae8004fa18c1f93f4834c47b4ef743a60c",
|
|
20
20
|
"src/client/tools.ts": "58fe41f0edbd490e3fab7f12f258b1dbc2163b653553b03ac61caf1543618e50",
|
|
21
21
|
"src/client/types.ts": "11fd40e5784a2e765635f4f57d6edd5c97ad0eae85b1825b9da32a88d25b4db9",
|
|
22
22
|
"src/client/vite-env.d.ts": "65996936fbb042915f7b74a200fcdde7e410f32a669b1ab9597cfaa4b0faddb5"
|
|
23
23
|
},
|
|
24
|
-
"recordedAt": "2026-10-
|
|
24
|
+
"recordedAt": "2026-10-18T02:43:39.581Z"
|
|
25
25
|
}
|