unbound-cli 0.1.7 → 0.1.9
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/package.json +1 -1
- package/src/commands/login.js +2 -3
- package/src/commands/status.js +8 -11
- package/src/index.js +19 -35
- package/src/update-check.js +79 -0
package/package.json
CHANGED
package/src/commands/login.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const { Option } = require('commander');
|
|
1
2
|
const config = require('../config');
|
|
2
3
|
const output = require('../output');
|
|
3
4
|
const api = require('../api');
|
|
@@ -8,7 +9,7 @@ function register(program) {
|
|
|
8
9
|
.command('login')
|
|
9
10
|
.description('Authenticate with Unbound. Opens a browser for interactive login, or use --api-key for CI/CD environments.')
|
|
10
11
|
.option('--api-key <key>', 'Authenticate with an API key directly (non-interactive)')
|
|
11
|
-
.
|
|
12
|
+
.addOption(new Option('--base-url <url>', 'Set a custom API base URL').hideHelp())
|
|
12
13
|
.option('--domain <domain>', 'Use a custom domain for login (e.g. custom.example.com)')
|
|
13
14
|
.addHelpText('after', `
|
|
14
15
|
Authentication methods:
|
|
@@ -23,13 +24,11 @@ Authentication methods:
|
|
|
23
24
|
|
|
24
25
|
Options:
|
|
25
26
|
--domain sets a custom domain for organizations with self-hosted frontends.
|
|
26
|
-
--base-url sets the backend API URL before authenticating (persisted).
|
|
27
27
|
|
|
28
28
|
Examples:
|
|
29
29
|
$ unbound login # Login via default gateway
|
|
30
30
|
$ unbound login --domain custom.example.com # Login via custom domain
|
|
31
31
|
$ unbound login --api-key sk-abc123 # Non-interactive login
|
|
32
|
-
$ unbound login --base-url http://localhost:8000 # Use local backend
|
|
33
32
|
`)
|
|
34
33
|
.action(async (opts) => {
|
|
35
34
|
try {
|
package/src/commands/status.js
CHANGED
|
@@ -8,13 +8,12 @@ function register(program) {
|
|
|
8
8
|
.description('Show the current CLI status including config location, login state, and API connectivity. Useful for debugging connection issues.')
|
|
9
9
|
.addHelpText('after', `
|
|
10
10
|
Output fields:
|
|
11
|
-
Config file
|
|
12
|
-
Logged in
|
|
13
|
-
Email
|
|
14
|
-
Organization
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
API status - Connectivity check result (Connected / Error)
|
|
11
|
+
Config file - Path to the config file (~/.unbound/config.json)
|
|
12
|
+
Logged in - Whether credentials are stored (Yes/No)
|
|
13
|
+
Email - The authenticated user's email (if logged in)
|
|
14
|
+
Organization - The organization name (if logged in)
|
|
15
|
+
Unbound Gateway - The Unbound gateway URL (if logged in)
|
|
16
|
+
API status - Connectivity check result (Connected / Error)
|
|
18
17
|
|
|
19
18
|
Examples:
|
|
20
19
|
$ unbound status
|
|
@@ -34,8 +33,7 @@ Examples:
|
|
|
34
33
|
if (loggedIn) {
|
|
35
34
|
pairs.push(['Email', cfg.email || '-']);
|
|
36
35
|
pairs.push(['Organization', cfg.org_name || '-']);
|
|
37
|
-
pairs.push(['
|
|
38
|
-
pairs.push(['Frontend URL', config.getFrontendUrl()]);
|
|
36
|
+
pairs.push(['Unbound Gateway', config.getFrontendUrl()]);
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
// Check API connectivity
|
|
@@ -59,8 +57,7 @@ Examples:
|
|
|
59
57
|
logged_in: loggedIn,
|
|
60
58
|
email: loggedIn ? (cfg.email || null) : null,
|
|
61
59
|
organization: loggedIn ? (cfg.org_name || null) : null,
|
|
62
|
-
|
|
63
|
-
frontend_url: loggedIn ? config.getFrontendUrl() : null,
|
|
60
|
+
gateway_url: loggedIn ? config.getFrontendUrl() : null,
|
|
64
61
|
api_status: connectivity,
|
|
65
62
|
});
|
|
66
63
|
return;
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,9 @@ const { Command } = require('commander');
|
|
|
4
4
|
const config = require('./config');
|
|
5
5
|
const output = require('./output');
|
|
6
6
|
const { version } = require('../package.json');
|
|
7
|
+
const { checkForUpdates } = require('./update-check');
|
|
8
|
+
|
|
9
|
+
checkForUpdates();
|
|
7
10
|
|
|
8
11
|
const program = new Command();
|
|
9
12
|
|
|
@@ -67,14 +70,6 @@ TOOL CONNECTIONS
|
|
|
67
70
|
|
|
68
71
|
CONFIGURATION
|
|
69
72
|
$ unbound config show Show all settings
|
|
70
|
-
$ unbound config set-url <url> Set API base URL
|
|
71
|
-
$ unbound config set-frontend-url <url> Set frontend URL
|
|
72
|
-
$ unbound config reset-url Reset API URL to default
|
|
73
|
-
$ unbound config reset-frontend-url Reset frontend URL to default
|
|
74
|
-
|
|
75
|
-
ENVIRONMENT VARIABLES
|
|
76
|
-
UNBOUND_API_URL Override the API base URL (e.g. http://localhost:8000)
|
|
77
|
-
UNBOUND_FRONTEND_URL Override the frontend URL (e.g. http://localhost:3000)
|
|
78
73
|
|
|
79
74
|
FILES
|
|
80
75
|
~/.unbound/config.json Credentials and CLI settings
|
|
@@ -98,31 +93,27 @@ require('./commands/setup').register(program);
|
|
|
98
93
|
// config command for managing CLI settings
|
|
99
94
|
const configCmd = program
|
|
100
95
|
.command('config')
|
|
101
|
-
.description('Manage CLI configuration
|
|
96
|
+
.description('Manage CLI configuration settings.');
|
|
102
97
|
|
|
98
|
+
// Internal/dev commands — hidden from help but still functional
|
|
103
99
|
configCmd
|
|
104
|
-
.command('set-url <url>')
|
|
105
|
-
.description('Set the API base URL
|
|
106
|
-
.addHelpText('after', `
|
|
107
|
-
Examples:
|
|
108
|
-
$ unbound config set-url http://localhost:8000 # Local development
|
|
109
|
-
$ unbound config set-url https://backend.getunbound.ai # Production (default)
|
|
110
|
-
`)
|
|
100
|
+
.command('set-url <url>', { hidden: true })
|
|
101
|
+
.description('Set the API base URL (internal)')
|
|
111
102
|
.action((url) => {
|
|
112
103
|
config.setBaseUrl(url);
|
|
113
104
|
output.success(`API base URL set to ${url}`);
|
|
114
105
|
});
|
|
115
106
|
|
|
116
107
|
configCmd
|
|
117
|
-
.command('get-url')
|
|
118
|
-
.description('Show the current API base URL
|
|
108
|
+
.command('get-url', { hidden: true })
|
|
109
|
+
.description('Show the current API base URL (internal)')
|
|
119
110
|
.action(() => {
|
|
120
111
|
console.log(config.getBaseUrl());
|
|
121
112
|
});
|
|
122
113
|
|
|
123
114
|
configCmd
|
|
124
|
-
.command('reset-url')
|
|
125
|
-
.description('Reset the API base URL to
|
|
115
|
+
.command('reset-url', { hidden: true })
|
|
116
|
+
.description('Reset the API base URL to default (internal)')
|
|
126
117
|
.action(() => {
|
|
127
118
|
const cfg = config.readConfig();
|
|
128
119
|
delete cfg.base_url;
|
|
@@ -131,28 +122,23 @@ configCmd
|
|
|
131
122
|
});
|
|
132
123
|
|
|
133
124
|
configCmd
|
|
134
|
-
.command('set-frontend-url <url>')
|
|
135
|
-
.description('Set the frontend URL
|
|
136
|
-
.addHelpText('after', `
|
|
137
|
-
Examples:
|
|
138
|
-
$ unbound config set-frontend-url http://localhost:3000 # Local development
|
|
139
|
-
$ unbound config set-frontend-url https://gateway.getunbound.ai # Production (default)
|
|
140
|
-
`)
|
|
125
|
+
.command('set-frontend-url <url>', { hidden: true })
|
|
126
|
+
.description('Set the frontend URL (internal)')
|
|
141
127
|
.action((url) => {
|
|
142
128
|
config.setFrontendUrl(url);
|
|
143
129
|
output.success(`Frontend URL set to ${url}`);
|
|
144
130
|
});
|
|
145
131
|
|
|
146
132
|
configCmd
|
|
147
|
-
.command('get-frontend-url')
|
|
148
|
-
.description('Show the current frontend URL
|
|
133
|
+
.command('get-frontend-url', { hidden: true })
|
|
134
|
+
.description('Show the current frontend URL (internal)')
|
|
149
135
|
.action(() => {
|
|
150
136
|
console.log(config.getFrontendUrl());
|
|
151
137
|
});
|
|
152
138
|
|
|
153
139
|
configCmd
|
|
154
|
-
.command('reset-frontend-url')
|
|
155
|
-
.description('Reset the frontend URL to
|
|
140
|
+
.command('reset-frontend-url', { hidden: true })
|
|
141
|
+
.description('Reset the frontend URL to default (internal)')
|
|
156
142
|
.action(() => {
|
|
157
143
|
const cfg = config.readConfig();
|
|
158
144
|
delete cfg.frontend_url;
|
|
@@ -170,8 +156,7 @@ configCmd
|
|
|
170
156
|
if (opts.json) {
|
|
171
157
|
output.json({
|
|
172
158
|
config_file: config.CONFIG_FILE,
|
|
173
|
-
|
|
174
|
-
frontend_url: config.getFrontendUrl(),
|
|
159
|
+
gateway_url: config.getFrontendUrl(),
|
|
175
160
|
logged_in: config.isLoggedIn(),
|
|
176
161
|
email: cfg.email || null,
|
|
177
162
|
organization: cfg.org_name || null,
|
|
@@ -181,8 +166,7 @@ configCmd
|
|
|
181
166
|
|
|
182
167
|
output.keyValue([
|
|
183
168
|
['Config file', config.CONFIG_FILE],
|
|
184
|
-
['
|
|
185
|
-
['Frontend URL', config.getFrontendUrl()],
|
|
169
|
+
['Unbound Gateway', config.getFrontendUrl()],
|
|
186
170
|
['Logged in', config.isLoggedIn() ? 'Yes' : 'No'],
|
|
187
171
|
['Email', cfg.email || '-'],
|
|
188
172
|
['Organization', cfg.org_name || '-'],
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execFile } = require('child_process');
|
|
4
|
+
const { CONFIG_DIR } = require('./config');
|
|
5
|
+
|
|
6
|
+
const CACHE_FILE = path.join(CONFIG_DIR, 'update-check.json');
|
|
7
|
+
const CHECK_INTERVAL = 24 * 60 * 60 * 1000; // 24 hours
|
|
8
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/unbound-cli/latest';
|
|
9
|
+
|
|
10
|
+
function newerThan(latest, current) {
|
|
11
|
+
const a = latest.split('.').map(Number);
|
|
12
|
+
const b = current.split('.').map(Number);
|
|
13
|
+
for (let i = 0; i < 3; i++) {
|
|
14
|
+
if ((a[i] || 0) > (b[i] || 0)) return true;
|
|
15
|
+
if ((a[i] || 0) < (b[i] || 0)) return false;
|
|
16
|
+
}
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function checkForUpdates() {
|
|
21
|
+
if (!process.stderr.isTTY || process.env.UNBOUND_NO_UPDATE_CHECK) return;
|
|
22
|
+
|
|
23
|
+
const { version: current } = require('../package.json');
|
|
24
|
+
let cache = null;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
cache = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
|
|
28
|
+
} catch {
|
|
29
|
+
// No cache or corrupted — will trigger a background check
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// If cached result shows a newer version, schedule notice on exit
|
|
33
|
+
if (cache && cache.latest && newerThan(cache.latest, current)) {
|
|
34
|
+
const useColor = !process.env.NO_COLOR;
|
|
35
|
+
const yellow = (s) => useColor ? `\x1b[33m${s}\x1b[0m` : s;
|
|
36
|
+
process.on('exit', () => {
|
|
37
|
+
console.error('');
|
|
38
|
+
console.error(yellow(`A new version of Unbound CLI is available: ${current} → ${cache.latest}`));
|
|
39
|
+
console.error(yellow(`Update using: npm install -g unbound-cli`));
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// If cache is stale or missing, spawn a background check
|
|
44
|
+
const isStale = !cache || !cache.checkedAt || (Date.now() - cache.checkedAt > CHECK_INTERVAL);
|
|
45
|
+
if (isStale) {
|
|
46
|
+
const configDir = CONFIG_DIR.replace(/\\/g, '\\\\');
|
|
47
|
+
const cacheFile = CACHE_FILE.replace(/\\/g, '\\\\');
|
|
48
|
+
const script = `
|
|
49
|
+
const https = require('https');
|
|
50
|
+
const fs = require('fs');
|
|
51
|
+
https.get('${REGISTRY_URL}', { headers: { 'Accept': 'application/json' }, timeout: 10000 }, (res) => {
|
|
52
|
+
let data = '';
|
|
53
|
+
res.on('data', (c) => data += c);
|
|
54
|
+
res.on('end', () => {
|
|
55
|
+
try {
|
|
56
|
+
const latest = JSON.parse(data).version;
|
|
57
|
+
if (latest) {
|
|
58
|
+
fs.mkdirSync('${configDir}', { recursive: true });
|
|
59
|
+
fs.writeFileSync('${cacheFile}', JSON.stringify({ latest, checkedAt: Date.now() }));
|
|
60
|
+
}
|
|
61
|
+
} catch {}
|
|
62
|
+
});
|
|
63
|
+
}).on('error', () => {});
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const child = execFile(process.execPath, ['-e', script], {
|
|
68
|
+
detached: true,
|
|
69
|
+
stdio: 'ignore',
|
|
70
|
+
windowsHide: true,
|
|
71
|
+
});
|
|
72
|
+
child.unref();
|
|
73
|
+
} catch {
|
|
74
|
+
// Best-effort — silently ignore failures
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = { checkForUpdates };
|