@general-liquidity/gordon-cli 0.8.19 → 0.8.22

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,455 +1,126 @@
1
- <p align="center">
2
- <img src="gordon_ascii.png" alt="Gordon" width="400">
3
- </p>
4
-
5
- <h1 align="center">The Frontier Trading Agent</h1>
6
-
7
- <p align="center">
8
- <em>Talk naturally. Trade confidently. Sleep peacefully.</em>
9
- </p>
1
+ <h1 align="center">Gordon CLI</h1>
10
2
 
11
3
  <p align="center">
12
- <a href="https://www.npmjs.com/package/@general-liquidity/gordon-cli"><img src="https://img.shields.io/npm/v/@general-liquidity/gordon-cli.svg?style=flat-square&color=d4a27f" alt="npm version"></a>
13
- <a href="https://github.com/general-liquidity/gordon-cli/actions"><img src="https://img.shields.io/github/actions/workflow/status/general-liquidity/gordon-cli/release.yml?style=flat-square&label=build" alt="build status"></a>
14
- <a href="https://github.com/general-liquidity/gordon-cli/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/@general-liquidity/gordon-cli.svg?style=flat-square" alt="license"></a>
15
- <a href="https://discord.gg/general-liquidity"><img src="https://img.shields.io/discord/XXXXXXXXX?style=flat-square&logo=discord&logoColor=white&label=discord" alt="Discord"></a>
4
+ The Frontier Trading Agent
16
5
  </p>
17
6
 
18
7
  <p align="center">
19
- <a href="#installation">Installation</a> •
20
- <a href="#quick-start">Quick Start</a> •
21
- <a href="#how-it-works">How It Works</a> •
22
- <a href="#safety">Safety</a>
23
- <a href="https://docs.gordon.trade">Docs</a>
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>
24
12
  </p>
25
13
 
26
- ---
27
-
28
- ## What is Gordon?
14
+ ## Install
29
15
 
30
- Gordon is **Claude Code for vibe trading**. Describe what you want to trade in plain English, and Gordon handles the technical analysis, risk management, and order execution—with your approval at every step.
16
+ `npm`:
31
17
 
18
+ ```bash
19
+ npm install -g @general-liquidity/gordon-cli
32
20
  ```
33
- You: I think ETH is gonna bounce here
34
21
 
35
- Gordon: ETH/USDT @ $3,421. RSI oversold at 28, price 1.2% above the
36
- $3,380 support (tested 4x). Looks like a textbook bounce setup.
22
+ If global npm install fails with `EACCES` / permission errors on Linux or macOS, use the user-local npm path instead:
37
23
 
38
- Entry: $3,400 (limit, near support)
39
- Stop: $3,290 (3.2% below entry)
40
- TP1: $3,580 (50% position, +5.3%)
41
- TP2: $3,720 (50% position, +9.4%)
42
-
43
- Risk/Reward: 1:2.8 — Want me to create this plan?
24
+ ```bash
25
+ npx @general-liquidity/gordon-cli@latest install
44
26
  ```
45
27
 
46
- No dashboards. No charts. No complexity. Just vibes. 📈
28
+ That installs Gordon into a user-writable bin directory without `sudo`.
47
29
 
48
- ---
49
-
50
- ## Installation
51
-
52
- **npm** (recommended)
53
- ```bash
54
- npm install -g @general-liquidity/gordon-cli
55
- ```
56
- Downloads the matching prebuilt binary for your platform during install.
30
+ `bun`:
57
31
 
58
- **bun**
59
32
  ```bash
60
33
  bun add -g @general-liquidity/gordon-cli
61
34
  ```
62
35
 
63
- **curl** (standalone binary)
64
- ```bash
65
- curl -fsSL https://raw.githubusercontent.com/general-liquidity/gordon-cli/main/scripts/install.sh | sh
66
- ```
36
+ `Homebrew`:
67
37
 
68
- **from source**
69
38
  ```bash
70
- git clone https://github.com/general-liquidity/gordon-cli.git
71
- cd gordon && bun install && bun run build
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
72
41
  ```
73
42
 
74
- Then run:
75
- ```bash
76
- gordon
77
- ```
43
+ `Scoop`:
78
44
 
