@tessl/cli 0.62.1 → 0.63.0

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.
Files changed (3) hide show
  1. package/bin/tessl.js +300 -0
  2. package/package.json +6 -26
  3. package/src/main.js +0 -1292
package/bin/tessl.js ADDED
@@ -0,0 +1,300 @@
1
+ #!/usr/bin/env node
2
+
3
+ // installer/src/main.ts
4
+ import { existsSync as existsSync3 } from "node:fs";
5
+ import { createInterface } from "node:readline";
6
+
7
+ // installer/src/channels.ts
8
+ async function fetchLatestVersion(channel) {
9
+ const url = getChannelUrl(channel);
10
+ const response = await fetch(url);
11
+ if (!response.ok) {
12
+ throw new Error(`Failed to fetch version from ${url}: ${response.status} ${response.statusText}`);
13
+ }
14
+ const version = (await response.text()).trim();
15
+ if (!version) {
16
+ throw new Error(`Empty version returned from ${url}`);
17
+ }
18
+ return version;
19
+ }
20
+ function getChannelFromVersion(version) {
21
+ if (version.includes("head"))
22
+ return "head";
23
+ if (version.includes("nightly"))
24
+ return "nightly";
25
+ if (version.includes("beta"))
26
+ return "beta";
27
+ return "latest";
28
+ }
29
+ function getChannelUrl(channel) {
30
+ const baseUrl = process.env.TESSL_INSTALL_BASE_URL || "https://install.tessl.io";
31
+ return `${baseUrl}/.well-known/tessl/${channel}.txt`;
32
+ }
33
+ function getBinaryUrl(version, platform) {
34
+ const baseUrl = process.env.TESSL_INSTALL_BASE_URL || "https://install.tessl.io";
35
+ const encodedVersion = encodeURIComponent(version);
36
+ return `${baseUrl}/binaries/${encodedVersion}/tessl-${encodedVersion}-${platform}.tar.gz`;
37
+ }
38
+
39
+ // installer/src/download.ts
40
+ async function downloadTarball(url) {
41
+ const response = await fetch(url);
42
+ if (!response.ok) {
43
+ throw new Error(`Failed to download binary: ${response.status} ${response.statusText}`);
44
+ }
45
+ if (!response.body) {
46
+ throw new Error("Response body is null");
47
+ }
48
+ return response.body;
49
+ }
50
+
51
+ // installer/src/extract.ts
52
+ import { spawn } from "node:child_process";
53
+ import { mkdir } from "node:fs/promises";
54
+ import { pipeline } from "node:stream/promises";
55
+ async function extractTarball(stream, destination) {
56
+ await mkdir(destination, { recursive: true });
57
+ const tar = spawn("tar", ["-xzf", "-", "-C", destination], {
58
+ stdio: ["pipe", "inherit", "inherit"]
59
+ });
60
+ if (!tar.stdin) {
61
+ throw new Error("Failed to open tar stdin");
62
+ }
63
+ const tarExit = new Promise((resolve, reject) => {
64
+ tar.on("exit", (code) => {
65
+ if (code === 0) {
66
+ resolve();
67
+ } else {
68
+ reject(new Error(`tar exited with code ${code}`));
69
+ }
70
+ });
71
+ tar.on("error", reject);
72
+ });
73
+ await pipeline(stream, tar.stdin);
74
+ await tarExit;
75
+ }
76
+
77
+ // installer/src/paths.ts
78
+ import { homedir } from "node:os";
79
+ import { join } from "node:path";
80
+ function getXdgDataHome() {
81
+ return process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
82
+ }
83
+ function getSymlinkPath() {
84
+ return join(homedir(), ".local", "bin", "tessl");
85
+ }
86
+ function getVersionsDirectory() {
87
+ return join(getXdgDataHome(), "tessl", "versions");
88
+ }
89
+ function getBinaryPath(version, platform) {
90
+ return join(getVersionsDirectory(), `tessl-${version}-${platform}`);
91
+ }
92
+
93
+ // installer/src/platform.ts
94
+ import { execSync } from "node:child_process";
95
+ import { existsSync } from "node:fs";
96
+ function getLibcVariant() {
97
+ if (process.platform !== "linux")
98
+ return null;
99
+ try {
100
+ if (existsSync("/etc/alpine-release"))
101
+ return "musl";
102
+ } catch {}
103
+ try {
104
+ const lddOutput = execSync("ldd /bin/ls 2>/dev/null", {
105
+ encoding: "utf-8"
106
+ });
107
+ if (lddOutput.includes("musl"))
108
+ return "musl";
109
+ } catch {}
110
+ return "glibc";
111
+ }
112
+ function getPlatformString() {
113
+ const platform = process.platform;
114
+ const arch = process.arch;
115
+ if (platform === "darwin") {
116
+ if (arch === "arm64")
117
+ return "darwin-arm64";
118
+ if (arch === "x64")
119
+ return "darwin-x64";
120
+ }
121
+ if (platform === "linux") {
122
+ const variant = getLibcVariant();
123
+ if (arch === "x64") {
124
+ return variant === "musl" ? "linux-x64-musl" : "linux-x64";
125
+ }
126
+ if (arch === "arm64") {
127
+ return variant === "musl" ? "linux-arm64-musl" : "linux-arm64";
128
+ }
129
+ }
130
+ return null;
131
+ }
132
+ function getPlatformDescription() {
133
+ const platform = process.platform;
134
+ const arch = process.arch;
135
+ const platformNames = {
136
+ darwin: "macOS",
137
+ linux: "Linux",
138
+ win32: "Windows"
139
+ };
140
+ const archNames = {
141
+ arm64: "ARM64",
142
+ x64: "x64",
143
+ ia32: "x86"
144
+ };
145
+ const platformName = platformNames[platform] ?? platform;
146
+ const archName = archNames[arch] ?? arch;
147
+ return `${platformName} ${archName}`;
148
+ }
149
+
150
+ // installer/src/spawn.ts
151
+ import { spawn as spawn2 } from "node:child_process";
152
+ function spawnBinary(binaryPath, args) {
153
+ const child = spawn2(binaryPath, args, {
154
+ stdio: "inherit",
155
+ env: {
156
+ ...process.env,
157
+ TESSL_MANAGED_BY_NPM: "1"
158
+ }
159
+ });
160
+ const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
161
+ const handlers = new Map;
162
+ for (const signal of signals) {
163
+ const handler = () => {
164
+ child.kill(signal);
165
+ };
166
+ handlers.set(signal, handler);
167
+ process.on(signal, handler);
168
+ }
169
+ child.on("exit", (code, signal) => {
170
+ if (signal) {
171
+ for (const [sig, handler] of handlers) {
172
+ process.removeListener(sig, handler);
173
+ }
174
+ process.kill(process.pid, signal);
175
+ } else {
176
+ process.exit(code ?? 1);
177
+ }
178
+ });
179
+ child.on("error", (err) => {
180
+ console.error(`Failed to spawn binary: ${err.message}`);
181
+ process.exit(1);
182
+ });
183
+ }
184
+
185
+ // installer/src/symlink.ts
186
+ import { existsSync as existsSync2 } from "node:fs";
187
+ import { symlink, rename, unlink, mkdir as mkdir2, readlink } from "node:fs/promises";
188
+ import { dirname, resolve } from "node:path";
189
+ async function getExistingBinaryPath(symlinkPath) {
190
+ if (!existsSync2(symlinkPath)) {
191
+ return null;
192
+ }
193
+ try {
194
+ const binaryPath = await readlink(symlinkPath);
195
+ const resolvedPath = resolve(dirname(symlinkPath), binaryPath);
196
+ if (!existsSync2(resolvedPath)) {
197
+ return null;
198
+ }
199
+ return resolvedPath;
200
+ } catch {
201
+ return null;
202
+ }
203
+ }
204
+ async function createSymlinkAtomic(target, linkPath) {
205
+ const tempPath = `${linkPath}.tmp`;
206
+ await mkdir2(dirname(linkPath), { recursive: true });
207
+ try {
208
+ await unlink(tempPath);
209
+ } catch {}
210
+ await symlink(target, tempPath);
211
+ await rename(tempPath, linkPath);
212
+ }
213
+
214
+ // installer/src/main.ts
215
+ function isInteractive() {
216
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
217
+ }
218
+ async function promptConfirmation(message) {
219
+ return new Promise((resolve2) => {
220
+ const rl = createInterface({
221
+ input: process.stdin,
222
+ output: process.stdout
223
+ });
224
+ rl.question(`${message} (Y/n) `, (answer) => {
225
+ rl.close();
226
+ const a = answer.trim().toLowerCase();
227
+ resolve2(a === "" || a === "y");
228
+ });
229
+ });
230
+ }
231
+ function printUnsupportedPlatformMessage() {
232
+ console.error("Error: Unsupported platform", getPlatformDescription());
233
+ console.error(`Tessl CLI binaries are available for:
234
+ - macOS (ARM64, x64)
235
+ - Linux (x64, ARM64)
236
+ `);
237
+ }
238
+ async function prepareBinary() {
239
+ const symlinkPath = getSymlinkPath();
240
+ const symlinkTarget = await getExistingBinaryPath(symlinkPath);
241
+ const installerVersion = "0.63.0";
242
+ const isTestOrDevBuild = installerVersion.includes("test") || installerVersion.includes("dev");
243
+ let symlinkMismatch = false;
244
+ if (isTestOrDevBuild && symlinkTarget) {
245
+ const platform2 = getPlatformString();
246
+ if (!platform2) {
247
+ printUnsupportedPlatformMessage();
248
+ return process.exit(1);
249
+ }
250
+ if (symlinkTarget !== getBinaryPath(installerVersion, platform2)) {
251
+ console.log(`Test/dev build - switching to version: ${installerVersion}`);
252
+ symlinkMismatch = true;
253
+ }
254
+ }
255
+ if (symlinkTarget && !symlinkMismatch) {
256
+ return symlinkPath;
257
+ }
258
+ const platform = getPlatformString();
259
+ if (!platform) {
260
+ printUnsupportedPlatformMessage();
261
+ return process.exit(1);
262
+ }
263
+ const version = isTestOrDevBuild ? installerVersion : await fetchLatestVersion(getChannelFromVersion(installerVersion));
264
+ const binaryPath = getBinaryPath(version, platform);
265
+ if (existsSync3(binaryPath)) {
266
+ return binaryPath;
267
+ }
268
+ console.log(`Downloading ${version} for ${platform}`);
269
+ const url = getBinaryUrl(version, platform);
270
+ const progressInterval = setInterval(() => process.stdout.write("."), 500).unref();
271
+ try {
272
+ const tarballStream = await downloadTarball(url);
273
+ const versionsDir = getVersionsDirectory();
274
+ await extractTarball(tarballStream, versionsDir);
275
+ } finally {
276
+ clearInterval(progressInterval);
277
+ console.log("");
278
+ }
279
+ const shouldCreateSymlink = !isInteractive() || await promptConfirmation(`Install Tessl CLI to ${symlinkPath}?`);
280
+ if (shouldCreateSymlink) {
281
+ await createSymlinkAtomic(binaryPath, symlinkPath);
282
+ if (platform.includes("musl")) {
283
+ console.log(`
284
+ This system requires libgcc and libstdc++. Install these using your distribution's package manager.
285
+ On Alpine: apk add libgcc libstdc++
286
+ `);
287
+ }
288
+ return symlinkPath;
289
+ }
290
+ return binaryPath;
291
+ }
292
+ async function main() {
293
+ const path = await prepareBinary();
294
+ spawnBinary(path, process.argv.slice(2));
295
+ }
296
+ main().catch((err) => {
297
+ console.error("Unexpected error:");
298
+ console.error(err);
299
+ process.exit(1);
300
+ });
package/package.json CHANGED
@@ -1,38 +1,18 @@
1
1
  {
2
2
  "name": "@tessl/cli",
3
- "version": "0.62.1",
4
- "description": "Tessl CLI - Registry and workspace operations with MCP support",
3
+ "version": "0.63.0",
4
+ "description": "Tessl CLI",
5
5
  "author": "Tessl",
6
- "license": "UNLICENSED",
6
+ "license": "SEE LICENSE.md",
7
7
  "type": "module",
8
- "main": "src/main.js",
9
8
  "bin": {
10
- "tessl": "src/main.js"
9
+ "tessl": "bin/tessl.js"
11
10
  },
12
11
  "files": [
13
- "src/main.js"
12
+ "bin"
14
13
  ],
15
14
  "engines": {
16
15
  "node": ">=20"
17
16
  },
18
- "dependencies": {
19
- "better-sqlite3": "12.6.2",
20
- "pino": "10.3.0",
21
- "pino-pretty": "13.1.3",
22
- "pino-opentelemetry-transport": "2.0.0",
23
- "atomic-sleep": "1.0.0",
24
- "@opentelemetry/api-logs": "0.211.0",
25
- "@opentelemetry/sdk-logs": "0.211.0",
26
- "@opentelemetry/exporter-logs-otlp-http": "0.211.0",
27
- "@opentelemetry/exporter-logs-otlp-proto": "0.211.0",
28
- "@opentelemetry/exporter-trace-otlp-http": "0.211.0",
29
- "@opentelemetry/otlp-transformer": "0.211.0",
30
- "@opentelemetry/semantic-conventions": "1.39.0",
31
- "@opentelemetry/sdk-metrics": "2.5.0",
32
- "@opentelemetry/api": "1.9.0",
33
- "@opentelemetry/instrumentation-undici": "0.21.0",
34
- "@opentelemetry/resources": "2.5.0",
35
- "@opentelemetry/sdk-node": "0.211.0",
36
- "@opentelemetry/sdk-trace-node": "2.5.0"
37
- }
17
+ "dependencies": {}
38
18
  }