@general-liquidity/gordon-cli 0.8.25 → 0.9.0-friends.5

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 CHANGED
@@ -1,126 +1,126 @@
1
- <h1 align="center">Gordon CLI</h1>
2
-
3
- <p align="center">
4
- The Frontier Trading Agent
5
- </p>
6
-
7
- <p align="center">
8
- <a href="https://www.npmjs.com/package/@general-liquidity/gordon-cli">npm</a> •
9
- <a href="https://gordoncli.com">Website</a> •
10
- <a href="https://docs.gordon.trade">Docs</a> •
11
- <a href="https://github.com/general-liquidity/gordon-cli-dist/releases">Downloads</a>
12
- </p>
13
-
14
- ## Install
15
-
16
- `npm`:
17
-
18
- ```bash
19
- npm install -g @general-liquidity/gordon-cli
20
- ```
21
-
22
- If global npm install fails with `EACCES` / permission errors on Linux or macOS, use the user-local npm path instead:
23
-
24
- ```bash
25
- npx @general-liquidity/gordon-cli@latest install
26
- ```
27
-
28
- That installs Gordon into a user-writable bin directory without `sudo`.
29
-
30
- `bun`:
31
-
32
- ```bash
33
- bun add -g @general-liquidity/gordon-cli
34
- ```
35
-
36
- `Homebrew`:
37
-
38
- ```bash
39
- brew tap general-liquidity/gordon-cli-dist https://github.com/general-liquidity/gordon-cli-dist
40
- brew install general-liquidity/gordon-cli-dist/gordon
41
- ```
42
-
43
- `Scoop`:
44
-
45
- ```powershell
46
- scoop bucket add gordon https://github.com/general-liquidity/gordon-cli-dist
47
- scoop install gordon/gordon
48
- ```
49
-
50
- Standalone install script:
51
-
52
- ```bash
53
- curl -fsSL https://raw.githubusercontent.com/general-liquidity/gordon-cli-dist/main/install.sh | sh
54
- ```
55
-
56
- Windows PowerShell:
57
-
58
- ```powershell
59
- irm https://raw.githubusercontent.com/general-liquidity/gordon-cli-dist/main/install.ps1 | iex
60
- ```
61
-
62
- The npm package is a thin wrapper. It downloads the matching prebuilt binary for your platform during install.
63
-
64
- ## npm Permission Fallback
65
-
66
- Global `npm install -g` can fail on Unix machines when the npm global prefix is root-owned. Gordon now supports a universal npm fallback:
67
-
68
- ```bash
69
- npx @general-liquidity/gordon-cli@latest install
70
- ```
71
-
72
- If the chosen install directory is not already on `PATH`, Gordon prints the exact command to add it.
73
-
74
- ## Upgrades
75
-
76
- Once installed, Gordon can upgrade itself with:
77
-
78
- ```bash
79
- gordon --upgrade
80
- ```
81
-
82
- That now resolves through the active install channel for npm, the user-local `npx` installer, Homebrew, Scoop, and the standalone install scripts.
83
-
84
- ## Supported binaries
85
-
86
- - macOS arm64
87
- - macOS x64
88
- - Linux arm64
89
- - Linux x64
90
- - Windows x64
91
-
92
- Release binaries and package manager manifests are published at:
93
-
94
- - `https://github.com/general-liquidity/gordon-cli-dist/releases`
95
-
96
- ## Setup
97
-
98
- Set one LLM provider key before first launch:
99
-
100
- ```bash
101
- export OPENAI_API_KEY="sk-..."
102
- ```
103
-
104
- or
105
-
106
- ```bash
107
- export DEDALUS_API_KEY="dd-..."
108
- ```
109
-
110
- or
111
-
112
- ```bash
113
- export INCEPTION_API_KEY="..."
114
- ```
115
-
116
- Then run:
117
-
118
- ```bash
119
- gordon
120
- ```
121
-
122
- ## Docs
123
-
124
- - Website: `https://gordoncli.com`
125
- - Docs: `https://docs.gordon.trade`
126
- - Public distribution repo: `https://github.com/general-liquidity/gordon-cli-dist`
1
+ <h1 align="center">Gordon CLI</h1>
2
+
3
+ <p align="center">
4
+ The Frontier Trading Agent
5
+ </p>
6
+
7
+ <p align="center">
8
+ <a href="https://www.npmjs.com/package/@general-liquidity/gordon-cli">npm</a> •
9
+ <a href="https://gordoncli.com">Website</a> •
10
+ <a href="https://docs.gordon.trade">Docs</a> •
11
+ <a href="https://github.com/general-liquidity/gordon-cli-dist/releases">Downloads</a>
12
+ </p>
13
+
14
+ ## Install
15
+
16
+ `npm`:
17
+
18
+ ```bash
19
+ npm install -g @general-liquidity/gordon-cli
20
+ ```
21
+
22
+ If global npm install fails with `EACCES` / permission errors on Linux or macOS, use the user-local npm path instead:
23
+
24
+ ```bash
25
+ npx @general-liquidity/gordon-cli@latest install
26
+ ```
27
+
28
+ That installs Gordon into a user-writable bin directory without `sudo`.
29
+
30
+ `bun`:
31
+
32
+ ```bash
33
+ bun add -g @general-liquidity/gordon-cli
34
+ ```
35
+
36
+ `Homebrew`:
37
+
38
+ ```bash
39
+ brew tap general-liquidity/gordon-cli-dist https://github.com/general-liquidity/gordon-cli-dist
40
+ brew install general-liquidity/gordon-cli-dist/gordon
41
+ ```
42
+
43
+ `Scoop`:
44
+
45
+ ```powershell
46
+ scoop bucket add gordon https://github.com/general-liquidity/gordon-cli-dist
47
+ scoop install gordon/gordon
48
+ ```
49
+
50
+ Standalone install script:
51
+
52
+ ```bash
53
+ curl -fsSL https://raw.githubusercontent.com/general-liquidity/gordon-cli-dist/main/install.sh | sh
54
+ ```
55
+
56
+ Windows PowerShell:
57
+
58
+ ```powershell
59
+ irm https://raw.githubusercontent.com/general-liquidity/gordon-cli-dist/main/install.ps1 | iex
60
+ ```
61
+
62
+ The npm package is a thin wrapper. It downloads the matching prebuilt binary for your platform during install.
63
+
64
+ ## npm Permission Fallback
65
+
66
+ Global `npm install -g` can fail on Unix machines when the npm global prefix is root-owned. Gordon now supports a universal npm fallback:
67
+
68
+ ```bash
69
+ npx @general-liquidity/gordon-cli@latest install
70
+ ```
71
+
72
+ If the chosen install directory is not already on `PATH`, Gordon prints the exact command to add it.
73
+
74
+ ## Upgrades
75
+
76
+ Once installed, Gordon can upgrade itself with:
77
+
78
+ ```bash
79
+ gordon --upgrade
80
+ ```
81
+
82
+ That now resolves through the active install channel for npm, the user-local `npx` installer, Homebrew, Scoop, and the standalone install scripts.
83
+
84
+ ## Supported binaries
85
+
86
+ - macOS arm64
87
+ - macOS x64
88
+ - Linux arm64
89
+ - Linux x64
90
+ - Windows x64
91
+
92
+ Release binaries and package manager manifests are published at:
93
+
94
+ - `https://github.com/general-liquidity/gordon-cli-dist/releases`
95
+
96
+ ## Setup
97
+
98
+ Set one LLM provider key before first launch:
99
+
100
+ ```bash
101
+ export OPENAI_API_KEY="sk-..."
102
+ ```
103
+
104
+ or
105
+
106
+ ```bash
107
+ export DEDALUS_API_KEY="dd-..."
108
+ ```
109
+
110
+ or
111
+
112
+ ```bash
113
+ export INCEPTION_API_KEY="..."
114
+ ```
115
+
116
+ Then run:
117
+
118
+ ```bash
119
+ gordon
120
+ ```
121
+
122
+ ## Docs
123
+
124
+ - Website: `https://gordoncli.com`
125
+ - Docs: `https://docs.gordon.trade`
126
+ - Public distribution repo: `https://github.com/general-liquidity/gordon-cli-dist`
package/bin/gordon.cjs CHANGED
@@ -1,52 +1,52 @@
1
- #!/usr/bin/env node
2
-
3
- const fs = require("fs");
4
- const path = require("path");
5
- const { spawn } = require("child_process");
6
- const { getInstalledBinaryPath } = require("../lib/platform.cjs");
7
- const { runSelfInstall } = require("../lib/self-install.cjs");
8
-
9
- const packageRoot = path.resolve(__dirname, "..");
10
- const args = process.argv.slice(2);
11
-
12
- if (args[0] === "install") {
13
- runSelfInstall(args.slice(1), { packageRoot }).then(
14
- (code) => process.exit(code || 0),
15
- (error) => {
16
- console.error(`[gordon] ${error.message}`);
17
- process.exit(1);
18
- }
19
- );
20
- return;
21
- }
22
-
23
- let binaryPath;
24
- try {
25
- binaryPath = getInstalledBinaryPath(packageRoot);
26
- } catch (error) {
27
- console.error(`[gordon] ${error.message}`);
28
- process.exit(1);
29
- }
30
-
31
- if (!fs.existsSync(binaryPath)) {
32
- console.error(
33
- "[gordon] The Gordon binary is missing. Reinstall with `npm install -g @general-liquidity/gordon-cli` or run `npx @general-liquidity/gordon-cli@latest install` for a user-local install."
34
- );
35
- process.exit(1);
36
- }
37
-
38
- const child = spawn(binaryPath, args, { stdio: "inherit" });
39
-
40
- child.on("error", (error) => {
41
- console.error(`[gordon] Failed to launch ${path.basename(binaryPath)}: ${error.message}`);
42
- process.exit(1);
43
- });
44
-
45
- child.on("exit", (code, signal) => {
46
- if (signal) {
47
- process.kill(process.pid, signal);
48
- return;
49
- }
50
-
51
- process.exit(code ?? 1);
52
- });
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const { spawn } = require("child_process");
6
+ const { getInstalledBinaryPath } = require("../lib/platform.cjs");
7
+ const { runSelfInstall } = require("../lib/self-install.cjs");
8
+
9
+ const packageRoot = path.resolve(__dirname, "..");
10
+ const args = process.argv.slice(2);
11
+
12
+ if (args[0] === "install") {
13
+ runSelfInstall(args.slice(1), { packageRoot }).then(
14
+ (code) => process.exit(code || 0),
15
+ (error) => {
16
+ console.error(`[gordon] ${error.message}`);
17
+ process.exit(1);
18
+ }
19
+ );
20
+ return;
21
+ }
22
+
23
+ let binaryPath;
24
+ try {
25
+ binaryPath = getInstalledBinaryPath(packageRoot);
26
+ } catch (error) {
27
+ console.error(`[gordon] ${error.message}`);
28
+ process.exit(1);
29
+ }
30
+
31
+ if (!fs.existsSync(binaryPath)) {
32
+ console.error(
33
+ "[gordon] The Gordon binary is missing. Reinstall with `npm install -g @general-liquidity/gordon-cli` or run `npx @general-liquidity/gordon-cli@latest install` for a user-local install."
34
+ );
35
+ process.exit(1);
36
+ }
37
+
38
+ const child = spawn(binaryPath, args, { stdio: "inherit" });
39
+
40
+ child.on("error", (error) => {
41
+ console.error(`[gordon] Failed to launch ${path.basename(binaryPath)}: ${error.message}`);
42
+ process.exit(1);
43
+ });
44
+
45
+ child.on("exit", (code, signal) => {
46
+ if (signal) {
47
+ process.kill(process.pid, signal);
48
+ return;
49
+ }
50
+
51
+ process.exit(code ?? 1);
52
+ });
package/lib/platform.cjs CHANGED
@@ -1,53 +1,78 @@
1
- const path = require("path");
2
-
3
- const SUPPORTED_TARGETS = {
4
- "darwin:arm64": { assetName: "gordon-darwin-arm64", binaryName: "gordon" },
5
- "darwin:x64": { assetName: "gordon-darwin-x64", binaryName: "gordon" },
6
- "linux:arm64": { assetName: "gordon-linux-arm64", binaryName: "gordon" },
7
- "linux:x64": { assetName: "gordon-linux-x64", binaryName: "gordon" },
8
- "win32:arm64": {
9
- assetName: "gordon-windows-x64.exe",
10
- binaryName: "gordon.exe",
11
- note: "Using the Windows x64 binary on arm64."
12
- },
13
- "win32:x64": { assetName: "gordon-windows-x64.exe", binaryName: "gordon.exe" }
14
- };
15
-
16
- function getTarget(platform = process.platform, arch = process.arch) {
17
- const key = `${platform}:${arch}`;
18
- const target = SUPPORTED_TARGETS[key];
19
- if (target) {
20
- return target;
21
- }
22
-
23
- const supported = Object.keys(SUPPORTED_TARGETS)
24
- .sort()
25
- .join(", ");
26
- throw new Error(`Unsupported platform ${platform}/${arch}. Supported targets: ${supported}`);
27
- }
28
-
29
- function getInstallDirectory(packageRoot = path.resolve(__dirname, "..")) {
30
- return path.join(packageRoot, "vendor");
31
- }
32
-
33
- function getInstalledBinaryPath(packageRoot = path.resolve(__dirname, ".."), platform, arch) {
34
- const { binaryName } = getTarget(platform, arch);
35
- return path.join(getInstallDirectory(packageRoot), binaryName);
36
- }
37
-
38
- function getDownloadUrl(version, platform = process.platform, arch = process.arch) {
39
- const { assetName } = getTarget(platform, arch);
40
- const cleanVersion = String(version).replace(/^v/, "");
41
- const distRepo = process.env.GORDON_BINARY_DIST_REPO || "general-liquidity/gordon-cli-dist";
42
- const baseUrl =
43
- process.env.GORDON_BINARY_BASE_URL || `https://github.com/${distRepo}/releases/download/v${cleanVersion}`;
44
- return `${baseUrl.replace(/\/$/, "")}/${assetName}`;
45
- }
46
-
47
- module.exports = {
48
- SUPPORTED_TARGETS,
49
- getDownloadUrl,
50
- getInstallDirectory,
51
- getInstalledBinaryPath,
52
- getTarget
53
- };
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ /**
5
+ * Detect whether we're running on a musl libc Linux distro (Alpine,
6
+ * Void, etc). musl binaries are ABI-incompatible with glibc binaries —
7
+ * shipping the wrong one crashes immediately at startup.
8
+ */
9
+ function isMuslLinux() {
10
+ if (process.platform !== "linux") return false;
11
+ // Heuristic 1: Alpine and Void leave a marker file
12
+ if (fs.existsSync("/etc/alpine-release")) return true;
13
+ // Heuristic 2: ldd prints the libc path
14
+ try {
15
+ const { execFileSync } = require("child_process");
16
+ const out = execFileSync("ldd", ["--version"], { stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" });
17
+ if (/musl/i.test(out)) return true;
18
+ } catch {
19
+ // ldd missing — fall through
20
+ }
21
+ return false;
22
+ }
23
+
24
+ const SUPPORTED_TARGETS = {
25
+ "darwin:arm64": { assetName: "gordon-darwin-arm64", binaryName: "gordon" },
26
+ "darwin:x64": { assetName: "gordon-darwin-x64", binaryName: "gordon" },
27
+ "linux:arm64": { assetName: "gordon-linux-arm64", binaryName: "gordon" },
28
+ "linux:x64": { assetName: "gordon-linux-x64", binaryName: "gordon" },
29
+ "linux:arm64:musl": { assetName: "gordon-linux-arm64-musl", binaryName: "gordon" },
30
+ "linux:x64:musl": { assetName: "gordon-linux-x64-musl", binaryName: "gordon" },
31
+ "win32:arm64": { assetName: "gordon-windows-arm64.exe", binaryName: "gordon.exe" },
32
+ "win32:x64": { assetName: "gordon-windows-x64.exe", binaryName: "gordon.exe" }
33
+ };
34
+
35
+ function getTarget(platform = process.platform, arch = process.arch) {
36
+ // Linux gets a musl variant — pick the right one based on the host libc
37
+ let key = `${platform}:${arch}`;
38
+ if (platform === "linux" && isMuslLinux()) {
39
+ const muslKey = `${platform}:${arch}:musl`;
40
+ if (SUPPORTED_TARGETS[muslKey]) key = muslKey;
41
+ }
42
+
43
+ const target = SUPPORTED_TARGETS[key];
44
+ if (target) {
45
+ return target;
46
+ }
47
+
48
+ const supported = Object.keys(SUPPORTED_TARGETS)
49
+ .sort()
50
+ .join(", ");
51
+ throw new Error(`Unsupported platform ${platform}/${arch}. Supported targets: ${supported}`);
52
+ }
53
+
54
+ function getInstallDirectory(packageRoot = path.resolve(__dirname, "..")) {
55
+ return path.join(packageRoot, "vendor");
56
+ }
57
+
58
+ function getInstalledBinaryPath(packageRoot = path.resolve(__dirname, ".."), platform, arch) {
59
+ const { binaryName } = getTarget(platform, arch);
60
+ return path.join(getInstallDirectory(packageRoot), binaryName);
61
+ }
62
+
63
+ function getDownloadUrl(version, platform = process.platform, arch = process.arch) {
64
+ const { assetName } = getTarget(platform, arch);
65
+ const cleanVersion = String(version).replace(/^v/, "");
66
+ const distRepo = process.env.GORDON_BINARY_DIST_REPO || "general-liquidity/gordon-cli-dist";
67
+ const baseUrl =
68
+ process.env.GORDON_BINARY_BASE_URL || `https://github.com/${distRepo}/releases/download/v${cleanVersion}`;
69
+ return `${baseUrl.replace(/\/$/, "")}/${assetName}`;
70
+ }
71
+
72
+ module.exports = {
73
+ SUPPORTED_TARGETS,
74
+ getDownloadUrl,
75
+ getInstallDirectory,
76
+ getInstalledBinaryPath,
77
+ getTarget
78
+ };
@@ -1,343 +1,343 @@
1
- const fs = require("fs");
2
- const os = require("os");
3
- const path = require("path");
4
- const https = require("https");
5
- const { pipeline } = require("stream/promises");
6
- const { getDownloadUrl, getInstalledBinaryPath, getTarget } = require("./platform.cjs");
7
- const pkg = require("../package.json");
8
- const INSTALL_METADATA_FILENAME = "gordon-install.json";
9
-
10
- function log(message) {
11
- console.log(`[gordon] ${message}`);
12
- }
13
-
14
- function parseArgs(args) {
15
- const options = {
16
- help: false,
17
- targetDir: null
18
- };
19
-
20
- for (let index = 0; index < args.length; index += 1) {
21
- const arg = args[index];
22
- if (arg === "--help" || arg === "-h") {
23
- options.help = true;
24
- continue;
25
- }
26
-
27
- if ((arg === "--target-dir" || arg === "--dir") && args[index + 1]) {
28
- options.targetDir = path.resolve(args[index + 1]);
29
- index += 1;
30
- continue;
31
- }
32
-
33
- throw new Error(`Unknown install option: ${arg}`);
34
- }
35
-
36
- return options;
37
- }
38
-
39
- function printHelp() {
40
- console.log(`gordon install
41
-
42
- Install Gordon into a user-writable bin directory without requiring \`npm install -g\`.
43
-
44
- Usage:
45
- npx @general-liquidity/gordon-cli@latest install
46
- gordon install --target-dir <directory>
47
-
48
- Options:
49
- --target-dir <dir> Override the install directory
50
- -h, --help Show this help
51
- `);
52
- }
53
-
54
- function normalizeForCompare(value) {
55
- const resolved = path.resolve(value);
56
- return process.platform === "win32" ? resolved.toLowerCase() : resolved;
57
- }
58
-
59
- function splitPathEntries(envPath = process.env.PATH || "") {
60
- return envPath.split(path.delimiter).filter(Boolean);
61
- }
62
-
63
- function pathContainsDirectory(directory, env = process.env) {
64
- const target = normalizeForCompare(directory);
65
- return splitPathEntries(env.PATH).some((entry) => {
66
- try {
67
- return normalizeForCompare(entry) === target;
68
- } catch {
69
- return false;
70
- }
71
- });
72
- }
73
-
74
- function uniqueDirectories(values) {
75
- const seen = new Set();
76
- const result = [];
77
- for (const value of values) {
78
- if (!value) {
79
- continue;
80
- }
81
-
82
- const normalized = normalizeForCompare(value);
83
- if (seen.has(normalized)) {
84
- continue;
85
- }
86
-
87
- seen.add(normalized);
88
- result.push(path.resolve(value));
89
- }
90
- return result;
91
- }
92
-
93
- async function isWritableDirectory(directory, { create } = { create: false }) {
94
- try {
95
- if (create) {
96
- await fs.promises.mkdir(directory, { recursive: true });
97
- } else {
98
- const stats = await fs.promises.stat(directory);
99
- if (!stats.isDirectory()) {
100
- return false;
101
- }
102
- }
103
-
104
- const probePath = path.join(directory, `.gordon-write-test-${process.pid}-${Date.now()}`);
105
- await fs.promises.writeFile(probePath, "");
106
- await fs.promises.rm(probePath, { force: true });
107
- return true;
108
- } catch {
109
- return false;
110
- }
111
- }
112
-
113
- async function resolveInstallDirectory(explicitTargetDir = null, env = process.env) {
114
- if (explicitTargetDir) {
115
- return {
116
- directory: explicitTargetDir,
117
- inPath: pathContainsDirectory(explicitTargetDir, env)
118
- };
119
- }
120
-
121
- const homeDirectory = os.homedir();
122
- const pathEntries = splitPathEntries(env.PATH);
123
- const standardCandidates = [];
124
- const pathCandidates = [];
125
-
126
- if (process.platform === "win32") {
127
- if (env.APPDATA) {
128
- standardCandidates.push(path.join(env.APPDATA, "npm"));
129
- }
130
- if (env.LOCALAPPDATA) {
131
- standardCandidates.push(path.join(env.LOCALAPPDATA, "Microsoft", "WinGet", "Links"));
132
- standardCandidates.push(path.join(env.LOCALAPPDATA, "gordon"));
133
- }
134
-
135
- for (const entry of pathEntries) {
136
- const resolvedEntry = path.resolve(entry);
137
- const normalizedEntry = normalizeForCompare(resolvedEntry);
138
- const userRoots = uniqueDirectories([
139
- env.USERPROFILE,
140
- env.LOCALAPPDATA,
141
- env.APPDATA,
142
- homeDirectory
143
- ]);
144
- const withinUserRoot = userRoots.some((root) => {
145
- const normalizedRoot = normalizeForCompare(root);
146
- return normalizedEntry === normalizedRoot || normalizedEntry.startsWith(`${normalizedRoot}${path.sep}`);
147
- });
148
- if (
149
- withinUserRoot
150
- && !normalizedEntry.includes(`${path.sep}node_modules${path.sep}`)
151
- ) {
152
- pathCandidates.push(resolvedEntry);
153
- }
154
- }
155
- } else {
156
- standardCandidates.push(path.join(homeDirectory, ".local", "bin"));
157
- standardCandidates.push(path.join(homeDirectory, "bin"));
158
-
159
- for (const entry of pathEntries) {
160
- const resolvedEntry = path.resolve(entry);
161
- const normalizedEntry = normalizeForCompare(resolvedEntry);
162
- const normalizedHome = `${normalizeForCompare(homeDirectory)}${path.sep}`;
163
- if (
164
- normalizedEntry.startsWith(normalizedHome)
165
- && !normalizedEntry.includes(`${path.sep}node_modules${path.sep}`)
166
- ) {
167
- pathCandidates.push(resolvedEntry);
168
- }
169
- }
170
- }
171
-
172
- const orderedCandidates = uniqueDirectories([
173
- ...standardCandidates.filter((candidate) => pathContainsDirectory(candidate, env)),
174
- ...pathCandidates,
175
- ...standardCandidates
176
- ]);
177
-
178
- for (const candidate of orderedCandidates) {
179
- const create = standardCandidates.some((standardCandidate) => normalizeForCompare(standardCandidate) === normalizeForCompare(candidate));
180
- if (await isWritableDirectory(candidate, { create })) {
181
- return {
182
- directory: candidate,
183
- inPath: pathContainsDirectory(candidate, env)
184
- };
185
- }
186
- }
187
-
188
- throw new Error("Could not find a writable user install directory.");
189
- }
190
-
191
- async function downloadBinary(url, destinationPath, redirectCount = 0) {
192
- if (redirectCount > 5) {
193
- throw new Error(`Too many redirects while downloading ${url}`);
194
- }
195
-
196
- await new Promise((resolve, reject) => {
197
- const request = https.get(
198
- url,
199
- { headers: { "User-Agent": "gordon-npm-installer" } },
200
- async (response) => {
201
- if (
202
- response.statusCode &&
203
- response.statusCode >= 300 &&
204
- response.statusCode < 400 &&
205
- response.headers.location
206
- ) {
207
- response.resume();
208
- try {
209
- await downloadBinary(response.headers.location, destinationPath, redirectCount + 1);
210
- resolve();
211
- } catch (error) {
212
- reject(error);
213
- }
214
- return;
215
- }
216
-
217
- if (response.statusCode !== 200) {
218
- response.resume();
219
- reject(new Error(`Download failed with status ${response.statusCode} for ${url}`));
220
- return;
221
- }
222
-
223
- const fileStream = fs.createWriteStream(destinationPath);
224
- pipeline(response, fileStream).then(resolve, reject);
225
- }
226
- );
227
-
228
- request.on("error", reject);
229
- });
230
- }
231
-
232
- async function finalizeBinary(tempPath, targetPath) {
233
- if (process.platform !== "win32") {
234
- await fs.promises.chmod(tempPath, 0o755);
235
- }
236
-
237
- await fs.promises.rm(targetPath, { force: true });
238
- await fs.promises.rename(tempPath, targetPath);
239
- }
240
-
241
- async function writeInstallMetadata(targetDirectory, version, channel) {
242
- const metadataPath = path.join(targetDirectory, INSTALL_METADATA_FILENAME);
243
- const payload = {
244
- channel,
245
- installDir: targetDirectory,
246
- version,
247
- installedAt: new Date().toISOString()
248
- };
249
- await fs.promises.writeFile(metadataPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
250
- }
251
-
252
- function getUnixPathHint(directory, env = process.env) {
253
- const shell = env.SHELL || "";
254
- if (shell.includes("fish")) {
255
- return {
256
- profile: "~/.config/fish/config.fish",
257
- command: `fish_add_path ${directory.replace(os.homedir(), "$HOME")}`
258
- };
259
- }
260
-
261
- if (shell.includes("zsh")) {
262
- return {
263
- profile: "~/.zshrc",
264
- command: `echo 'export PATH=\"${directory.replace(os.homedir(), "$HOME")}:$PATH\"' >> ~/.zshrc`
265
- };
266
- }
267
-
268
- return {
269
- profile: "~/.bashrc",
270
- command: `echo 'export PATH=\"${directory.replace(os.homedir(), "$HOME")}:$PATH\"' >> ~/.bashrc`
271
- };
272
- }
273
-
274
- function printPathGuidance(directory) {
275
- if (process.platform === "win32") {
276
- log(`Add ${directory} to your user PATH, then open a new terminal.`);
277
- return;
278
- }
279
-
280
- const hint = getUnixPathHint(directory);
281
- log(`Add ${directory} to PATH if your shell cannot find \`gordon\` yet.`);
282
- console.log(` ${hint.command}`);
283
- console.log(` # then restart your shell or source ${hint.profile}`);
284
- }
285
-
286
- async function installBinary({ targetDirectory, sourceBinaryPath, version }) {
287
- const { binaryName, assetName } = getTarget();
288
- await fs.promises.mkdir(targetDirectory, { recursive: true });
289
-
290
- const installPath = path.join(targetDirectory, binaryName);
291
- const tempPath = `${installPath}.tmp`;
292
- await fs.promises.rm(tempPath, { force: true });
293
-
294
- if (sourceBinaryPath && fs.existsSync(sourceBinaryPath)) {
295
- log(`Copying ${path.basename(sourceBinaryPath)} to ${installPath}`);
296
- await fs.promises.copyFile(sourceBinaryPath, tempPath);
297
- } else {
298
- const downloadUrl = getDownloadUrl(version);
299
- log(`Downloading ${assetName} from ${downloadUrl}`);
300
- await downloadBinary(downloadUrl, tempPath);
301
- }
302
-
303
- await finalizeBinary(tempPath, installPath);
304
- return installPath;
305
- }
306
-
307
- async function runSelfInstall(args, options = {}) {
308
- const parsed = parseArgs(args);
309
- if (parsed.help) {
310
- printHelp();
311
- return 0;
312
- }
313
-
314
- const packageRoot = options.packageRoot || path.resolve(__dirname, "..");
315
- const version = String(options.version || pkg.version).replace(/^v/, "");
316
- const bundledBinaryPath = options.sourceBinaryPath || getInstalledBinaryPath(packageRoot);
317
- const sourceBinaryPath = fs.existsSync(bundledBinaryPath) ? bundledBinaryPath : null;
318
- const { directory, inPath } = await resolveInstallDirectory(parsed.targetDir);
319
- const installPath = await installBinary({
320
- targetDirectory: directory,
321
- sourceBinaryPath,
322
- version
323
- });
324
- await writeInstallMetadata(directory, version, options.channel || "npx");
325
-
326
- console.log("");
327
- log(`Installed Gordon v${version} to ${installPath}`);
328
- if (!inPath) {
329
- printPathGuidance(directory);
330
- }
331
- console.log("");
332
- console.log("Next steps:");
333
- console.log(" gordon --help");
334
- console.log(" gordon");
335
-
336
- return 0;
337
- }
338
-
339
- module.exports = {
340
- parseArgs,
341
- resolveInstallDirectory,
342
- runSelfInstall
343
- };
1
+ const fs = require("fs");
2
+ const os = require("os");
3
+ const path = require("path");
4
+ const https = require("https");
5
+ const { pipeline } = require("stream/promises");
6
+ const { getDownloadUrl, getInstalledBinaryPath, getTarget } = require("./platform.cjs");
7
+ const pkg = require("../package.json");
8
+ const INSTALL_METADATA_FILENAME = "gordon-install.json";
9
+
10
+ function log(message) {
11
+ console.log(`[gordon] ${message}`);
12
+ }
13
+
14
+ function parseArgs(args) {
15
+ const options = {
16
+ help: false,
17
+ targetDir: null
18
+ };
19
+
20
+ for (let index = 0; index < args.length; index += 1) {
21
+ const arg = args[index];
22
+ if (arg === "--help" || arg === "-h") {
23
+ options.help = true;
24
+ continue;
25
+ }
26
+
27
+ if ((arg === "--target-dir" || arg === "--dir") && args[index + 1]) {
28
+ options.targetDir = path.resolve(args[index + 1]);
29
+ index += 1;
30
+ continue;
31
+ }
32
+
33
+ throw new Error(`Unknown install option: ${arg}`);
34
+ }
35
+
36
+ return options;
37
+ }
38
+
39
+ function printHelp() {
40
+ console.log(`gordon install
41
+
42
+ Install Gordon into a user-writable bin directory without requiring \`npm install -g\`.
43
+
44
+ Usage:
45
+ npx @general-liquidity/gordon-cli@latest install
46
+ gordon install --target-dir <directory>
47
+
48
+ Options:
49
+ --target-dir <dir> Override the install directory
50
+ -h, --help Show this help
51
+ `);
52
+ }
53
+
54
+ function normalizeForCompare(value) {
55
+ const resolved = path.resolve(value);
56
+ return process.platform === "win32" ? resolved.toLowerCase() : resolved;
57
+ }
58
+
59
+ function splitPathEntries(envPath = process.env.PATH || "") {
60
+ return envPath.split(path.delimiter).filter(Boolean);
61
+ }
62
+
63
+ function pathContainsDirectory(directory, env = process.env) {
64
+ const target = normalizeForCompare(directory);
65
+ return splitPathEntries(env.PATH).some((entry) => {
66
+ try {
67
+ return normalizeForCompare(entry) === target;
68
+ } catch {
69
+ return false;
70
+ }
71
+ });
72
+ }
73
+
74
+ function uniqueDirectories(values) {
75
+ const seen = new Set();
76
+ const result = [];
77
+ for (const value of values) {
78
+ if (!value) {
79
+ continue;
80
+ }
81
+
82
+ const normalized = normalizeForCompare(value);
83
+ if (seen.has(normalized)) {
84
+ continue;
85
+ }
86
+
87
+ seen.add(normalized);
88
+ result.push(path.resolve(value));
89
+ }
90
+ return result;
91
+ }
92
+
93
+ async function isWritableDirectory(directory, { create } = { create: false }) {
94
+ try {
95
+ if (create) {
96
+ await fs.promises.mkdir(directory, { recursive: true });
97
+ } else {
98
+ const stats = await fs.promises.stat(directory);
99
+ if (!stats.isDirectory()) {
100
+ return false;
101
+ }
102
+ }
103
+
104
+ const probePath = path.join(directory, `.gordon-write-test-${process.pid}-${Date.now()}`);
105
+ await fs.promises.writeFile(probePath, "");
106
+ await fs.promises.rm(probePath, { force: true });
107
+ return true;
108
+ } catch {
109
+ return false;
110
+ }
111
+ }
112
+
113
+ async function resolveInstallDirectory(explicitTargetDir = null, env = process.env) {
114
+ if (explicitTargetDir) {
115
+ return {
116
+ directory: explicitTargetDir,
117
+ inPath: pathContainsDirectory(explicitTargetDir, env)
118
+ };
119
+ }
120
+
121
+ const homeDirectory = os.homedir();
122
+ const pathEntries = splitPathEntries(env.PATH);
123
+ const standardCandidates = [];
124
+ const pathCandidates = [];
125
+
126
+ if (process.platform === "win32") {
127
+ if (env.APPDATA) {
128
+ standardCandidates.push(path.join(env.APPDATA, "npm"));
129
+ }
130
+ if (env.LOCALAPPDATA) {
131
+ standardCandidates.push(path.join(env.LOCALAPPDATA, "Microsoft", "WinGet", "Links"));
132
+ standardCandidates.push(path.join(env.LOCALAPPDATA, "gordon"));
133
+ }
134
+
135
+ for (const entry of pathEntries) {
136
+ const resolvedEntry = path.resolve(entry);
137
+ const normalizedEntry = normalizeForCompare(resolvedEntry);
138
+ const userRoots = uniqueDirectories([
139
+ env.USERPROFILE,
140
+ env.LOCALAPPDATA,
141
+ env.APPDATA,
142
+ homeDirectory
143
+ ]);
144
+ const withinUserRoot = userRoots.some((root) => {
145
+ const normalizedRoot = normalizeForCompare(root);
146
+ return normalizedEntry === normalizedRoot || normalizedEntry.startsWith(`${normalizedRoot}${path.sep}`);
147
+ });
148
+ if (
149
+ withinUserRoot
150
+ && !normalizedEntry.includes(`${path.sep}node_modules${path.sep}`)
151
+ ) {
152
+ pathCandidates.push(resolvedEntry);
153
+ }
154
+ }
155
+ } else {
156
+ standardCandidates.push(path.join(homeDirectory, ".local", "bin"));
157
+ standardCandidates.push(path.join(homeDirectory, "bin"));
158
+
159
+ for (const entry of pathEntries) {
160
+ const resolvedEntry = path.resolve(entry);
161
+ const normalizedEntry = normalizeForCompare(resolvedEntry);
162
+ const normalizedHome = `${normalizeForCompare(homeDirectory)}${path.sep}`;
163
+ if (
164
+ normalizedEntry.startsWith(normalizedHome)
165
+ && !normalizedEntry.includes(`${path.sep}node_modules${path.sep}`)
166
+ ) {
167
+ pathCandidates.push(resolvedEntry);
168
+ }
169
+ }
170
+ }
171
+
172
+ const orderedCandidates = uniqueDirectories([
173
+ ...standardCandidates.filter((candidate) => pathContainsDirectory(candidate, env)),
174
+ ...pathCandidates,
175
+ ...standardCandidates
176
+ ]);
177
+
178
+ for (const candidate of orderedCandidates) {
179
+ const create = standardCandidates.some((standardCandidate) => normalizeForCompare(standardCandidate) === normalizeForCompare(candidate));
180
+ if (await isWritableDirectory(candidate, { create })) {
181
+ return {
182
+ directory: candidate,
183
+ inPath: pathContainsDirectory(candidate, env)
184
+ };
185
+ }
186
+ }
187
+
188
+ throw new Error("Could not find a writable user install directory.");
189
+ }
190
+
191
+ async function downloadBinary(url, destinationPath, redirectCount = 0) {
192
+ if (redirectCount > 5) {
193
+ throw new Error(`Too many redirects while downloading ${url}`);
194
+ }
195
+
196
+ await new Promise((resolve, reject) => {
197
+ const request = https.get(
198
+ url,
199
+ { headers: { "User-Agent": "gordon-npm-installer" } },
200
+ async (response) => {
201
+ if (
202
+ response.statusCode &&
203
+ response.statusCode >= 300 &&
204
+ response.statusCode < 400 &&
205
+ response.headers.location
206
+ ) {
207
+ response.resume();
208
+ try {
209
+ await downloadBinary(response.headers.location, destinationPath, redirectCount + 1);
210
+ resolve();
211
+ } catch (error) {
212
+ reject(error);
213
+ }
214
+ return;
215
+ }
216
+
217
+ if (response.statusCode !== 200) {
218
+ response.resume();
219
+ reject(new Error(`Download failed with status ${response.statusCode} for ${url}`));
220
+ return;
221
+ }
222
+
223
+ const fileStream = fs.createWriteStream(destinationPath);
224
+ pipeline(response, fileStream).then(resolve, reject);
225
+ }
226
+ );
227
+
228
+ request.on("error", reject);
229
+ });
230
+ }
231
+
232
+ async function finalizeBinary(tempPath, targetPath) {
233
+ if (process.platform !== "win32") {
234
+ await fs.promises.chmod(tempPath, 0o755);
235
+ }
236
+
237
+ await fs.promises.rm(targetPath, { force: true });
238
+ await fs.promises.rename(tempPath, targetPath);
239
+ }
240
+
241
+ async function writeInstallMetadata(targetDirectory, version, channel) {
242
+ const metadataPath = path.join(targetDirectory, INSTALL_METADATA_FILENAME);
243
+ const payload = {
244
+ channel,
245
+ installDir: targetDirectory,
246
+ version,
247
+ installedAt: new Date().toISOString()
248
+ };
249
+ await fs.promises.writeFile(metadataPath, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
250
+ }
251
+
252
+ function getUnixPathHint(directory, env = process.env) {
253
+ const shell = env.SHELL || "";
254
+ if (shell.includes("fish")) {
255
+ return {
256
+ profile: "~/.config/fish/config.fish",
257
+ command: `fish_add_path ${directory.replace(os.homedir(), "$HOME")}`
258
+ };
259
+ }
260
+
261
+ if (shell.includes("zsh")) {
262
+ return {
263
+ profile: "~/.zshrc",
264
+ command: `echo 'export PATH=\"${directory.replace(os.homedir(), "$HOME")}:$PATH\"' >> ~/.zshrc`
265
+ };
266
+ }
267
+
268
+ return {
269
+ profile: "~/.bashrc",
270
+ command: `echo 'export PATH=\"${directory.replace(os.homedir(), "$HOME")}:$PATH\"' >> ~/.bashrc`
271
+ };
272
+ }
273
+
274
+ function printPathGuidance(directory) {
275
+ if (process.platform === "win32") {
276
+ log(`Add ${directory} to your user PATH, then open a new terminal.`);
277
+ return;
278
+ }
279
+
280
+ const hint = getUnixPathHint(directory);
281
+ log(`Add ${directory} to PATH if your shell cannot find \`gordon\` yet.`);
282
+ console.log(` ${hint.command}`);
283
+ console.log(` # then restart your shell or source ${hint.profile}`);
284
+ }
285
+
286
+ async function installBinary({ targetDirectory, sourceBinaryPath, version }) {
287
+ const { binaryName, assetName } = getTarget();
288
+ await fs.promises.mkdir(targetDirectory, { recursive: true });
289
+
290
+ const installPath = path.join(targetDirectory, binaryName);
291
+ const tempPath = `${installPath}.tmp`;
292
+ await fs.promises.rm(tempPath, { force: true });
293
+
294
+ if (sourceBinaryPath && fs.existsSync(sourceBinaryPath)) {
295
+ log(`Copying ${path.basename(sourceBinaryPath)} to ${installPath}`);
296
+ await fs.promises.copyFile(sourceBinaryPath, tempPath);
297
+ } else {
298
+ const downloadUrl = getDownloadUrl(version);
299
+ log(`Downloading ${assetName} from ${downloadUrl}`);
300
+ await downloadBinary(downloadUrl, tempPath);
301
+ }
302
+
303
+ await finalizeBinary(tempPath, installPath);
304
+ return installPath;
305
+ }
306
+
307
+ async function runSelfInstall(args, options = {}) {
308
+ const parsed = parseArgs(args);
309
+ if (parsed.help) {
310
+ printHelp();
311
+ return 0;
312
+ }
313
+
314
+ const packageRoot = options.packageRoot || path.resolve(__dirname, "..");
315
+ const version = String(options.version || pkg.version).replace(/^v/, "");
316
+ const bundledBinaryPath = options.sourceBinaryPath || getInstalledBinaryPath(packageRoot);
317
+ const sourceBinaryPath = fs.existsSync(bundledBinaryPath) ? bundledBinaryPath : null;
318
+ const { directory, inPath } = await resolveInstallDirectory(parsed.targetDir);
319
+ const installPath = await installBinary({
320
+ targetDirectory: directory,
321
+ sourceBinaryPath,
322
+ version
323
+ });
324
+ await writeInstallMetadata(directory, version, options.channel || "npx");
325
+
326
+ console.log("");
327
+ log(`Installed Gordon v${version} to ${installPath}`);
328
+ if (!inPath) {
329
+ printPathGuidance(directory);
330
+ }
331
+ console.log("");
332
+ console.log("Next steps:");
333
+ console.log(" gordon --help");
334
+ console.log(" gordon");
335
+
336
+ return 0;
337
+ }
338
+
339
+ module.exports = {
340
+ parseArgs,
341
+ resolveInstallDirectory,
342
+ runSelfInstall
343
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@general-liquidity/gordon-cli",
3
- "version": "0.8.25",
3
+ "version": "0.9.0-friends.5",
4
4
  "description": "The Frontier Trading Agent",
5
5
  "author": "General Liquidity, Inc.",
6
6
  "license": "MIT",
@@ -32,6 +32,10 @@
32
32
  "engines": {
33
33
  "node": ">=18"
34
34
  },
35
+ "publishConfig": {
36
+ "access": "public",
37
+ "tag": "friends"
38
+ },
35
39
  "scripts": {
36
40
  "prepack": "node ../scripts/prepare-npm-wrapper.cjs",
37
41
  "postinstall": "node scripts/postinstall.cjs"