79
- ---
80
-
81
- ## Quick Start
82
-
83
- ### 1. First Run
84
-
85
- Gordon walks you through setup on first launch—API keys, preferences, safety modes.
45
+ ```powershell
46
+ scoop bucket add gordon https://github.com/general-liquidity/gordon-cli-dist
47
+ scoop install gordon/gordon
48
+ ```
86
49
 
87
- ### 2. Set Environment Variables
50
+ Standalone install script:
88
51
 
89
52
  ```bash
90
- # LLM Provider (pick one)
91
- export OPENAI_API_KEY="sk-..."
92
- export DEDALUS_API_KEY="dd-..."
93
-
94
- # Exchange (Binance for now)
95
- export BINANCE_API_KEY="..."
96
- export BINANCE_API_SECRET="..."
53
+ curl -fsSL https://raw.githubusercontent.com/general-liquidity/gordon-cli-dist/main/install.sh | sh
97
54
  ```
98
55
 
99
- ### 3. Start Talking
56
+ Windows PowerShell:
100
57
 
101
- ```bash
102
- gordon
58
+ ```powershell
59
+ irm https://raw.githubusercontent.com/general-liquidity/gordon-cli-dist/main/install.ps1 | iex
103
60
  ```
104
61
 
105
- That's it. No config files needed. Just talk.
62
+ The npm package is a thin wrapper. It downloads the matching prebuilt binary for your platform during install.
106
63
 
107
- ---
64
+ ## npm Permission Fallback
108
65
 
109
- ## How It Works
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:
110
67
 
111
- ```
112
- ┌─────────────────────────────────────────────────────────────────┐
113
- │ YOU │
114
- │ "buy BTC near support" │
115
- └─────────────────────────────────────────────────────────────────┘
116
-
117
-
118
- ┌─────────────────────────────────────────────────────────────────┐
119
- │ 🛡️ MIDDLEWARE LAYER
120
- │ Input Guardrails │ Access Control │ Rate Limiting │
121
- └─────────────────────────────────────────────────────────────────┘
122
-
123
-
124
- ┌─────────────────────────────────────────────────────────────────┐
125
- │ 📈 GORDON (Mastra Agent Network)
126
- │ │
127
- │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
128
- │ │ Scanner │ │ Analyst │ │ Planner │ │ Executor │ │
129
- │ │ ──────── │ │ ──────── │ │ ──────── │ │ ──────── │ │
130
- │ │ Find │ │ Deep │ │ Create │ │ Execute │ │
131
- │ │ setups │ │ analysis │ │ plans │ │ orders │ │
132
- │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
133
- │ │
134
- │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
135
- │ │ Monitor │ │ Teacher │ │Backtester│ │ Gordon │ │
136
- │ │ ──────── │ │ ──────── │ │ ──────── │ │ ──────── │ │
137
- │ │ Track │ │ Explain │ │ Test │ │ Route & │ │
138
- │ │ positions│ │ concepts │ │ strategy │ │ orchestrate│ │
139
- │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
140
- │ │
141
- │ Handoff Validation │ Fallback Chains │ Error Recovery │
142
- └─────────────────────────────────────────────────────────────────┘
143
-
144
-
145
- ┌─────────────────────────────────────────────────────────────────┐
146
- │ 🔧 TOOLS LAYER (29 modules)
147
- │ │
148
- │ Trading: create_plan, execute_plan, close_trade, grid_plan │
149
- │ Analysis: indicators, orderbook, market_analysis, charts │
150
- │ Discovery: scan_market, trending, new_listings, top_movers │
151
- │ Risk: kelly_size, volatility_size, exit_conditions, drawdown │
152
- │ Portfolio: positions, wallet, earn, history, account │
153
- │ Backtest: run_backtest, optimize, monte_carlo, walk_forward │
154
- │ System: arm/disarm, scheduler, explain, shared_context │
155
- └─────────────────────────────────────────────────────────────────┘
156
-
157
-
158
- ┌─────────────────────────────────────────────────────────────────┐
159
- │ 💾 INFRASTRUCTURE LAYER
160
- │ │
161
- │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
162
- │ │ Binance │ │ LLM │ │ SQLite │ │ LibSQL │ │
163
- │ │ REST+WS │ │ Providers │ │ Storage │ │ Vector │ │
164
- │ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │
165
- │ │ │ │ │ │
166
- │ Orders & OpenAI Plans & Semantic │
167
- │ Market Data Anthropic Trades Memory │
168
- │ Real-time Google Events RAG │
169
- │ WebSocket Dedalus Audit Recall │
170
- └─────────────────────────────────────────────────────────────────┘
171
-
172
-
173
- ┌─────────────────────────────────────────────────────────────────┐
174
- │ TRADE PLAN │
175
- │ ┌────────────────────────────────────────────────────────────┐ │
176
- │ │ + BUY 0.15 ETH @ $3,400 (limit) │ │
177
- │ │ + STOP 0.15 ETH @ $3,290 (stop-limit) │ │
178
- │ │ + SELL 0.075 ETH @ $3,580 (TP1) │ │
179
- │ │ + SELL 0.075 ETH @ $3,720 (TP2) │ │
180
- │ └────────────────────────────────────────────────────────────┘ │
181
- │ │
182
- │ [ APPROVE ] [ MODIFY ] [ REJECT ] │
183
- └─────────────────────────────────────────────────────────────────┘
184
-
185
- ▼ (only if you approve + ARM)
186
- ┌─────────────────────────────────────────────────────────────────┐
187
- │ EXECUTION │
188
- │ Orders placed on Binance. Gordon monitors. │
189
- │ Auto-disarms after 24h. You stay in control. │
190
- └─────────────────────────────────────────────────────────────────┘
68
+ ```bash
69
+ npx @general-liquidity/gordon-cli@latest install
191
70
  ```
