@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 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
+ ```
@@ -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 };