@frumu/tandem-panel 0.3.27
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 +42 -0
- package/README.md +131 -0
- package/bin/init-env.js +89 -0
- package/bin/setup.js +1369 -0
- package/dist/assets/index-BGpNZLeu.js +916 -0
- package/dist/assets/index-TZfxUYN2.css +1 -0
- package/dist/favicon.svg +11 -0
- package/dist/index.html +21 -0
- package/package.json +58 -0
package/.env.example
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Control panel bind port
|
|
2
|
+
TANDEM_CONTROL_PANEL_PORT=39732
|
|
3
|
+
|
|
4
|
+
# Tip: run `tandem-control-panel --init` to auto-generate this file and token.
|
|
5
|
+
|
|
6
|
+
# Full engine URL (preferred)
|
|
7
|
+
TANDEM_ENGINE_URL=http://127.0.0.1:39731
|
|
8
|
+
|
|
9
|
+
# Optional engine host/port fallback (used when TANDEM_ENGINE_URL is not set)
|
|
10
|
+
TANDEM_ENGINE_HOST=127.0.0.1
|
|
11
|
+
TANDEM_ENGINE_PORT=39731
|
|
12
|
+
|
|
13
|
+
# Auto-start local engine if not running (1=yes, 0=no)
|
|
14
|
+
TANDEM_CONTROL_PANEL_AUTO_START_ENGINE=1
|
|
15
|
+
|
|
16
|
+
# Engine API token used when control panel auto-starts a local engine.
|
|
17
|
+
# If unset, the panel generates one at startup and prints it in logs.
|
|
18
|
+
TANDEM_CONTROL_PANEL_ENGINE_TOKEN=tk_change_me
|
|
19
|
+
|
|
20
|
+
# Disable per-run guard budgets by default (1=yes, 0=no)
|
|
21
|
+
TANDEM_DISABLE_TOOL_GUARD_BUDGETS=1
|
|
22
|
+
TANDEM_TOOL_ROUTER_ENABLED=0
|
|
23
|
+
TANDEM_PROMPT_CONTEXT_HOOK_TIMEOUT_MS=5000
|
|
24
|
+
TANDEM_PROVIDER_STREAM_CONNECT_TIMEOUT_MS=30000
|
|
25
|
+
TANDEM_PROVIDER_STREAM_IDLE_TIMEOUT_MS=90000
|
|
26
|
+
TANDEM_PERMISSION_WAIT_TIMEOUT_MS=15000
|
|
27
|
+
TANDEM_TOOL_EXEC_TIMEOUT_MS=45000
|
|
28
|
+
TANDEM_BASH_TIMEOUT_MS=30000
|
|
29
|
+
|
|
30
|
+
# Optional caps when guard budgets are enabled
|
|
31
|
+
# TANDEM_TOOL_BUDGET_DEFAULT=10
|
|
32
|
+
# TANDEM_TOOL_BUDGET_BATCH=10
|
|
33
|
+
# TANDEM_TOOL_BUDGET_WEBSEARCH=8
|
|
34
|
+
# TANDEM_TOOL_BUDGET_READ=8
|
|
35
|
+
# TANDEM_TOOL_BUDGET_SEARCH=6
|
|
36
|
+
# TANDEM_TOOL_BUDGET_GLOB=4
|
|
37
|
+
|
|
38
|
+
# Back-compat alias also respected for auto-started engines.
|
|
39
|
+
# TANDEM_API_TOKEN=tk_change_me
|
|
40
|
+
|
|
41
|
+
# Session TTL in minutes
|
|
42
|
+
TANDEM_CONTROL_PANEL_SESSION_TTL_MINUTES=1440
|
package/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Tandem Control Panel
|
|
2
|
+
|
|
3
|
+
Full web control center for Tandem Engine (non-desktop entry point).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -g @frumu/tandem-panel
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Run
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
tandem-control-panel
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Alias also supported:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
tandem-setup
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Bootstrap env/token first (recommended):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
tandem-control-panel --init
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
tandem-control-panel-init
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Install Linux systemd services (engine + panel):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
sudo tandem-control-panel --install-services
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Options:
|
|
42
|
+
|
|
43
|
+
- `--service-mode=both|engine|panel` (default `both`)
|
|
44
|
+
- `--service-user=<linux-user>` (default: `SUDO_USER`/current user)
|
|
45
|
+
|
|
46
|
+
## Features
|
|
47
|
+
|
|
48
|
+
- Token-gated web portal
|
|
49
|
+
- Dashboard + health overview
|
|
50
|
+
- Chat + session management
|
|
51
|
+
- Routines/automations
|
|
52
|
+
- Channels (Telegram/Discord/Slack)
|
|
53
|
+
- MCP server management
|
|
54
|
+
- Node-based swarm orchestration + live flow visualization
|
|
55
|
+
- Memory browsing/search/delete
|
|
56
|
+
- Agent teams + mission approvals
|
|
57
|
+
- Global live event feed
|
|
58
|
+
- Provider settings
|
|
59
|
+
|
|
60
|
+
## Environment
|
|
61
|
+
|
|
62
|
+
Copy and customize if needed:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
cp .env.example .env
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Variables:
|
|
69
|
+
|
|
70
|
+
- `TANDEM_CONTROL_PANEL_PORT` (default `39732`)
|
|
71
|
+
- `TANDEM_ENGINE_URL` (default `http://127.0.0.1:39731`)
|
|
72
|
+
- `TANDEM_ENGINE_HOST` + `TANDEM_ENGINE_PORT` fallback
|
|
73
|
+
- `TANDEM_CONTROL_PANEL_AUTO_START_ENGINE` (`1`/`0`)
|
|
74
|
+
- `TANDEM_CONTROL_PANEL_ENGINE_TOKEN` (token injected when panel auto-starts engine)
|
|
75
|
+
- `TANDEM_API_TOKEN` (backward-compatible alias for engine token)
|
|
76
|
+
- `TANDEM_CONTROL_PANEL_SESSION_TTL_MINUTES` (default `1440`)
|
|
77
|
+
- `TANDEM_DISABLE_TOOL_GUARD_BUDGETS` (`1` disables per-run guard budgets; default in installer/service env is `1`)
|
|
78
|
+
- `TANDEM_PROMPT_CONTEXT_HOOK_TIMEOUT_MS` (default `5000`)
|
|
79
|
+
- `TANDEM_PROVIDER_STREAM_CONNECT_TIMEOUT_MS` (default `30000`)
|
|
80
|
+
- `TANDEM_PROVIDER_STREAM_IDLE_TIMEOUT_MS` (default `90000`)
|
|
81
|
+
- `TANDEM_BASH_TIMEOUT_MS` (default `30000`)
|
|
82
|
+
- `TANDEM_TOOL_BUDGET_DEFAULT`, `TANDEM_TOOL_BUDGET_BATCH`, `TANDEM_TOOL_BUDGET_WEBSEARCH`,
|
|
83
|
+
`TANDEM_TOOL_BUDGET_READ`, `TANDEM_TOOL_BUDGET_SEARCH`, `TANDEM_TOOL_BUDGET_GLOB` (used when guards are enabled)
|
|
84
|
+
|
|
85
|
+
## Token Behavior
|
|
86
|
+
|
|
87
|
+
- If the panel connects to an already-running engine, use that engine's API token to sign in.
|
|
88
|
+
- If the panel auto-starts an engine, it now always starts with a known token:
|
|
89
|
+
- `TANDEM_CONTROL_PANEL_ENGINE_TOKEN` (preferred), or
|
|
90
|
+
- `TANDEM_API_TOKEN` (alias), or
|
|
91
|
+
- auto-generated at startup (printed in panel logs).
|
|
92
|
+
|
|
93
|
+
## Tool Guard Budgets
|
|
94
|
+
|
|
95
|
+
Default installs now set:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
TANDEM_DISABLE_TOOL_GUARD_BUDGETS=1
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
To enforce caps instead, set:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
TANDEM_DISABLE_TOOL_GUARD_BUDGETS=0
|
|
105
|
+
TANDEM_TOOL_BUDGET_DEFAULT=10
|
|
106
|
+
TANDEM_TOOL_BUDGET_BATCH=10
|
|
107
|
+
TANDEM_TOOL_BUDGET_WEBSEARCH=8
|
|
108
|
+
TANDEM_TOOL_BUDGET_READ=8
|
|
109
|
+
TANDEM_TOOL_BUDGET_SEARCH=6
|
|
110
|
+
TANDEM_TOOL_BUDGET_GLOB=4
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Notes:
|
|
114
|
+
|
|
115
|
+
- Unknown tools use `TANDEM_TOOL_BUDGET_DEFAULT`.
|
|
116
|
+
- `0|none|unlimited|infinite|inf` for a budget key means no cap for that key.
|
|
117
|
+
|
|
118
|
+
## Setup Flow
|
|
119
|
+
|
|
120
|
+
1. Run `tandem-control-panel --init` to create/update `.env` and generate a token if missing.
|
|
121
|
+
2. Run `tandem-control-panel`.
|
|
122
|
+
3. Sign in with the printed `TANDEM_CONTROL_PANEL_ENGINE_TOKEN`.
|
|
123
|
+
|
|
124
|
+
## Development
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
cd packages/tandem-control-panel
|
|
128
|
+
npm install
|
|
129
|
+
npm run dev
|
|
130
|
+
npm run build
|
|
131
|
+
```
|
package/bin/init-env.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
4
|
+
import { resolve, join } from "path";
|
|
5
|
+
import { randomBytes } from "crypto";
|
|
6
|
+
|
|
7
|
+
function parseEnv(content) {
|
|
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
|
+
}
|
|
23
|
+
|
|
24
|
+
function serializeEnv(entries) {
|
|
25
|
+
return `${entries.map(([k, v]) => `${k}=${v}`).join("\n")}\n`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function ensureEnv({ cwd = process.cwd(), overwrite = false } = {}) {
|
|
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 };
|