@frumu/tandem-tui 0.3.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/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # Tandem TUI (npm Wrapper)
2
+
3
+ ```text
4
+ TTTTT A N N DDDD EEEEE M M
5
+ T A A NN N D D E MM MM
6
+ T AAAAA N N N D D EEEE M M M
7
+ T A A N NN D D E M M
8
+ T A A N N DDDD EEEEE M M
9
+ ```
10
+
11
+ ## What This Is
12
+
13
+ Prebuilt npm distribution of Tandem TUI for macOS, Linux, and Windows.
14
+ Installing this package gives you the `tandem-tui` terminal client binary without compiling Rust locally.
15
+
16
+ If you want to build from Rust source instead, use the crate docs in `crates/tandem-tui/README.md`.
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ npm install -g @frumu/tandem-tui
22
+ ```
23
+
24
+ The installer downloads the release asset that matches this package version. Tags and package versions are expected to match (for example, `v0.3.3`).
25
+
26
+ ## Quick Start
27
+
28
+ Start the engine in one terminal:
29
+
30
+ ```bash
31
+ tandem-engine serve --hostname 127.0.0.1 --port 39731
32
+ ```
33
+
34
+ Start the TUI in another terminal:
35
+
36
+ ```bash
37
+ tandem-tui
38
+ ```
39
+
40
+ On first run, the setup wizard walks you through selecting a provider, entering an API key, and choosing a default model. Keys are stored in the system keystore.
41
+
42
+ ## Key Features
43
+
44
+ - Multi-session chat UI
45
+ - Request Center for approvals and questions
46
+ - Slash commands for fast navigation and configuration
47
+ - Agent and mission workflows
48
+
49
+ ## Core Keybindings
50
+
51
+ - `Ctrl+N`: New session
52
+ - `Ctrl+W`: Close active session
53
+ - `Ctrl+C`: Cancel active agent (press twice to quit)
54
+ - `Alt+R`: Request Center
55
+ - `F1`: Help
56
+ - `F2`: Open docs
57
+
58
+ ## Common Slash Commands
59
+
60
+ - `/help`
61
+ - `/providers`
62
+ - `/provider <id>`
63
+ - `/models`
64
+ - `/model <id>`
65
+ - `/sessions`
66
+ - `/new`
67
+ - `/title <name>`
68
+ - `/requests`
69
+ - `/approve <id>`
70
+ - `/deny <id>`
71
+
72
+ ## Troubleshooting
73
+
74
+ - If the TUI shows a connection error, ensure the engine is running.
75
+ - If port 39731 is in use, start the engine with `--port` and set `TANDEM_ENGINE_PORT` for the TUI.
76
+
77
+ ## Documentation
78
+
79
+ - TUI guide and reference: https://tandem.frumu.ai/docs
80
+ - GitHub releases: https://github.com/frumu-ai/tandem/releases
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@frumu/tandem-tui",
3
+ "version": "0.3.3",
4
+ "description": "Tandem TUI binary distribution",
5
+ "homepage": "https://tandem.frumu.ai",
6
+ "bin": {
7
+ "tandem-tui": "bin/tandem-tui"
8
+ },
9
+ "scripts": {
10
+ "postinstall": "node scripts/install.js"
11
+ },
12
+ "files": [
13
+ "bin",
14
+ "scripts"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/frumu-ai/tandem.git"
19
+ },
20
+ "author": "Frumu Ltd",
21
+ "license": "MIT OR Apache-2.0",
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "os": [
26
+ "darwin",
27
+ "linux",
28
+ "win32"
29
+ ],
30
+ "cpu": [
31
+ "x64",
32
+ "arm64"
33
+ ]
34
+ }
@@ -0,0 +1,151 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const https = require('https');
4
+ const { execSync } = require('child_process');
5
+
6
+ const REPO = "frumu-ai/tandem";
7
+ const MIN_SIZE = 1024 * 1024;
8
+
9
+ const PLATFORM_MAP = {
10
+ 'win32': { os: 'windows', ext: '.exe' },
11
+ 'darwin': { os: 'darwin', ext: '' },
12
+ 'linux': { os: 'linux', ext: '' }
13
+ };
14
+
15
+ const ARCH_MAP = {
16
+ 'x64': 'x64',
17
+ 'arm64': 'arm64'
18
+ };
19
+
20
+ function getArtifactInfo() {
21
+ const platform = PLATFORM_MAP[process.platform];
22
+ const arch = ARCH_MAP[process.arch];
23
+
24
+ if (!platform || !arch) {
25
+ throw new Error(`Unsupported platform: ${process.platform}-${process.arch}`);
26
+ }
27
+
28
+ let artifactName = `tandem-tui-${platform.os}-${arch}`;
29
+ if (platform.os === 'windows') {
30
+ artifactName += '.zip';
31
+ } else if (platform.os === 'darwin') {
32
+ artifactName += '.zip';
33
+ } else {
34
+ artifactName += '.tar.gz';
35
+ }
36
+
37
+ return {
38
+ artifactName,
39
+ binaryName: `tandem-tui${platform.ext}`,
40
+ isWindows: platform.os === 'windows'
41
+ };
42
+ }
43
+
44
+ const { artifactName, binaryName, isWindows } = getArtifactInfo();
45
+ const binDir = path.join(__dirname, '..', 'bin');
46
+ const destPath = path.join(binDir, binaryName);
47
+
48
+ if (!fs.existsSync(binDir)) {
49
+ fs.mkdirSync(binDir, { recursive: true });
50
+ }
51
+
52
+ if (fs.existsSync(destPath)) {
53
+ console.log("Binary already present.");
54
+ process.exit(0);
55
+ }
56
+
57
+ function fetchJson(url) {
58
+ return new Promise((resolve, reject) => {
59
+ https.get(url, { headers: { 'User-Agent': 'tandem-tui-installer' } }, (res) => {
60
+ if (res.statusCode !== 200) {
61
+ if (res.statusCode === 302 || res.statusCode === 301) {
62
+ return fetchJson(res.headers.location).then(resolve).catch(reject);
63
+ }
64
+ return reject(new Error(`GitHub API HTTP ${res.statusCode}`));
65
+ }
66
+ let data = '';
67
+ res.on('data', chunk => data += chunk);
68
+ res.on('end', () => {
69
+ try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
70
+ });
71
+ }).on('error', reject);
72
+ });
73
+ }
74
+
75
+ async function download() {
76
+ console.log(`Checking releases for ${REPO}...`);
77
+ const releases = await fetchJson(`https://api.github.com/repos/${REPO}/releases`);
78
+
79
+ const packageVersion = require('../package.json').version;
80
+ const targetTag = `v${packageVersion}`;
81
+
82
+ console.log(`Filtering releases for ${REPO} (Target: ${targetTag})...`);
83
+ let release = releases.find(r => r.tag_name === targetTag);
84
+
85
+ if (!release) {
86
+ console.warn(`Warning: No release found for tag ${targetTag}. Checking for latest compatible assets...`);
87
+ release = releases.find(r => r.assets.some(a => a.name === artifactName));
88
+ }
89
+
90
+ if (!release) {
91
+ console.error(`Status: No release found with asset ${artifactName}`);
92
+ console.error("Available assets in latest:", releases[0]?.assets?.map(a => a.name));
93
+ process.exit(1);
94
+ }
95
+
96
+ const asset = release.assets.find(a => a.name === artifactName);
97
+ console.log(`Downloading ${asset.name} from ${release.tag_name}...`);
98
+
99
+ const file = fs.createWriteStream(path.join(binDir, artifactName));
100
+
101
+ return new Promise((resolve, reject) => {
102
+ const downloadUrl = asset.browser_download_url;
103
+
104
+ const request = (url) => {
105
+ https.get(url, { headers: { 'User-Agent': 'tandem-tui-installer' } }, (res) => {
106
+ if (res.statusCode === 302 || res.statusCode === 301) {
107
+ return request(res.headers.location);
108
+ }
109
+ if (res.statusCode !== 200) return reject(new Error(`Download failed: HTTP ${res.statusCode}`));
110
+ res.pipe(file);
111
+ file.on('finish', () => {
112
+ file.close();
113
+ resolve(path.join(binDir, artifactName));
114
+ });
115
+ }).on('error', err => {
116
+ fs.unlink(path.join(binDir, artifactName), () => { });
117
+ reject(err);
118
+ });
119
+ };
120
+ request(downloadUrl);
121
+ });
122
+ }
123
+
124
+ async function extract(archivePath) {
125
+ console.log("Extracting...");
126
+ if (isWindows) {
127
+ execSync(`powershell -Command "Expand-Archive -Path '${archivePath}' -DestinationPath '${binDir}' -Force"`);
128
+ } else {
129
+ if (artifactName.endsWith('.zip')) {
130
+ execSync(`unzip -o "${archivePath}" -d "${binDir}"`);
131
+ } else {
132
+ execSync(`tar -xzf "${archivePath}" -C "${binDir}"`);
133
+ }
134
+ }
135
+
136
+ fs.unlinkSync(archivePath);
137
+
138
+ if (fs.existsSync(destPath)) {
139
+ console.log("Verified binary extracted.");
140
+ if (!isWindows) fs.chmodSync(destPath, 0o755);
141
+ } else {
142
+ console.error("Binary not found at expected path:", destPath);
143
+ console.log("Files in bin:", fs.readdirSync(binDir));
144
+ process.exit(1);
145
+ }
146
+ }
147
+
148
+ download().then(extract).catch(err => {
149
+ console.error("Install failed:", err);
150
+ process.exit(1);
151
+ });