@trackseries/cli 0.1.0-preview.1

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,60 @@
1
+ # TrackSeries CLI
2
+
3
+ The TrackSeries CLI provides terminal and agent-friendly access to your TrackSeries account. This public repository contains the npm installer and downloadable releases; the application source is maintained separately.
4
+
5
+ ## Install with npm
6
+
7
+ Node.js 20 or newer is required. Install the latest stable release with:
8
+
9
+ ```bash
10
+ npm install --global @trackseries/cli
11
+ trackseries auth login
12
+ ```
13
+
14
+ Before the first stable release, install the current preview with:
15
+
16
+ ```bash
17
+ npm install --global @trackseries/cli@next
18
+ ```
19
+
20
+ The npm installer downloads and checksum-verifies the matching release artifact for the current operating system and architecture. The installed `trackseries` command is a standalone executable and does not use Node.js while running.
21
+
22
+ Supported targets:
23
+
24
+ - glibc-based Linux x64 and ARM64
25
+ - macOS x64 and ARM64
26
+ - Windows x64 and ARM64
27
+
28
+ ## Direct download
29
+
30
+ Archives and SHA-256 checksums are available from [GitHub Releases](https://github.com/TrackSeries/cli/releases).
31
+
32
+ After installation, authenticate and inspect the available commands:
33
+
34
+ ```bash
35
+ trackseries auth login
36
+ trackseries auth status
37
+ trackseries skill
38
+ ```
39
+
40
+ `trackseries skill` prints version-matched instructions designed for coding agents and other automated tools.
41
+
42
+ ## Updates
43
+
44
+ Install the latest published version with:
45
+
46
+ ```bash
47
+ npm install --global @trackseries/cli@latest
48
+ ```
49
+
50
+ Preview releases are published under the npm `next` tag:
51
+
52
+ ```bash
53
+ npm install --global @trackseries/cli@next
54
+ ```
55
+
56
+ ## Security
57
+
58
+ Authentication uses the OAuth device authorization flow. Credentials are stored in the platform-specific TrackSeries configuration directory and are never stored in this repository or the npm package.
59
+
60
+ Release archives are verified against their accompanying SHA-256 checksum before installation. The initial binaries are not code-signed, so Windows SmartScreen or macOS Gatekeeper may require manual approval.
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from "node:child_process";
4
+ import { existsSync } from "node:fs";
5
+ import { dirname, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
9
+ const executableName = process.platform === "win32" ? "trackseries.exe" : "trackseries";
10
+ const executablePath = resolve(packageRoot, "vendor", executableName);
11
+
12
+ if (!existsSync(executablePath)) {
13
+ console.error("The TrackSeries CLI executable is missing. Reinstall @trackseries/cli.");
14
+ process.exit(1);
15
+ }
16
+
17
+ const result = spawnSync(executablePath, process.argv.slice(2), {
18
+ stdio: "inherit"
19
+ });
20
+
21
+ if (result.error) {
22
+ console.error(`Unable to start the TrackSeries CLI: ${result.error.message}`);
23
+ process.exit(1);
24
+ }
25
+
26
+ if (result.signal) {
27
+ process.kill(process.pid, result.signal);
28
+ }
29
+
30
+ process.exit(result.status ?? 1);
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@trackseries/cli",
3
+ "version": "0.1.0-preview.1",
4
+ "description": "Install and run the TrackSeries CLI.",
5
+ "type": "module",
6
+ "bin": {
7
+ "trackseries": "./bin/trackseries.js"
8
+ },
9
+ "files": [
10
+ "bin/trackseries.js",
11
+ "scripts/install.js",
12
+ "scripts/platform.js"
13
+ ],
14
+ "scripts": {
15
+ "postinstall": "node scripts/install.js",
16
+ "test": "node --test"
17
+ },
18
+ "engines": {
19
+ "node": ">=20"
20
+ },
21
+ "dependencies": {
22
+ "fflate": "0.8.3",
23
+ "tar": "7.5.22"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/TrackSeries/cli.git"
31
+ },
32
+ "homepage": "https://github.com/TrackSeries/cli#readme",
33
+ "bugs": {
34
+ "url": "https://github.com/TrackSeries/cli/issues"
35
+ }
36
+ }
@@ -0,0 +1,110 @@
1
+ import { createHash } from "node:crypto";
2
+ import { chmod, copyFile, lstat, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { dirname, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { unzipSync } from "fflate";
7
+ import { x as extractTar } from "tar";
8
+ import { assertSupportedRuntime, releaseArtifact } from "./platform.js";
9
+
10
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
11
+ const metadata = JSON.parse(await readFile(resolve(packageRoot, "package.json"), "utf8"));
12
+ assertSupportedRuntime();
13
+ const artifact = releaseArtifact(metadata.version);
14
+ const releaseBaseUrl =
15
+ process.env.TRACKSERIES_CLI_RELEASE_BASE_URL ?? `https://github.com/TrackSeries/cli/releases/download/cli-v${metadata.version}`;
16
+ const temporaryDirectory = await mkdtemp(resolve(tmpdir(), "trackseries-cli-"));
17
+ const archivePath = resolve(temporaryDirectory, artifact);
18
+ const extractionPath = resolve(temporaryDirectory, "extracted");
19
+ const executableName = process.platform === "win32" ? "trackseries.exe" : "trackseries";
20
+ const installedExecutable = resolve(packageRoot, "vendor", executableName);
21
+ const pendingExecutable = `${installedExecutable}.${process.pid}.tmp`;
22
+
23
+ async function download(url) {
24
+ let lastError;
25
+
26
+ for (let attempt = 1; attempt <= 3; attempt += 1) {
27
+ let response;
28
+ try {
29
+ response = await fetch(url, {
30
+ headers: {
31
+ "User-Agent": `@trackseries/cli/${metadata.version}`
32
+ },
33
+ signal: AbortSignal.timeout(60_000)
34
+ });
35
+ } catch (error) {
36
+ lastError = error;
37
+ }
38
+
39
+ if (response?.ok) {
40
+ return Buffer.from(await response.arrayBuffer());
41
+ }
42
+
43
+ if (response) {
44
+ lastError = new Error(`HTTP ${response.status}`);
45
+ if (response.status < 500 && response.status !== 408 && response.status !== 429) break;
46
+ }
47
+
48
+ if (attempt < 3) {
49
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, attempt * 1_000));
50
+ }
51
+ }
52
+
53
+ throw new Error(`Unable to download ${url}: ${lastError instanceof Error ? lastError.message : String(lastError)}.`);
54
+ }
55
+
56
+ try {
57
+ const [archive, checksumFile] = await Promise.all([
58
+ download(`${releaseBaseUrl}/${artifact}`),
59
+ download(`${releaseBaseUrl}/${artifact}.sha256`)
60
+ ]);
61
+ const expectedChecksum = checksumFile.toString("utf8").trim().split(/\s+/)[0]?.toLowerCase();
62
+ const actualChecksum = createHash("sha256").update(archive).digest("hex");
63
+
64
+ if (!expectedChecksum || actualChecksum !== expectedChecksum) {
65
+ throw new Error(`Checksum verification failed for ${artifact}.`);
66
+ }
67
+
68
+ await mkdir(extractionPath, { recursive: true });
69
+ await writeFile(archivePath, archive);
70
+
71
+ if (process.platform === "win32") {
72
+ const executable = unzipSync(archive, {
73
+ filter: (file) => file.name === executableName
74
+ })[executableName];
75
+ if (!executable) {
76
+ throw new Error(`${artifact} does not contain ${executableName}.`);
77
+ }
78
+ await writeFile(resolve(extractionPath, executableName), executable);
79
+ } else {
80
+ await extractTar({
81
+ cwd: extractionPath,
82
+ file: archivePath,
83
+ filter: (path, entry) => (path === executableName || path === `./${executableName}`) && entry.type === "File"
84
+ });
85
+ }
86
+
87
+ const extractedExecutable = resolve(extractionPath, executableName);
88
+ if (!(await lstat(extractedExecutable)).isFile()) {
89
+ throw new Error(`${artifact} does not contain a regular ${executableName} file.`);
90
+ }
91
+
92
+ await mkdir(dirname(installedExecutable), { recursive: true });
93
+ await copyFile(extractedExecutable, pendingExecutable);
94
+
95
+ if (process.platform !== "win32") {
96
+ await chmod(pendingExecutable, 0o755);
97
+ } else {
98
+ await rm(installedExecutable, { force: true });
99
+ }
100
+
101
+ await rename(pendingExecutable, installedExecutable);
102
+ } catch (error) {
103
+ throw new Error(
104
+ `Unable to install TrackSeries CLI ${metadata.version}. Download ${artifact} manually from ${releaseBaseUrl}.`,
105
+ { cause: error }
106
+ );
107
+ } finally {
108
+ await rm(pendingExecutable, { force: true });
109
+ await rm(temporaryDirectory, { force: true, recursive: true });
110
+ }
@@ -0,0 +1,22 @@
1
+ export function releaseArtifact(version, platform = process.platform, architecture = process.arch) {
2
+ const platformNames = {
3
+ darwin: "macos",
4
+ linux: "linux",
5
+ win32: "windows"
6
+ };
7
+ const architectures = new Set(["arm64", "x64"]);
8
+ const platformName = platformNames[platform];
9
+
10
+ if (!platformName || !architectures.has(architecture)) {
11
+ throw new Error(`TrackSeries CLI does not support ${platform}-${architecture}.`);
12
+ }
13
+
14
+ const extension = platform === "win32" ? "zip" : "tar.gz";
15
+ return `trackseries-${version}-${platformName}-${architecture}.${extension}`;
16
+ }
17
+
18
+ export function assertSupportedRuntime(platform = process.platform, report = process.report?.getReport()) {
19
+ if (platform === "linux" && !report?.header?.glibcVersionRuntime) {
20
+ throw new Error("TrackSeries CLI requires a glibc-based Linux distribution. Alpine and other musl-based systems are not yet supported.");
21
+ }
22
+ }