192
71
 
193
- ---
72
+ If the chosen install directory is not already on `PATH`, Gordon prints the exact command to add it.
194
73
 
195
- ## Safety
74
+ ## Upgrades
196
75
 
197
- Gordon is paranoid about your money. Here's how:
76
+ Once installed, Gordon can upgrade itself with:
198
77
 
199
- | Protection | Description |
200
- |------------|-------------|
201
- | 🔒 **SAFE Mode Default** | Gordon starts in SAFE mode. Can analyze, cannot trade. |
202
- | ⏰ **24h Auto-Disarm** | ARMED mode expires automatically. No forgotten bots. |
203
- | ✋ **Human Approval** | Every order requires explicit "yes". No exceptions. |
204
- | 📊 **Risk Disclosure** | See exact $ at risk before every trade. |
205
- | 🛡️ **Position Limits** | Configurable max allocation per trade (default 10%). |
206
- | 💰 **Cash Reserve** | Always keeps 20% cash. Never goes all-in. |
207
-
208
- ```
209
- SAFE MODE (default) ARMED MODE (you enable)
210
- ───────────────────── ─────────────────────────
211
- ✓ Scan markets ✓ Everything in SAFE, plus:
212
- ✓ Analyze coins ✓ Execute approved plans
213
- ✓ Create plans ✓ Place real orders
214
- ✓ Explain concepts ✓ Monitor positions
215
- ✗ Execute trades ⏰ Auto-expires in 24h
78
+ ```bash
79
+ gordon --upgrade
216
80
  ```
217
81
 
218
- ---
82
+ That now resolves through the active install channel for npm, the user-local `npx` installer, Homebrew, Scoop, and the standalone install scripts.
219
83
 
220
- ## Everything We Built
221
-
222
- ~200 TypeScript files implementing a production-grade trading platform.
223
-
224
- ### Core Platform
225
-
226
- - **8-Agent Network** — Mastra-based orchestration: Scanner, Analyst, Planner, Executor, Monitor, Teacher, Backtester, Gordon (coordinator)
227
- - **Agent Handoffs** — Validated transitions between agents with fallback chains and error recovery
228
- - **Cross-Agent Memory** — Shared context allowing agents to pass analysis, plans, and backtest results
229
- - **Semantic Memory** — LibSQL Vector for RAG-based recall of past trades and analyses
230
- - **LLM Client** — Multi-provider support (OpenAI, Anthropic Claude, Google Gemini, Dedalus Labs)
231
-
232
- ### Security & Middleware
233
-
234
- - **Input Guardrails** — Prompt injection detection, dangerous command blocking
235
- - **Output Sanitization** — Sensitive data filtering in responses
236
- - **Access Control** — ARMED mode enforcement for trading tools
237
- - **Rate Limiting** — Per-agent, per-tool rate limits to prevent abuse
238
- - **Audit Logging** — Comprehensive trail of all sensitive operations
239
-
240
- ### Observability
241
-
242
- - **OpenTelemetry Tracing** — Distributed tracing for agent calls and tool execution
243
- - **Metrics Collection** — Tool invocation counts, success/failure rates, latency tracking
244
- - **Request Monitoring** — Per-request performance metrics and error classification
245
-
246
- ### Trading Engine
247
-
248
- - **Scanner** — Market-wide opportunity detection across top 50 cryptos
249
- - **Analyzer** — Deep technical analysis per coin with multiple timeframes
250
- - **Planner** — AI-powered trade plan generation with entry/SL/TP levels
251
- - **Validator** — Risk checks, position limits, allocation validation
252
- - **Executor** — Order placement with OCO orders and rollback on failure
253
- - **Monitor** — Real-time position tracking via WebSocket, fill detection, alerts
254
- - **Trailing Stops** — Automatic trailing stop-loss management
255
- - **Order Recovery** — Automatic recovery mechanism for failed/interrupted orders
256
- - **Grid Calculator** — DCA and grid entry calculations
257
-
258
- ### Trading Strategies
259
-
260
- - **Tier 1 (Beginner):** Support Bounce, Bollinger Bounce, SMA Crossover, Volume Surge, VWAP Bounce
261
- - **Tier 2 (Intermediate):** Consolidation Pop, ADX Trend, EMA-RSI Crossover, Relative Strength, Engulfing Pattern
262
- - **Strategy Ensemble** — Combine multiple strategies with configurable weights
263
-
264
- ### Backtesting Engine
265
-
266
- - **Historical Simulation** — Full backtesting against historical data
267
- - **Monte Carlo Analysis** — Statistical confidence intervals
268
- - **Walk-Forward Validation** — Out-of-sample testing
269
- - **Grid Search Optimization** — Hyperparameter tuning
270
- - **Alpha Decay Analysis** — Strategy degradation detection
271
- - **Performance Metrics** — Sharpe ratio, max drawdown, profit factor, win rate
272
-
273
- ### Technical Analysis
274
-
275
- - **RSI** — Relative Strength Index (oversold/overbought)
276
- - **MACD** — Momentum and trend direction
277
- - **Bollinger Bands** — Volatility and mean reversion
278
- - **ATR** — Average True Range for stop placement
279
- - **VWAP** — Volume Weighted Average Price
280
- - **Stochastic RSI** — Momentum oscillator
281
- - **EMA/SMA** — Trend following indicators
282
- - **Volume Analysis** — Confirmation signals and whale detection
283
- - **Support/Resistance** — Automatic level detection
284
-
285
- ### Infrastructure
286
-
287
- - **Binance Client** — Full REST API with HMAC signing, rate limiting, circuit breaker
288
- - **Binance WebSocket** — Real-time price feeds and order updates
289
- - **SQLite Storage** — Plans, trades, events, audit logs with WAL mode
290
- - **LibSQL Vector** — Semantic memory and RAG for conversation recall
291
- - **Service Container** — Dependency injection for clean architecture
292
- - **Repository Pattern** — Data access layer for trades and plans
293
- - **Event Bus** — Event-driven architecture for system coordination
294
- - **Caching Layer** — Price and result caching with TTL
295
- - **Resilience** — Retry logic, exponential backoff, circuit breakers, fallback chains
296
-
297
- ### User Experience
298
-
299
- - **Ink CLI** — React-based terminal UI with theme support
300
- - **Real-time Chat** — Streaming responses with agent attribution
301
- - **Onboarding Flow** — First-run setup wizard
302
- - **Model Selector** — Choose your preferred LLM
303
- - **Keyboard Shortcuts** — Power user navigation
304
- - **Command Autocomplete** — Slash command suggestions
305
- - **Status Bar** — Mode, portfolio value, BTC price, connection status
306
-
307
- ---
308
-
309
- ## Configuration
310
-
311
- Gordon stores config at `~/.gordon/config.json`:
312
-
313
- ```json
314
- {
315
- "version": "1.0.0",
316
- "mode": "SAFE",
317
- "preferences": {
318
- "cashReservePercent": 0.2,
319
- "maxAllocationPerTrade": 0.1,
320
- "defaultTimeframes": ["1h", "4h"],
321
- "topNCoins": 50
322
- }
323
- }
324
- ```
84
+ ## Supported binaries
325
85
 
326
- ### Environment Variables
86
+ - macOS arm64
87
+ - macOS x64
88
+ - Linux arm64
89
+ - Linux x64
90
+ - Windows x64
327
91
 
328
- | Variable | Required | Description |
329
- |----------|----------|-------------|
330
- | `OPENAI_API_KEY` | One LLM | OpenAI API key |
331
- | `ANTHROPIC_API_KEY` | provider | Anthropic Claude API key |
332
- | `GOOGLE_GENERATIVE_AI_API_KEY` | required | Google Gemini API key |
333
- | `DEDALUS_API_KEY` | | Dedalus Labs API key (20+ models) |
334
- | `BINANCE_API_KEY` | For trading | Binance API key |
335
- | `BINANCE_API_SECRET` | For trading | Binance secret |
92
+ Release binaries and package manager manifests are published at:
336
93
 
337
- ---
94
+ - `https://github.com/general-liquidity/gordon-cli-dist/releases`
338
95
 
