@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 +80 -0
- package/package.json +34 -0
- package/scripts/install.js +151 -0
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
|
+
});
|