@liaisonio/cli 0.1.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.
package/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # Liaison Cloud CLI
2
+
3
+ Official command-line interface for [liaison.cloud](https://liaison.cloud), designed
4
+ to be **scripted and agent-friendly**.
5
+
6
+ - JSON output by default — ready for piping into `jq` or parsing by LLM agents
7
+ - `--output table` for humans, `--output yaml` when you prefer it
8
+ - Credentials from env var (`LIAISON_TOKEN`), config file, or explicit `--token` flag
9
+ - Every command has `-h` / `--help` with examples
10
+ - Non-interactive by default: destructive operations require `--yes`
11
+
12
+ ## Install
13
+
14
+ Pick whichever fits your environment. All paths land at the same versioned binary.
15
+
16
+ ### One-line installer (curl, recommended)
17
+
18
+ ```bash
19
+ curl -fsSL https://github.com/liaisonio/cli/releases/latest/download/install.sh | sh
20
+ ```
21
+
22
+ Auto-detects OS/arch, verifies SHA256, drops the binary in `~/.local/bin` (or
23
+ `/usr/local/bin` with sudo if `~/.local/bin` is not writable). Pin a version with
24
+ `LIAISON_CLI_VERSION=v0.1.0`.
25
+
26
+ ### npx / npm
27
+
28
+ ```bash
29
+ # Run once without installing
30
+ npx @liaisonio/cli edge list
31
+
32
+ # Or install globally
33
+ npm i -g @liaisonio/cli
34
+ liaison edge list
35
+ ```
36
+
37
+ The npm wrapper is a thin Node.js shim that downloads the matching native binary
38
+ from the GitHub release on `postinstall`, verifies its SHA256, and execs it.
39
+
40
+ ### Go install
41
+
42
+ ```bash
43
+ go install github.com/liaisonio/cli/cmd/liaison@latest
44
+ ```
45
+
46
+ Requires Go 1.22+. Best for Go developers who already have `$GOPATH/bin` in their PATH.
47
+
48
+ ### Build from source
49
+
50
+ ```bash
51
+ git clone https://github.com/liaisonio/cli
52
+ cd cli
53
+ make build # ./bin/liaison (current platform)
54
+ make release # ./dist/liaison-* (all 5 platforms + SHA256SUMS)
55
+ ```
56
+
57
+ ### Verify
58
+
59
+ ```bash
60
+ liaison version
61
+ ```
62
+
63
+ ## Authenticate
64
+
65
+ The CLI accepts a JWT bearer token issued by liaison.cloud. For now the slider-captcha
66
+ login flow used by the web UI is not supported headlessly — you need to obtain the
67
+ token out of band:
68
+
69
+ 1. Log in to [liaison.cloud](https://liaison.cloud) in your browser.
70
+ 2. Open DevTools → Application → Local Storage → copy the `authorization` value.
71
+ 3. Persist it:
72
+
73
+ ```bash
74
+ liaison login --token eyJhbGciOi...
75
+ ```
76
+
77
+ This writes `~/.liaison/config.yaml` (mode 0600) and verifies the token against
78
+ `/api/v1/iam/profile_json`.
79
+
80
+ Alternatively, skip the config file entirely and pass the token per-invocation:
81
+
82
+ ```bash
83
+ LIAISON_TOKEN=eyJhbGciOi... liaison edge list
84
+ # or
85
+ liaison --token eyJhbGciOi... edge list
86
+ ```
87
+
88
+ Precedence (highest wins): `--token` flag → `LIAISON_TOKEN` env → config file → built-in default.
89
+
90
+ ## Usage
91
+
92
+ ```bash
93
+ liaison whoami # who am I logged in as?
94
+
95
+ # Connectors (edges)
96
+ liaison edge list
97
+ liaison edge list --online 1 # only online connectors
98
+ liaison edge list --output table
99
+ liaison edge get 100017
100
+ liaison edge create --name lab-server --description "office lab"
101
+ liaison edge update 100017 --status stopped # disable + kick
102
+ liaison edge update 100017 --status running # re-enable
103
+ liaison edge delete 100017 --yes
104
+
105
+ # Backend applications (IP:port exposed by a connector)
106
+ liaison application list
107
+ liaison application create --name my-ssh --protocol ssh --ip 192.168.1.10 --port 22 --edge-id 100017
108
+ liaison application update 123 --port 2222
109
+ liaison application delete 123 --yes
110
+
111
+ # Entries (public proxies)
112
+ liaison proxy list
113
+ liaison proxy create --name my-ssh-entry --protocol ssh --application-id 123
114
+ liaison proxy update 456 --status stopped
115
+ liaison proxy delete 456 --yes
116
+
117
+ # Devices
118
+ liaison device list
119
+ liaison device get 789
120
+ ```
121
+
122
+ ## Global flags
123
+
124
+ | Flag | Env | Description |
125
+ |---------------|------------------|-------------------------------------------------------|
126
+ | `--server` | `LIAISON_SERVER` | Liaison base URL (default `https://liaison.cloud`) |
127
+ | `--token` | `LIAISON_TOKEN` | JWT bearer token |
128
+ | `--config` | | Config file path (default `~/.liaison/config.yaml`) |
129
+ | `--output,-o` | | `json` (default), `yaml`, or `table` |
130
+ | `--insecure` | | Skip TLS verification (self-signed testing only) |
131
+ | `--verbose,-v`| | Print each HTTP request to stderr |
132
+
133
+ ## Output formats
134
+
135
+ ```bash
136
+ liaison edge list # pretty JSON (default)
137
+ liaison edge list -o yaml # YAML
138
+ liaison edge list -o table # aligned text table
139
+ liaison edge get 100017 | jq .name # pipe into jq
140
+ ```
141
+
142
+ ## Exit codes
143
+
144
+ - `0` — success
145
+ - `1` — any error (auth, network, API error, invalid args)
146
+
147
+ Error messages go to stderr; output goes to stdout — safe to redirect.
148
+
149
+ ## Agent guidance
150
+
151
+ If you are an LLM agent, here's the minimal path:
152
+
153
+ **Install** (pick the first one that works in your environment):
154
+
155
+ ```bash
156
+ # Option 1: npx — no install, works in any node-equipped sandbox
157
+ npx -y @liaisonio/cli@latest <command>
158
+
159
+ # Option 2: curl one-liner — works on any unix shell with curl
160
+ curl -fsSL https://github.com/liaisonio/cli/releases/latest/download/install.sh | sh
161
+
162
+ # Option 3: go install — if Go toolchain is already present
163
+ go install github.com/liaisonio/cli/cmd/liaison@latest
164
+ ```
165
+
166
+ **Authenticate**:
167
+
168
+ ```bash
169
+ # Best: ask the user to put a Personal Access Token in your secrets store as LIAISON_TOKEN.
170
+ # Then every command works without any login flow:
171
+ LIAISON_TOKEN=liaison_pat_... liaison whoami
172
+ ```
173
+
174
+ **Use**:
175
+
176
+ 1. Always parse stdout as JSON (it's the default output format).
177
+ 2. Discover commands with `liaison --help` and `liaison <resource> --help`. Every flag
178
+ has a description and examples.
179
+ 3. Never omit `--yes` for `delete` actions — the CLI refuses to proceed without it.
180
+ 4. Don't retry on exit code 1 — read the error message on stderr first.
181
+ 5. Errors go to stderr; data goes to stdout. Safe to redirect them separately.
182
+
183
+ ## License
184
+
185
+ Apache 2.0
package/bin/liaison.js ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ //
3
+ // Entry point listed in package.json "bin". This file is what `npx
4
+ // @liaisonio/cli ...` and the symlinked `liaison` command both call.
5
+ //
6
+ // All it does is locate the platform-specific Go binary that was downloaded
7
+ // during `postinstall` (see scripts/install.js) and exec it, forwarding
8
+ // argv, stdio, and the exit code.
9
+ //
10
+ // We deliberately do NOT use require('child_process').execFile or .exec here
11
+ // because they buffer stdout/stderr — the CLI prints progress lines for
12
+ // `liaison login`, and we want the user to see them in real time.
13
+
14
+ 'use strict';
15
+
16
+ const { spawnSync } = require('child_process');
17
+ const path = require('path');
18
+ const fs = require('fs');
19
+
20
+ const PLATFORM_BINARY = {
21
+ 'darwin-arm64': 'liaison',
22
+ 'darwin-x64': 'liaison',
23
+ 'linux-arm64': 'liaison',
24
+ 'linux-x64': 'liaison',
25
+ 'win32-x64': 'liaison.exe',
26
+ };
27
+
28
+ function fail(msg, code) {
29
+ process.stderr.write(`liaison-cli: ${msg}\n`);
30
+ process.exit(code || 1);
31
+ }
32
+
33
+ const key = `${process.platform}-${process.arch}`;
34
+ const binaryName = PLATFORM_BINARY[key];
35
+ if (!binaryName) {
36
+ fail(
37
+ `unsupported platform ${key}. Supported: ${Object.keys(PLATFORM_BINARY).join(', ')}`,
38
+ );
39
+ }
40
+
41
+ const binaryPath = path.join(__dirname, '..', 'vendor', binaryName);
42
+ if (!fs.existsSync(binaryPath)) {
43
+ fail(
44
+ `binary not found at ${binaryPath}.\n` +
45
+ 'This usually means the postinstall download was skipped or failed.\n' +
46
+ 'Try: npm rebuild @liaisonio/cli (or `npm install` again with --foreground-scripts)',
47
+ );
48
+ }
49
+
50
+ const result = spawnSync(binaryPath, process.argv.slice(2), {
51
+ stdio: 'inherit',
52
+ windowsHide: true,
53
+ });
54
+
55
+ if (result.error) {
56
+ fail(`failed to run binary: ${result.error.message}`);
57
+ }
58
+ process.exit(result.status === null ? 1 : result.status);
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@liaisonio/cli",
3
+ "version": "0.1.0",
4
+ "description": "Liaison Cloud CLI — manage connectors, entries, and applications from the command line",
5
+ "keywords": ["liaison", "liaison-cloud", "cli", "edge", "tunnel", "agent"],
6
+ "homepage": "https://liaison.cloud",
7
+ "license": "Apache-2.0",
8
+ "author": "Liaison Cloud",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/liaisonio/cli.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/liaisonio/cli/issues"
15
+ },
16
+ "bin": {
17
+ "liaison": "bin/liaison.js"
18
+ },
19
+ "files": [
20
+ "bin/",
21
+ "scripts/",
22
+ "README.md"
23
+ ],
24
+ "scripts": {
25
+ "postinstall": "node scripts/install.js"
26
+ },
27
+ "engines": {
28
+ "node": ">=14"
29
+ }
30
+ }
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+ //
3
+ // Postinstall hook for @liaisonio/cli.
4
+ //
5
+ // Downloads the platform-specific Go binary from the matching GitHub release
6
+ // and verifies its SHA256 against the published SHA256SUMS file. The binary is
7
+ // dropped at vendor/<liaison|liaison.exe> next to this script's parent.
8
+ //
9
+ // Skipped automatically when:
10
+ // - LIAISON_CLI_SKIP_DOWNLOAD=1 (dev / CI scenarios that don't need the bin)
11
+ // - The user is on an unsupported platform (we exit 0 + warn, NOT fail,
12
+ // so npm install doesn't break for transitive deps)
13
+ //
14
+ // Network failures DO fail the install — silently shipping a broken package
15
+ // is worse than a clear error the user can retry.
16
+
17
+ 'use strict';
18
+
19
+ const https = require('https');
20
+ const fs = require('fs');
21
+ const path = require('path');
22
+ const crypto = require('crypto');
23
+
24
+ const pkg = require('../package.json');
25
+ const VERSION = `v${pkg.version}`;
26
+ const REPO = 'liaisonio/cli';
27
+ const RELEASE_BASE = `https://github.com/${REPO}/releases/download/${VERSION}`;
28
+
29
+ // process.platform-process.arch → release asset GOOS-GOARCH suffix.
30
+ const PLATFORMS = {
31
+ 'darwin-arm64': { os: 'darwin', arch: 'arm64', ext: '' },
32
+ 'darwin-x64': { os: 'darwin', arch: 'amd64', ext: '' },
33
+ 'linux-arm64': { os: 'linux', arch: 'arm64', ext: '' },
34
+ 'linux-x64': { os: 'linux', arch: 'amd64', ext: '' },
35
+ 'win32-x64': { os: 'windows', arch: 'amd64', ext: '.exe' },
36
+ };
37
+
38
+ function log(msg) {
39
+ process.stdout.write(`liaison-cli: ${msg}\n`);
40
+ }
41
+
42
+ function warn(msg) {
43
+ process.stderr.write(`liaison-cli: ${msg}\n`);
44
+ }
45
+
46
+ function die(msg) {
47
+ warn(msg);
48
+ process.exit(1);
49
+ }
50
+
51
+ if (process.env.LIAISON_CLI_SKIP_DOWNLOAD === '1') {
52
+ log('LIAISON_CLI_SKIP_DOWNLOAD=1, skipping binary download');
53
+ process.exit(0);
54
+ }
55
+
56
+ const key = `${process.platform}-${process.arch}`;
57
+ const platform = PLATFORMS[key];
58
+ if (!platform) {
59
+ warn(
60
+ `unsupported platform ${key}; package installed without a binary. ` +
61
+ `Use --ignore-scripts or set LIAISON_CLI_SKIP_DOWNLOAD=1 to silence.`,
62
+ );
63
+ // Exit 0 so transitive dependents don't break.
64
+ process.exit(0);
65
+ }
66
+
67
+ const filename = `liaison-${VERSION}-${platform.os}-${platform.arch}${platform.ext}`;
68
+ const url = `${RELEASE_BASE}/${filename}`;
69
+ const sumsUrl = `${RELEASE_BASE}/SHA256SUMS`;
70
+
71
+ const vendorDir = path.join(__dirname, '..', 'vendor');
72
+ const destPath = path.join(vendorDir, `liaison${platform.ext}`);
73
+
74
+ fs.mkdirSync(vendorDir, { recursive: true });
75
+
76
+ // Followed-redirect HTTP GET that streams to a file.
77
+ function downloadToFile(targetUrl, outPath) {
78
+ return new Promise((resolve, reject) => {
79
+ const file = fs.createWriteStream(outPath);
80
+ function get(u, depth) {
81
+ if (depth > 5) {
82
+ reject(new Error(`too many redirects fetching ${targetUrl}`));
83
+ return;
84
+ }
85
+ https
86
+ .get(u, { headers: { 'User-Agent': 'liaison-cli-installer' } }, (res) => {
87
+ if (
88
+ res.statusCode &&
89
+ res.statusCode >= 300 &&
90
+ res.statusCode < 400 &&
91
+ res.headers.location
92
+ ) {
93
+ res.resume();
94
+ get(res.headers.location, depth + 1);
95
+ return;
96
+ }
97
+ if (res.statusCode !== 200) {
98
+ reject(new Error(`HTTP ${res.statusCode} for ${u}`));
99
+ res.resume();
100
+ return;
101
+ }
102
+ res.pipe(file);
103
+ file.on('finish', () => file.close(() => resolve()));
104
+ file.on('error', reject);
105
+ })
106
+ .on('error', reject);
107
+ }
108
+ get(targetUrl, 0);
109
+ });
110
+ }
111
+
112
+ function downloadToString(targetUrl) {
113
+ return new Promise((resolve, reject) => {
114
+ function get(u, depth) {
115
+ if (depth > 5) {
116
+ reject(new Error(`too many redirects fetching ${targetUrl}`));
117
+ return;
118
+ }
119
+ https
120
+ .get(u, { headers: { 'User-Agent': 'liaison-cli-installer' } }, (res) => {
121
+ if (
122
+ res.statusCode &&
123
+ res.statusCode >= 300 &&
124
+ res.statusCode < 400 &&
125
+ res.headers.location
126
+ ) {
127
+ res.resume();
128
+ get(res.headers.location, depth + 1);
129
+ return;
130
+ }
131
+ if (res.statusCode !== 200) {
132
+ reject(new Error(`HTTP ${res.statusCode} for ${u}`));
133
+ res.resume();
134
+ return;
135
+ }
136
+ let body = '';
137
+ res.setEncoding('utf8');
138
+ res.on('data', (chunk) => (body += chunk));
139
+ res.on('end', () => resolve(body));
140
+ })
141
+ .on('error', reject);
142
+ }
143
+ get(targetUrl, 0);
144
+ });
145
+ }
146
+
147
+ function sha256(filepath) {
148
+ const hash = crypto.createHash('sha256');
149
+ hash.update(fs.readFileSync(filepath));
150
+ return hash.digest('hex');
151
+ }
152
+
153
+ async function main() {
154
+ log(`fetching ${url}`);
155
+ await downloadToFile(url, destPath);
156
+ fs.chmodSync(destPath, 0o755);
157
+
158
+ log('verifying SHA256');
159
+ const sums = await downloadToString(sumsUrl);
160
+ const line = sums
161
+ .split('\n')
162
+ .map((l) => l.trim())
163
+ .find((l) => l.endsWith(filename));
164
+ if (!line) {
165
+ fs.unlinkSync(destPath);
166
+ die(`no SHA256 entry for ${filename} in SHA256SUMS — release may be corrupt`);
167
+ }
168
+ const expected = line.split(/\s+/)[0];
169
+ const actual = sha256(destPath);
170
+ if (expected !== actual) {
171
+ fs.unlinkSync(destPath);
172
+ die(`SHA256 mismatch for ${filename}: expected ${expected}, got ${actual}`);
173
+ }
174
+ log(`installed ${filename} (sha256 ok)`);
175
+ }
176
+
177
+ main().catch((err) => {
178
+ die(`download failed: ${err.message}`);
179
+ });