@frumu/tandem-panel 0.3.28 → 0.4.3
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/.env.example +20 -1
- package/README.md +44 -15
- package/bin/cli.js +134 -0
- package/bin/init-env.js +10 -84
- package/bin/service-local.sh +63 -0
- package/bin/service-runner.js +48 -0
- package/bin/setup.js +3412 -263
- package/dist/assets/index-4aX9RGDL.css +1 -0
- package/dist/assets/index-CpH1IYO9.js +22 -0
- package/dist/assets/markdown-DMcD1LHz.js +60 -0
- package/dist/assets/motion-BCvrfAt1.js +9 -0
- package/dist/assets/preact-vendor-jo0muZ28.js +1 -0
- package/dist/assets/react-query-PgFuErlI.js +1 -0
- package/dist/assets/vendor-CdeM8LjL.js +42 -0
- package/dist/index.html +8 -3
- package/package.json +16 -5
- package/dist/assets/index-D3Dg1Jyu.css +0 -1
- package/dist/assets/index-buoKzueq.js +0 -1335
package/.env.example
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
# Control panel bind port
|
|
2
2
|
TANDEM_CONTROL_PANEL_PORT=39732
|
|
3
3
|
|
|
4
|
-
# Tip: run `tandem-
|
|
4
|
+
# Tip: run `tandem-setup init` to auto-generate this file and token.
|
|
5
|
+
|
|
6
|
+
# Control panel bind host (loopback by default)
|
|
7
|
+
TANDEM_CONTROL_PANEL_HOST=127.0.0.1
|
|
8
|
+
|
|
9
|
+
# Optional externally reachable URL for future pairing / gateway flows
|
|
10
|
+
TANDEM_CONTROL_PANEL_PUBLIC_URL=
|
|
5
11
|
|
|
6
12
|
# Full engine URL (preferred)
|
|
7
13
|
TANDEM_ENGINE_URL=http://127.0.0.1:39731
|
|
@@ -13,6 +19,10 @@ TANDEM_ENGINE_PORT=39731
|
|
|
13
19
|
# Auto-start local engine if not running (1=yes, 0=no)
|
|
14
20
|
TANDEM_CONTROL_PANEL_AUTO_START_ENGINE=1
|
|
15
21
|
|
|
22
|
+
# Canonical state roots for official bootstrap installs
|
|
23
|
+
TANDEM_STATE_DIR=
|
|
24
|
+
TANDEM_CONTROL_PANEL_STATE_DIR=
|
|
25
|
+
|
|
16
26
|
# Engine API token used when control panel auto-starts a local engine.
|
|
17
27
|
# If unset, the panel generates one at startup and prints it in logs.
|
|
18
28
|
TANDEM_CONTROL_PANEL_ENGINE_TOKEN=tk_change_me
|
|
@@ -26,6 +36,15 @@ TANDEM_PROVIDER_STREAM_IDLE_TIMEOUT_MS=90000
|
|
|
26
36
|
TANDEM_PERMISSION_WAIT_TIMEOUT_MS=15000
|
|
27
37
|
TANDEM_TOOL_EXEC_TIMEOUT_MS=45000
|
|
28
38
|
TANDEM_BASH_TIMEOUT_MS=30000
|
|
39
|
+
# Bug Monitor (disabled by default)
|
|
40
|
+
# TANDEM_BUG_MONITOR_ENABLED=0
|
|
41
|
+
# TANDEM_BUG_MONITOR_REPO=owner/repo
|
|
42
|
+
# TANDEM_BUG_MONITOR_MCP_SERVER=github
|
|
43
|
+
# TANDEM_BUG_MONITOR_PROVIDER_PREFERENCE=auto
|
|
44
|
+
# TANDEM_BUG_MONITOR_PROVIDER_ID=openrouter
|
|
45
|
+
# TANDEM_BUG_MONITOR_MODEL_ID=openai/gpt-4.1-mini
|
|
46
|
+
# Optional global duplicate-signature retry limit for tool calls.
|
|
47
|
+
# TANDEM_TOOL_LOOP_DUPLICATE_SIGNATURE_LIMIT=200
|
|
29
48
|
|
|
30
49
|
# Optional caps when guard budgets are enabled
|
|
31
50
|
# TANDEM_TOOL_BUDGET_DEFAULT=10
|
package/README.md
CHANGED
|
@@ -8,40 +8,47 @@ Full web control center for Tandem Engine (non-desktop entry point).
|
|
|
8
8
|
npm i -g @frumu/tandem-panel
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Official Bootstrap
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
tandem-
|
|
14
|
+
tandem-setup init
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
This creates a canonical env file, bootstraps engine state, and installs services on Linux/macOS when run with the privileges needed for service registration.
|
|
18
|
+
|
|
19
|
+
Useful follow-up commands:
|
|
18
20
|
|
|
19
21
|
```bash
|
|
20
|
-
tandem-setup
|
|
22
|
+
tandem-setup doctor
|
|
23
|
+
tandem-setup service status
|
|
24
|
+
tandem-setup service restart
|
|
25
|
+
tandem-setup pair mobile
|
|
21
26
|
```
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
## Run Foreground
|
|
24
29
|
|
|
25
30
|
```bash
|
|
26
|
-
tandem-control-panel
|
|
31
|
+
tandem-control-panel
|
|
27
32
|
```
|
|
28
33
|
|
|
29
34
|
Or:
|
|
30
35
|
|
|
31
36
|
```bash
|
|
32
|
-
tandem-
|
|
37
|
+
tandem-setup run
|
|
33
38
|
```
|
|
34
39
|
|
|
35
|
-
|
|
40
|
+
## Service Management
|
|
36
41
|
|
|
37
42
|
```bash
|
|
38
|
-
|
|
43
|
+
tandem-setup service install
|
|
44
|
+
tandem-setup service status
|
|
45
|
+
tandem-setup service restart
|
|
46
|
+
tandem-setup service logs
|
|
39
47
|
```
|
|
40
48
|
|
|
41
|
-
|
|
49
|
+
Legacy flag mode is still supported for compatibility:
|
|
42
50
|
|
|
43
|
-
- `--
|
|
44
|
-
- `--service-user=<linux-user>` (default: `SUDO_USER`/current user)
|
|
51
|
+
`tandem-control-panel --init`, `--install-services`, and `--service-op=...`
|
|
45
52
|
|
|
46
53
|
## Features
|
|
47
54
|
|
|
@@ -68,8 +75,12 @@ cp .env.example .env
|
|
|
68
75
|
Variables:
|
|
69
76
|
|
|
70
77
|
- `TANDEM_CONTROL_PANEL_PORT` (default `39732`)
|
|
78
|
+
- `TANDEM_CONTROL_PANEL_HOST` (default `127.0.0.1`)
|
|
79
|
+
- `TANDEM_CONTROL_PANEL_PUBLIC_URL` (optional future pairing / gateway URL)
|
|
71
80
|
- `TANDEM_ENGINE_URL` (default `http://127.0.0.1:39731`)
|
|
72
81
|
- `TANDEM_ENGINE_HOST` + `TANDEM_ENGINE_PORT` fallback
|
|
82
|
+
- `TANDEM_STATE_DIR` (canonical engine state dir for official installs)
|
|
83
|
+
- `TANDEM_CONTROL_PANEL_STATE_DIR` (control-panel state dir for official installs)
|
|
73
84
|
- `TANDEM_CONTROL_PANEL_AUTO_START_ENGINE` (`1`/`0`)
|
|
74
85
|
- `TANDEM_CONTROL_PANEL_ENGINE_TOKEN` (token injected when panel auto-starts engine)
|
|
75
86
|
- `TANDEM_API_TOKEN` (backward-compatible alias for engine token)
|
|
@@ -117,9 +128,10 @@ Notes:
|
|
|
117
128
|
|
|
118
129
|
## Setup Flow
|
|
119
130
|
|
|
120
|
-
1. Run `tandem-
|
|
121
|
-
2.
|
|
122
|
-
3.
|
|
131
|
+
1. Run `tandem-setup init`.
|
|
132
|
+
2. Verify with `tandem-setup doctor`.
|
|
133
|
+
3. If running foreground, start `tandem-control-panel`.
|
|
134
|
+
4. Sign in with the printed `TANDEM_CONTROL_PANEL_ENGINE_TOKEN`.
|
|
123
135
|
|
|
124
136
|
## Development
|
|
125
137
|
|
|
@@ -129,3 +141,20 @@ npm install
|
|
|
129
141
|
npm run dev
|
|
130
142
|
npm run build
|
|
131
143
|
```
|
|
144
|
+
|
|
145
|
+
### Repo Source Workflow (No Global npm Install)
|
|
146
|
+
|
|
147
|
+
If you run from this repo directly, use:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
node packages/tandem-control-panel/bin/cli.js init --no-service
|
|
151
|
+
node packages/tandem-control-panel/bin/cli.js run
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Service install/ops from source:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
sudo node packages/tandem-control-panel/bin/cli.js service install
|
|
158
|
+
node packages/tandem-control-panel/bin/cli.js service status
|
|
159
|
+
sudo node packages/tandem-control-panel/bin/cli.js service restart
|
|
160
|
+
```
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { basename } from "path";
|
|
4
|
+
import { spawn } from "child_process";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
import { printInitSummary, initializeInstall } from "../lib/setup/bootstrap.js";
|
|
8
|
+
import { printDoctor, runDoctor } from "../lib/setup/doctor.js";
|
|
9
|
+
import { parseCliArgs, err } from "../lib/setup/common.js";
|
|
10
|
+
import { resolveSetupPaths } from "../lib/setup/paths.js";
|
|
11
|
+
import { installSystemdServices, operateSystemdServices } from "../lib/setup/services/systemd.js";
|
|
12
|
+
import { installLaunchdServices, operateLaunchdServices } from "../lib/setup/services/launchd.js";
|
|
13
|
+
import { resolveUserHome } from "../lib/setup/services/common.js";
|
|
14
|
+
|
|
15
|
+
const argv = process.argv.slice(2);
|
|
16
|
+
const cli = parseCliArgs(argv);
|
|
17
|
+
const entry = basename(process.argv[1] || "tandem-setup");
|
|
18
|
+
const setupLegacyPath = fileURLToPath(new URL("./setup.js", import.meta.url));
|
|
19
|
+
|
|
20
|
+
function runLegacy(args = []) {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const child = spawn(process.execPath, [setupLegacyPath, ...args], {
|
|
23
|
+
stdio: "inherit",
|
|
24
|
+
env: process.env,
|
|
25
|
+
});
|
|
26
|
+
child.on("error", reject);
|
|
27
|
+
child.on("close", (code) => resolve(code || 0));
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function installServicesFromEnv(envFile) {
|
|
32
|
+
const paths = resolveSetupPaths();
|
|
33
|
+
const serviceUser = String(cli.value("service-user") || process.env.SUDO_USER || process.env.USER || "").trim();
|
|
34
|
+
const homeDir = (await resolveUserHome(serviceUser || process.env.USER, process.platform)) || paths.home;
|
|
35
|
+
const options = {
|
|
36
|
+
envFile,
|
|
37
|
+
homeDir,
|
|
38
|
+
logsDir: paths.logsDir,
|
|
39
|
+
serviceUser: serviceUser || process.env.USER,
|
|
40
|
+
nodePath: process.execPath,
|
|
41
|
+
};
|
|
42
|
+
if (process.platform === "linux") return installSystemdServices(options);
|
|
43
|
+
if (process.platform === "darwin") return installLaunchdServices(options);
|
|
44
|
+
throw new Error("Service install is only supported on Linux and macOS.");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function operateServices(operation) {
|
|
48
|
+
if (process.platform === "linux") return operateSystemdServices(operation);
|
|
49
|
+
if (process.platform === "darwin") return operateLaunchdServices(operation);
|
|
50
|
+
throw new Error("Service operations are only supported on Linux and macOS.");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function main() {
|
|
54
|
+
const first = String(argv[0] || "").trim();
|
|
55
|
+
if (!first) {
|
|
56
|
+
process.exit(await runLegacy([]));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (first.startsWith("--")) {
|
|
60
|
+
console.warn("[Tandem Setup] Legacy flag mode is deprecated. Use `tandem-setup init|service|doctor`.");
|
|
61
|
+
process.exit(await runLegacy(argv));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (first === "run") {
|
|
65
|
+
process.exit(await runLegacy(argv.slice(1)));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (first === "init") {
|
|
69
|
+
const result = await initializeInstall({
|
|
70
|
+
envPath: cli.value("env-file"),
|
|
71
|
+
overwrite: cli.has("rotate-token") || cli.has("reset-token"),
|
|
72
|
+
allowAmbientStateEnv: false,
|
|
73
|
+
allowCwdEnvMerge: false,
|
|
74
|
+
});
|
|
75
|
+
if (process.platform === "linux" || process.platform === "darwin") {
|
|
76
|
+
if (!cli.has("no-service") && !cli.has("foreground")) {
|
|
77
|
+
await installServicesFromEnv(result.envPath);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
printInitSummary(result);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (first === "doctor") {
|
|
85
|
+
const result = await runDoctor({
|
|
86
|
+
envFile: cli.value("env-file"),
|
|
87
|
+
allowAmbientStateEnv: false,
|
|
88
|
+
allowCwdEnvMerge: false,
|
|
89
|
+
});
|
|
90
|
+
printDoctor(result, cli.has("json"));
|
|
91
|
+
process.exit(result.ok ? 0 : 1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (first === "service") {
|
|
95
|
+
const op = String(argv[1] || "").trim().toLowerCase();
|
|
96
|
+
if (!op || op === "install") {
|
|
97
|
+
const result = await initializeInstall({
|
|
98
|
+
envPath: cli.value("env-file"),
|
|
99
|
+
overwrite: false,
|
|
100
|
+
allowAmbientStateEnv: false,
|
|
101
|
+
allowCwdEnvMerge: false,
|
|
102
|
+
});
|
|
103
|
+
await installServicesFromEnv(result.envPath);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
await operateServices(op);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (first === "pair" && String(argv[1] || "").trim().toLowerCase() === "mobile") {
|
|
111
|
+
const doctor = await runDoctor({
|
|
112
|
+
envFile: cli.value("env-file"),
|
|
113
|
+
allowAmbientStateEnv: false,
|
|
114
|
+
allowCwdEnvMerge: false,
|
|
115
|
+
});
|
|
116
|
+
console.log("Mobile pairing is not implemented in this build.");
|
|
117
|
+
console.log(`Control panel: http://${doctor.panelHost}:${doctor.panelPort}`);
|
|
118
|
+
console.log(`Public URL: ${doctor.panelPublicUrl || "(not configured)"}`);
|
|
119
|
+
console.log(`Engine URL: ${doctor.engineUrl}`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (entry === "tandem-control-panel") {
|
|
124
|
+
process.exit(await runLegacy(argv));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
err(`Unknown command: ${first}`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
main().catch((error) => {
|
|
132
|
+
err(error instanceof Error ? error.message : String(error));
|
|
133
|
+
process.exit(1);
|
|
134
|
+
});
|
package/bin/init-env.js
CHANGED
|
@@ -1,89 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { randomBytes } from "crypto";
|
|
3
|
+
import { initializeInstall, printInitSummary } from "../lib/setup/bootstrap.js";
|
|
4
|
+
import { parseCliArgs } from "../lib/setup/common.js";
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
const out = {};
|
|
9
|
-
for (const rawLine of String(content || "").split(/\r?\n/)) {
|
|
10
|
-
const line = rawLine.trim();
|
|
11
|
-
if (!line || line.startsWith("#")) continue;
|
|
12
|
-
const idx = line.indexOf("=");
|
|
13
|
-
if (idx <= 0) continue;
|
|
14
|
-
const key = line.slice(0, idx).trim();
|
|
15
|
-
let value = line.slice(idx + 1).trim();
|
|
16
|
-
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
17
|
-
value = value.slice(1, -1);
|
|
18
|
-
}
|
|
19
|
-
out[key] = value;
|
|
20
|
-
}
|
|
21
|
-
return out;
|
|
22
|
-
}
|
|
6
|
+
const cli = parseCliArgs(process.argv.slice(2));
|
|
23
7
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
8
|
+
const result = await initializeInstall({
|
|
9
|
+
envPath: cli.value("env-file"),
|
|
10
|
+
overwrite: cli.has("reset-token") || cli.has("rotate-token") || cli.has("overwrite"),
|
|
11
|
+
allowAmbientStateEnv: false,
|
|
12
|
+
allowCwdEnvMerge: false,
|
|
13
|
+
});
|
|
27
14
|
|
|
28
|
-
|
|
29
|
-
const envPath = resolve(cwd, ".env");
|
|
30
|
-
const existed = existsSync(envPath);
|
|
31
|
-
const examplePath = resolve(cwd, ".env.example");
|
|
32
|
-
const localExamplePath = resolve(join(process.cwd(), "packages", "tandem-control-panel", ".env.example"));
|
|
33
|
-
|
|
34
|
-
const sourcePath = existsSync(examplePath) ? examplePath : localExamplePath;
|
|
35
|
-
const defaults = existsSync(sourcePath)
|
|
36
|
-
? parseEnv(readFileSync(sourcePath, "utf8"))
|
|
37
|
-
: {
|
|
38
|
-
TANDEM_CONTROL_PANEL_PORT: "39732",
|
|
39
|
-
TANDEM_ENGINE_URL: "http://127.0.0.1:39731",
|
|
40
|
-
TANDEM_CONTROL_PANEL_AUTO_START_ENGINE: "1",
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const current = existsSync(envPath) ? parseEnv(readFileSync(envPath, "utf8")) : {};
|
|
44
|
-
const merged = { ...defaults, ...current };
|
|
45
|
-
|
|
46
|
-
if (overwrite || !merged.TANDEM_CONTROL_PANEL_ENGINE_TOKEN || merged.TANDEM_CONTROL_PANEL_ENGINE_TOKEN === "tk_change_me") {
|
|
47
|
-
merged.TANDEM_CONTROL_PANEL_ENGINE_TOKEN = `tk_${randomBytes(16).toString("hex")}`;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const preferredOrder = [
|
|
51
|
-
"TANDEM_CONTROL_PANEL_PORT",
|
|
52
|
-
"TANDEM_ENGINE_URL",
|
|
53
|
-
"TANDEM_ENGINE_HOST",
|
|
54
|
-
"TANDEM_ENGINE_PORT",
|
|
55
|
-
"TANDEM_CONTROL_PANEL_AUTO_START_ENGINE",
|
|
56
|
-
"TANDEM_CONTROL_PANEL_ENGINE_TOKEN",
|
|
57
|
-
"TANDEM_CONTROL_PANEL_SESSION_TTL_MINUTES",
|
|
58
|
-
];
|
|
59
|
-
|
|
60
|
-
const ordered = [];
|
|
61
|
-
for (const key of preferredOrder) {
|
|
62
|
-
if (merged[key] !== undefined) ordered.push([key, merged[key]]);
|
|
63
|
-
}
|
|
64
|
-
for (const [key, value] of Object.entries(merged)) {
|
|
65
|
-
if (!preferredOrder.includes(key)) ordered.push([key, value]);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
writeFileSync(envPath, serializeEnv(ordered), "utf8");
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
envPath,
|
|
72
|
-
token: merged.TANDEM_CONTROL_PANEL_ENGINE_TOKEN,
|
|
73
|
-
created: !existed,
|
|
74
|
-
engineUrl: merged.TANDEM_ENGINE_URL || `http://${merged.TANDEM_ENGINE_HOST || "127.0.0.1"}:${merged.TANDEM_ENGINE_PORT || "39731"}`,
|
|
75
|
-
panelPort: merged.TANDEM_CONTROL_PANEL_PORT || "39732",
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
80
|
-
const overwrite = process.argv.includes("--reset-token") || process.argv.includes("--overwrite");
|
|
81
|
-
const result = ensureEnv({ overwrite });
|
|
82
|
-
console.log("[Tandem Control Panel] Environment initialized.");
|
|
83
|
-
console.log(`[Tandem Control Panel] .env: ${result.envPath}`);
|
|
84
|
-
console.log(`[Tandem Control Panel] Engine URL: ${result.engineUrl}`);
|
|
85
|
-
console.log(`[Tandem Control Panel] Panel URL: http://localhost:${result.panelPort}`);
|
|
86
|
-
console.log(`[Tandem Control Panel] Token: ${result.token}`);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export { ensureEnv };
|
|
15
|
+
printInitSummary(result);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
5
|
+
SETUP_JS="$SCRIPT_DIR/setup.js"
|
|
6
|
+
NODE_BIN="${NODE_BIN:-$(command -v node || true)}"
|
|
7
|
+
|
|
8
|
+
if [[ -z "${NODE_BIN}" ]]; then
|
|
9
|
+
echo "node not found in PATH" >&2
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
cmd="${1:-help}"
|
|
14
|
+
arg1="${2:-}"
|
|
15
|
+
arg2="${3:-}"
|
|
16
|
+
|
|
17
|
+
run_setup() {
|
|
18
|
+
sudo "$NODE_BIN" "$SETUP_JS" "$@"
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
case "$cmd" in
|
|
22
|
+
install-panel)
|
|
23
|
+
panel_port="${arg1:-3402}"
|
|
24
|
+
sudo TANDEM_CONTROL_PANEL_PORT="$panel_port" "$NODE_BIN" "$SETUP_JS" --install-services --service-mode=panel
|
|
25
|
+
;;
|
|
26
|
+
install-both)
|
|
27
|
+
engine_port="${arg1:-39731}"
|
|
28
|
+
panel_port="${arg2:-3402}"
|
|
29
|
+
sudo TANDEM_ENGINE_PORT="$engine_port" TANDEM_CONTROL_PANEL_PORT="$panel_port" \
|
|
30
|
+
"$NODE_BIN" "$SETUP_JS" --install-services --service-mode=both
|
|
31
|
+
;;
|
|
32
|
+
restart-panel)
|
|
33
|
+
run_setup --service-op=restart --service-mode=panel
|
|
34
|
+
;;
|
|
35
|
+
restart-both)
|
|
36
|
+
run_setup --service-op=restart --service-mode=both
|
|
37
|
+
;;
|
|
38
|
+
status-panel)
|
|
39
|
+
run_setup --service-op=status --service-mode=panel
|
|
40
|
+
;;
|
|
41
|
+
status-both)
|
|
42
|
+
run_setup --service-op=status --service-mode=both
|
|
43
|
+
;;
|
|
44
|
+
logs-panel)
|
|
45
|
+
run_setup --service-op=logs --service-mode=panel
|
|
46
|
+
;;
|
|
47
|
+
logs-both)
|
|
48
|
+
run_setup --service-op=logs --service-mode=both
|
|
49
|
+
;;
|
|
50
|
+
*)
|
|
51
|
+
cat <<'EOF'
|
|
52
|
+
Usage:
|
|
53
|
+
bash packages/tandem-control-panel/bin/service-local.sh install-panel [panel_port]
|
|
54
|
+
bash packages/tandem-control-panel/bin/service-local.sh install-both [engine_port] [panel_port]
|
|
55
|
+
bash packages/tandem-control-panel/bin/service-local.sh restart-panel
|
|
56
|
+
bash packages/tandem-control-panel/bin/service-local.sh restart-both
|
|
57
|
+
bash packages/tandem-control-panel/bin/service-local.sh status-panel
|
|
58
|
+
bash packages/tandem-control-panel/bin/service-local.sh status-both
|
|
59
|
+
bash packages/tandem-control-panel/bin/service-local.sh logs-panel
|
|
60
|
+
bash packages/tandem-control-panel/bin/service-local.sh logs-both
|
|
61
|
+
EOF
|
|
62
|
+
;;
|
|
63
|
+
esac
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
import { loadDotEnvFile, resolveEnvLoadOrder } from "../lib/setup/env.js";
|
|
8
|
+
import { parseCliArgs } from "../lib/setup/common.js";
|
|
9
|
+
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const argv = process.argv.slice(2);
|
|
12
|
+
const mode = String(argv[0] || "").trim().toLowerCase();
|
|
13
|
+
const cli = parseCliArgs(argv.slice(1));
|
|
14
|
+
const explicitEnvFile = String(cli.value("env-file") || "").trim();
|
|
15
|
+
|
|
16
|
+
for (const envPath of resolveEnvLoadOrder({ explicitEnvFile })) {
|
|
17
|
+
loadDotEnvFile(envPath);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function runPanel() {
|
|
21
|
+
const runtimePath = fileURLToPath(new URL("./setup.js", import.meta.url));
|
|
22
|
+
const child = spawn(process.execPath, [runtimePath, "--env-file", explicitEnvFile].filter(Boolean), {
|
|
23
|
+
stdio: "inherit",
|
|
24
|
+
env: process.env,
|
|
25
|
+
});
|
|
26
|
+
child.on("close", (code) => process.exit(code || 0));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function runEngine() {
|
|
30
|
+
const engineEntrypoint = require.resolve("@frumu/tandem/bin/tandem-engine.js");
|
|
31
|
+
const host = String(process.env.TANDEM_ENGINE_HOST || "127.0.0.1").trim();
|
|
32
|
+
const port = String(process.env.TANDEM_ENGINE_PORT || "39731").trim();
|
|
33
|
+
const child = spawn(
|
|
34
|
+
process.execPath,
|
|
35
|
+
[engineEntrypoint, "serve", "--hostname", host, "--port", port],
|
|
36
|
+
{ stdio: "inherit", env: process.env }
|
|
37
|
+
);
|
|
38
|
+
child.on("close", (code) => process.exit(code || 0));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (mode === "engine") {
|
|
42
|
+
runEngine();
|
|
43
|
+
} else if (mode === "panel") {
|
|
44
|
+
runPanel();
|
|
45
|
+
} else {
|
|
46
|
+
console.error("Usage: service-runner.js <engine|panel> [--env-file PATH]");
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|