339
- ## Development
96
+ ## Setup
97
+
98
+ Set one LLM provider key before first launch:
340
99
 
341
100
  ```bash
342
- bun install # Install dependencies
343
- bun run dev # Development mode (hot reload)
344
- bun test # Run tests
345
- bun run typecheck # Type check
346
- bun run build # Build for npm
347
- bun run build:binary # Build standalone executable
101
+ export OPENAI_API_KEY="sk-..."
348
102
  ```
349
103
 
350
- ### Project Structure
104
+ or
351
105
 
106
+ ```bash
107
+ export DEDALUS_API_KEY="dd-..."
352
108
  ```
353
- gordon/
354
- ├── src/
355
- │ ├── app/ # Terminal UI (React + Ink)
356
- │ │ ├── App.tsx
357
- │ │ ├── ChatView.tsx
358
- │ │ ├── ChatInput.tsx
359
- │ │ ├── components/ # Reusable UI components
360
- │ │ └── ...
361
- │ ├── core/ # Trading logic
362
- │ │ ├── scanner.ts
363
- │ │ ├── analyzer.ts
364
- │ │ ├── planner.ts
365
- │ │ ├── executor.ts
366
- │ │ ├── monitor.ts
367
- │ │ ├── trailing-stop.ts
368
- │ │ ├── indicators/ # Technical indicators
369
- │ │ └── risk-management/
370
- │ ├── backtest/ # Backtesting engine
371
- │ │ ├── engine.ts
372
- │ │ ├── monte-carlo.ts
373
- │ │ ├── walk-forward.ts
374
- │ │ └── optimization/
375
- │ ├── strategies/ # Trading strategies
376
- │ │ ├── tier-1/ # Beginner strategies
377
- │ │ ├── tier-2/ # Intermediate strategies
378
- │ │ └── ensemble.ts
379
- │ ├── services/ # Business services
380
- │ │ ├── trading.service.ts
381
- │ │ ├── portfolio.service.ts
382
- │ │ └── container.ts # Dependency injection
383
- │ ├── repositories/ # Data access layer
384
- │ ├── infra/ # Infrastructure
385
- │ │ ├── agents/ # Mastra multi-agent system
386
- │ │ │ ├── tools/ # 29 tool modules
387
- │ │ │ └── middleware/ # Guardrails, access control
388
- │ │ ├── binance/ # Exchange client + WebSocket
389
- │ │ ├── llm/ # LLM providers
390
- │ │ ├── storage/ # SQLite + config
391
- │ │ ├── observability/ # OpenTelemetry tracing + metrics
392
- │ │ ├── audit/ # Audit logging
393
- │ │ └── cache/ # Caching layer
394
- │ ├── events/ # Event-driven architecture
395
- │ └── types/ # TypeScript definitions
396
- ├── prompts/ # LLM prompt templates
397
- ├── scripts/ # Install scripts
398
- └── .github/ # CI/CD workflows
399
- ```
400
-
401
- ---
402
-
403
- ## Roadmap
404
109
 
405
- - [x] Support Bounce strategy
406
- - [x] Binance spot trading
407
- - [x] Multi-provider LLM (OpenAI, Anthropic, Google, Dedalus)
408
- - [x] Plan-as-diff approval UI
409
- - [x] SAFE/ARMED modes with 24h auto-expiry
410
- - [x] Trailing stops
411
- - [x] 10+ trading strategies (tier 1 & tier 2)
412
- - [x] Backtesting engine with Monte Carlo & walk-forward
413
- - [x] Real-time WebSocket monitoring
414
- - [x] Order recovery mechanism
415
- - [x] Audit logging & security guardrails
416
- - [ ] More exchanges (Coinbase, Kraken)
417
- - [ ] Portfolio rebalancing
418
- - [ ] Mobile notifications
419
- - [ ] Paper trading mode
420
-
421
- ---
422
-
423
- ## Contributing
424
-
425
- We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
110
+ or
426
111
 
427
112
  ```bash
428
- # Fork, clone, then:
429
- bun install
430
- bun test
431
- # Make changes, add tests, submit PR
113
+ export INCEPTION_API_KEY="..."
432
114
  ```
433
115
 
434
- ---
435
-
436
- ## License
437
-
438
- MIT © [General Liquidity, Inc.](https://generalliquidity.com)
116
+ Then run:
439
117
 
440
- ---
118
+ ```bash
119
+ gordon
120
+ ```
441
121
 
442
- <p align="center">
443
- <sub>
444
- <em>"The most valuable commodity I know of is information."</em>
445
- <br>
446
- — Gordon Gekko (the other one)
447
- </sub>
448
- </p>
122
+ ## Docs
449
123
 
450
- <p align="center">
451
- <a href="https://github.com/general-liquidity/gordon-cli">GitHub</a> •
452
- <a href="https://www.npmjs.com/package/@general-liquidity/gordon-cli">npm</a> •
453
- <a href="https://discord.gg/general-liquidity">Discord</a> •
454
- <a href="https://docs.gordon.trade">Docs</a>
455
- </p>
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
@@ -4,8 +4,21 @@ const fs = require("fs");
4
4
  const path = require("path");
5
5
  const { spawn } = require("child_process");
6
6
  const { getInstalledBinaryPath } = require("../lib/platform.cjs");
7
+ const { runSelfInstall } = require("../lib/self-install.cjs");
7
8
 
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
+ }
9
22
 
10
23
  let binaryPath;
11
24
  try {
@@ -17,12 +30,12 @@ try {
17
30
 
18
31
  if (!fs.existsSync(binaryPath)) {
19
32
  console.error(
20
- "[gordon] The Gordon binary is missing. Reinstall with `npm install -g @general-liquidity/gordon-cli`."
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."
21
34
  );
22
35
  process.exit(1);
23
36
  }
24
37
 
25
- const child = spawn(binaryPath, process.argv.slice(2), { stdio: "inherit" });
38
+ const child = spawn(binaryPath, args, { stdio: "inherit" });
26
39
 
27
40
  child.on("error", (error) => {
28
41
  console.error(`[gordon] Failed to launch ${path.basename(binaryPath)}: ${error.message}`);
package/lib/platform.cjs CHANGED
@@ -38,9 +38,9 @@ function getInstalledBinaryPath(packageRoot = path.resolve(__dirname, ".."), pla
38
38
  function getDownloadUrl(version, platform = process.platform, arch = process.arch) {
39
39
  const { assetName } = getTarget(platform, arch);
40
40
  const cleanVersion = String(version).replace(/^v/, "");
41
+ const distRepo = process.env.GORDON_BINARY_DIST_REPO || "general-liquidity/gordon-cli-dist";
41
42
  const baseUrl =
42
- process.env.GORDON_BINARY_BASE_URL ||
43
- `https://github.com/general-liquidity/gordon-cli/releases/download/v${cleanVersion}`;
43
+ process.env.GORDON_BINARY_BASE_URL || `https://github.com/${distRepo}/releases/download/v${cleanVersion}`;
44
44
  return `${baseUrl.replace(/\/$/, "")}/${assetName}`;
45
45
  }
46
46
 
@@ -0,0 +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
+ };
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@general-liquidity/gordon-cli",
3
- "version": "0.8.19",
3
+ "version": "0.8.22",
4
4
  "description": "The Frontier Trading Agent",
5
5
  "author": "General Liquidity, Inc.",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/general-liquidity/gordon-cli.git"
9
+ "url": "https://github.com/general-liquidity/gordon-cli-dist.git"
10
10
  },
11
- "homepage": "https://github.com/general-liquidity/gordon-cli#readme",
12
- "bugs": "https://github.com/general-liquidity/gordon-cli/issues",
11
+ "homepage": "https://gordoncli.com",
12
+ "bugs": "https://github.com/general-liquidity/gordon-cli-dist/issues",
13
13
  "keywords": [
14
14
  "trading",
15
15
  "crypto",