@zeyos/cli 0.4.0 → 0.5.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 CHANGED
@@ -47,6 +47,9 @@ zeyos whoami --json
47
47
  ```
48
48
 
49
49
  `whoami` does not print access tokens by default. Use `zeyos whoami --show-token --json` only when you intentionally need to pass a token to another local tool.
50
+ If a stored refresh token has expired, interactive `whoami` offers to re-authenticate
51
+ and then retries the user lookup. In `--json`/`--yaml` or non-interactive runs, it
52
+ prints a diagnostic plus the matching `zeyos login --force` command instead.
50
53
 
51
54
  Inspect the CLI-supported resource registry:
52
55
 
package/bin/zeyos.mjs CHANGED
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * Commands:
8
8
  * login Authenticate with ZeyOS
9
- * logout Revoke session and clear tokens
9
+ * logout Revoke session and clear stored credentials
10
10
  * whoami Show current user info
11
11
  * list <resource> List records
12
12
  * count <resource> Count records
@@ -38,7 +38,7 @@ Usage: ${_z} <command> [options] [args…]
38
38
 
39
39
  ${_c.bold('Commands:')}
40
40
  ${_c.cyan('login')} Authenticate with a ZeyOS instance
41
- ${_c.cyan('logout')} Revoke session and clear stored tokens
41
+ ${_c.cyan('logout')} Revoke session and clear stored credentials
42
42
  ${_c.cyan('whoami')} Show currently authenticated user
43
43
  ${_c.cyan('list')} <resource> List / query records
44
44
  ${_c.cyan('count')} <resource> Count records (with optional filter)
@@ -49,6 +49,10 @@ export async function run(values) {
49
49
  const profileName = values.profile || null;
50
50
  const scope = values.global ? 'global' : 'local';
51
51
  const port = values.port ? Number(values.port) : DEFAULT_CALLBACK_PORT;
52
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
53
+ error('--port must be an integer between 1 and 65535.');
54
+ process.exit(1);
55
+ }
52
56
  const redirectUri = callbackUri(port);
53
57
 
54
58
  // Persist either into a named profile or the legacy local/global credential file.
@@ -1,22 +1,28 @@
1
1
  /**
2
2
  * zeyos logout
3
3
  *
4
- * Revokes the current access token (best-effort) and removes stored tokens
5
- * from the credential file. Connection params (baseUrl, clientId, clientSecret)
6
- * are kept so a subsequent `zeyos login` works without re-entering them.
4
+ * Revokes the current access token (best-effort) and clears the selected
5
+ * stored session. Local legacy credentials are cleared completely so a
6
+ * subsequent `zeyos login` starts from fresh connection parameters.
7
7
  *
8
8
  * Options:
9
9
  * --global Clear the global credentials file
10
10
  */
11
11
 
12
12
  import { createZeyosClient, MemoryTokenStore } from '@zeyos/client';
13
- import { loadConfigWithSource, clearTokens, clearTokensForSource } from '../lib/config.mjs';
14
- import { success, warn, info } from '../lib/output.mjs';
13
+ import {
14
+ loadConfigWithSource,
15
+ loadGlobalConfig,
16
+ clearTokensForSource,
17
+ clearLocalCredentialsForSource,
18
+ listProfiles
19
+ } from '../lib/config.mjs';
20
+ import { success, warn, info, error } from '../lib/output.mjs';
15
21
 
16
22
  export const USAGE = `\
17
23
  Usage: zeyos logout [options]
18
24
 
19
- Revoke the current session and clear stored tokens.
25
+ Revoke the current session and clear stored credentials.
20
26
 
21
27
  Options:
22
28
  --profile <name> Log out of a specific profile
@@ -25,9 +31,29 @@ Options:
25
31
  `;
26
32
 
27
33
  export async function run(values) {
28
- const { config, source } = loadConfigWithSource({ profile: values.profile });
34
+ let config;
35
+ let source;
36
+
37
+ if (values.global) {
38
+ config = loadGlobalConfig();
39
+ source = { kind: 'global' };
40
+ } else {
41
+ const loaded = loadConfigWithSource({ profile: values.profile });
42
+ if (loaded.profile?.missing) {
43
+ const names = Object.keys(listProfiles().profiles);
44
+ const known = names.length ? `Known profiles: ${names.join(', ')}.` : 'No profiles defined yet.';
45
+ error(`Profile "${loaded.profile.name}" not found (selected via ${loaded.profile.origin}). ${known}`);
46
+ process.exit(1);
47
+ }
48
+ config = loaded.config;
49
+ source = loaded.source;
50
+ }
29
51
 
30
52
  if (!config.accessToken) {
53
+ if (source?.kind === 'local' && clearLocalCredentialsForSource(source)) {
54
+ success('Logged out (local credentials).');
55
+ return;
56
+ }
31
57
  warn('Not currently logged in.');
32
58
  return;
33
59
  }
@@ -58,8 +84,8 @@ export async function run(values) {
58
84
  }
59
85
  }
60
86
 
61
- if (values.global) {
62
- clearTokens('global');
87
+ if (source?.kind === 'local') {
88
+ clearLocalCredentialsForSource(source);
63
89
  } else {
64
90
  clearTokensForSource(source);
65
91
  }
@@ -14,6 +14,7 @@
14
14
  * the credentials currently in effect (including tokens) into the new profile.
15
15
  */
16
16
 
17
+ import { createInterface } from 'node:readline';
17
18
  import {
18
19
  listProfiles, getProfile, upsertProfile, removeProfile,
19
20
  setActiveProfile, writeLocalPin, readLocalPin,
@@ -31,7 +32,7 @@ Commands:
31
32
  current Show which profile is in effect, and why
32
33
  use <name> Make <name> the active profile (global)
33
34
  use <name> --local Pin <name> to the current project (.zeyos/profile)
34
- add <name> [options] Create or update a profile
35
+ add [<name>] [options] Create or update a profile; prompts when run without connection options
35
36
  remove <name> Delete a profile
36
37
 
37
38
  Add options:
@@ -45,6 +46,7 @@ Global options:
45
46
  -h, --help Show this help
46
47
 
47
48
  Examples:
49
+ zeyos profile add # prompt for name and connection params
48
50
  zeyos profile add dev --base-url https://zeyos.cms-it.de/dev
49
51
  zeyos profile add prod --from-current
50
52
  zeyos profile use prod
@@ -147,30 +149,42 @@ function cmdUse(values, name) {
147
149
 
148
150
  // ── add ────────────────────────────────────────────────────────────────────────
149
151
 
150
- function cmdAdd(values, name) {
151
- if (!name) fail('Usage: zeyos profile add <name> [--base-url <url>] [--client-id <id>] [--secret <secret>] | --from-current');
152
+ async function cmdAdd(values, name) {
153
+ let promptSession = null;
154
+ const ask = (question, opts) => {
155
+ promptSession ??= createPromptSession();
156
+ return promptSession.ask(question, opts);
157
+ };
152
158
 
153
- let updates = {};
154
- if (values['from-current']) {
155
- const cfg = loadConfigWithSource().config; // whatever is in effect right now
156
- for (const k of ['baseUrl', 'instance', 'clientId', 'clientSecret', 'accessToken', 'refreshToken', 'expiresAt', 'refreshTokenExpiresAt']) {
157
- if (cfg[k] != null) updates[k] = cfg[k];
158
- }
159
- if (!updates.baseUrl) fail('Nothing to snapshot: no credentials are currently in effect.');
160
- } else {
161
- if (values['base-url']) updates.baseUrl = values['base-url'];
162
- if (values['client-id']) updates.clientId = values['client-id'];
163
- if (values.secret) updates.clientSecret = values.secret;
164
- if (Object.keys(updates).length === 0) {
165
- fail('Provide at least --base-url (and ideally --client-id/--secret), or use --from-current.');
159
+ try {
160
+ const profileName = name || await ask('Profile name');
161
+ if (!profileName) fail('Profile name is required.');
162
+
163
+ let updates = {};
164
+ if (values['from-current']) {
165
+ const cfg = loadConfigWithSource().config; // whatever is in effect right now
166
+ for (const k of ['baseUrl', 'instance', 'clientId', 'clientSecret', 'accessToken', 'refreshToken', 'expiresAt', 'refreshTokenExpiresAt']) {
167
+ if (cfg[k] != null) updates[k] = cfg[k];
168
+ }
169
+ if (!updates.baseUrl) fail('Nothing to snapshot: no credentials are currently in effect.');
170
+ } else {
171
+ if (values['base-url']) updates.baseUrl = values['base-url'];
172
+ if (values['client-id']) updates.clientId = values['client-id'];
173
+ if (values.secret) updates.clientSecret = values.secret;
174
+
175
+ if (Object.keys(updates).length === 0) {
176
+ updates = await promptProfileCredentials(profileName, ask);
177
+ }
166
178
  }
167
- }
168
179
 
169
- const existed = Boolean(getProfile(name));
170
- upsertProfile(name, updates);
171
- success(`${existed ? 'Updated' : 'Created'} profile "${name}".`);
172
- if (!updates.accessToken) {
173
- info(`Finish authenticating with: zeyos login --profile ${name}`);
180
+ const existed = Boolean(getProfile(profileName));
181
+ upsertProfile(profileName, updates);
182
+ success(`${existed ? 'Updated' : 'Created'} profile "${profileName}".`);
183
+ if (!updates.accessToken) {
184
+ info(`Finish authenticating with: zeyos login --profile ${profileName}`);
185
+ }
186
+ } finally {
187
+ promptSession?.close();
174
188
  }
175
189
  }
176
190
 
@@ -209,3 +223,84 @@ function fail(message) {
209
223
  error(message);
210
224
  process.exit(1);
211
225
  }
226
+
227
+ async function promptProfileCredentials(name, ask) {
228
+ const existing = getProfile(name) || {};
229
+
230
+ info(`Creating profile "${name}".`);
231
+ info('This stores the platform and OAuth app credentials; tokens are added by login.');
232
+ console.error('');
233
+
234
+ const baseUrl = await ask('ZeyOS platform URL', { currentValue: existing.baseUrl });
235
+ const clientId = await ask('Application ID', { currentValue: existing.clientId });
236
+ const clientSecret = await ask('Application secret', { currentValue: existing.clientSecret, secret: true });
237
+
238
+ if (!baseUrl || !clientId || !clientSecret) {
239
+ fail('ZeyOS URL, application ID and secret are all required.');
240
+ }
241
+
242
+ return { baseUrl, clientId, clientSecret };
243
+ }
244
+
245
+ function createPromptSession() {
246
+ const rl = createInterface({ input: process.stdin, output: process.stderr, terminal: process.stdin.isTTY && process.stderr.isTTY });
247
+ const originalWrite = rl._writeToOutput.bind(rl);
248
+ let hiddenPrompt = null;
249
+ let closed = false;
250
+ const queuedLines = [];
251
+ const waitingResolvers = [];
252
+
253
+ rl.on('line', (line) => {
254
+ const resolve = waitingResolvers.shift();
255
+ if (resolve) {
256
+ resolve(line);
257
+ } else {
258
+ queuedLines.push(line);
259
+ }
260
+ });
261
+
262
+ rl.on('close', () => {
263
+ closed = true;
264
+ let resolve;
265
+ while ((resolve = waitingResolvers.shift())) {
266
+ resolve('');
267
+ }
268
+ });
269
+
270
+ rl._writeToOutput = (value) => {
271
+ if (!hiddenPrompt || String(value).includes(hiddenPrompt) || value === '\n' || value === '\r\n') {
272
+ originalWrite(value);
273
+ }
274
+ };
275
+
276
+ const readLine = () => {
277
+ if (queuedLines.length) {
278
+ return Promise.resolve(queuedLines.shift());
279
+ }
280
+ if (closed) {
281
+ return Promise.resolve('');
282
+ }
283
+ return new Promise(resolve => {
284
+ waitingResolvers.push(resolve);
285
+ });
286
+ };
287
+
288
+ return {
289
+ ask(question, opts = {}) {
290
+ const currentValue = opts.currentValue || '';
291
+ const defaultLabel = opts.secret && currentValue ? 'stored, press Enter to keep' : currentValue;
292
+ const prompt = defaultLabel ? `${question} [${defaultLabel}]` : question;
293
+ hiddenPrompt = opts.secret && process.stdin.isTTY && process.stderr.isTTY ? prompt : null;
294
+ process.stderr.write(`${prompt}: `);
295
+
296
+ return readLine()
297
+ .then(answer => {
298
+ hiddenPrompt = null;
299
+ return answer.trim() || currentValue || '';
300
+ });
301
+ },
302
+ close() {
303
+ rl.close();
304
+ }
305
+ };
306
+ }
@@ -9,8 +9,11 @@
9
9
  * --show-token Include the current access token in output
10
10
  */
11
11
 
12
+ import { createInterface } from 'node:readline';
12
13
  import { buildClient, syncTokens } from '../lib/client.mjs';
14
+ import { globalConfigPath, profilesConfigPath } from '../lib/config.mjs';
13
15
  import { outputMode, printJson, printYaml, printRecord, formatDate, error } from '../lib/output.mjs';
16
+ import { run as runLogin } from './login.mjs';
14
17
 
15
18
  export const USAGE = `\
16
19
  Usage: zeyos whoami [options]
@@ -25,35 +28,23 @@ Options:
25
28
  `;
26
29
 
27
30
  export async function run(values) {
28
- let client, config, tokenStore, configSource;
29
- try {
30
- ({ client, config, tokenStore, configSource } = buildClient({}, { profile: values.profile }));
31
- } catch (err) {
32
- error(err.message);
33
- process.exit(1);
34
- }
31
+ let state = _buildClientState(values);
35
32
 
36
33
  let userInfo;
37
34
  try {
38
- userInfo = await client.oauth2.getUserInfo();
39
- await syncTokens(tokenStore, configSource);
35
+ userInfo = await _fetchUserInfo(state);
40
36
  } catch (err) {
41
- const status = err?.status;
42
- if (status === 502 || status === 503 || status === 504) {
43
- error(`ZeyOS instance is temporarily unavailable (HTTP ${status}). The server at ${config.baseUrl} may be down or restarting — this is server-side, not your credentials.`);
44
- } else if (status === 401) {
45
- error('Your session has expired or is invalid. Re-authenticate with: zeyos login --force');
46
- } else {
47
- error(`Failed to fetch user info: ${err.message}`);
48
- }
49
- process.exit(1);
37
+ const handled = await _handleFetchError(err, state, values);
38
+ if (!handled) process.exit(1);
39
+ state = handled.state;
40
+ userInfo = handled.userInfo;
50
41
  }
51
42
 
52
43
  const mode = outputMode(values);
53
44
 
54
45
  const output = { ...userInfo };
55
46
  if (values['show-token']) {
56
- const tokenSet = await tokenStore.get();
47
+ const tokenSet = await state.tokenStore.get();
57
48
  if (tokenSet?.accessToken) output.accessToken = tokenSet.accessToken;
58
49
  }
59
50
 
@@ -63,7 +54,7 @@ export async function run(values) {
63
54
  printYaml(output);
64
55
  } else {
65
56
  // Pretty key-value record with custom formatters
66
- const dateFormat = config.dateFormat ?? 'YYYY-MM-DD HH:mm';
57
+ const dateFormat = state.config.dateFormat ?? 'YYYY-MM-DD HH:mm';
67
58
  const keys = Object.keys(output);
68
59
  const formatters = {};
69
60
 
@@ -86,6 +77,56 @@ export async function run(values) {
86
77
  }
87
78
  }
88
79
 
80
+ function _buildClientState(values) {
81
+ try {
82
+ const state = buildClient({}, { profile: values.profile });
83
+ return {
84
+ client: state.client,
85
+ config: state.config,
86
+ tokenStore: state.tokenStore,
87
+ configSource: state.configSource
88
+ };
89
+ } catch (err) {
90
+ error(err.message);
91
+ process.exit(1);
92
+ }
93
+ }
94
+
95
+ async function _fetchUserInfo(state) {
96
+ const userInfo = await state.client.oauth2.getUserInfo();
97
+ await syncTokens(state.tokenStore, state.configSource);
98
+ return userInfo;
99
+ }
100
+
101
+ async function _handleFetchError(err, state, values) {
102
+ const status = err?.status;
103
+ if (status === 502 || status === 503 || status === 504) {
104
+ error(`ZeyOS instance is temporarily unavailable (HTTP ${status}). The server at ${state.config.baseUrl} may be down or restarting — this is server-side, not your credentials.`);
105
+ return null;
106
+ }
107
+
108
+ const authFailure = _authFailureSummary(err);
109
+ if (!authFailure) {
110
+ error(`Failed to fetch user info: ${err.message}`);
111
+ return null;
112
+ }
113
+
114
+ error(_formatAuthFailure(authFailure, err, state.config, state.configSource, values));
115
+ const reauthenticated = await _maybeReauthenticate(state.configSource, values);
116
+ if (!reauthenticated) return null;
117
+
118
+ const nextState = _buildClientState(values);
119
+ try {
120
+ return {
121
+ state: nextState,
122
+ userInfo: await _fetchUserInfo(nextState)
123
+ };
124
+ } catch (retryErr) {
125
+ error(`Re-authentication completed, but fetching user info still failed: ${retryErr.message}`);
126
+ return null;
127
+ }
128
+ }
129
+
89
130
  /**
90
131
  * Format an array of objects as a multi-line list.
91
132
  * Each item is shown as "name (rw)" or "name (ro)" on its own line.
@@ -105,3 +146,145 @@ function _formatObjectList(items, nameKey, writableKey) {
105
146
  })
106
147
  .join('\n');
107
148
  }
149
+
150
+ function _isInvalidRefreshTokenError(err) {
151
+ const detail = `${err?.message ?? ''}\n${_stringifyErrorBody(err?.body)}`;
152
+ return [400, 401, 403].includes(err?.status) &&
153
+ /refresh[_ -]?token|invalid_grant/i.test(detail) &&
154
+ /invalid|expired|forbidden|invalid_grant/i.test(detail);
155
+ }
156
+
157
+ function _authFailureSummary(err) {
158
+ if (_isInvalidRefreshTokenError(err)) {
159
+ return 'Your stored refresh token is invalid or expired.';
160
+ }
161
+ if (err?.status === 401) {
162
+ return 'Your session has expired or is invalid.';
163
+ }
164
+ return null;
165
+ }
166
+
167
+ function _formatAuthFailure(summary, err, config, source, values) {
168
+ const lines = [
169
+ summary,
170
+ `Platform URL: ${config.baseUrl ?? '(not configured)'}`,
171
+ `Credential source: ${_describeConfigSource(source)}`
172
+ ];
173
+
174
+ if (err?.url) {
175
+ lines.push(`OAuth endpoint: ${err.url}`);
176
+ }
177
+ if (err?.status) {
178
+ lines.push(`HTTP status: ${err.status}${err.statusText ? ` ${err.statusText}` : ''}`);
179
+ }
180
+
181
+ const detail = _authErrorDetail(err);
182
+ if (detail) {
183
+ lines.push(`OAuth error: ${detail}`);
184
+ }
185
+
186
+ lines.push(`Next step: ${_loginCommand(source, values)}`);
187
+ if (process.env.ZEYOS_TOKEN || process.env.ZEYOS_REFRESH_TOKEN) {
188
+ lines.push('Note: ZEYOS_TOKEN or ZEYOS_REFRESH_TOKEN is set and overrides stored credentials; update or unset it before retrying.');
189
+ }
190
+ return lines.join('\n');
191
+ }
192
+
193
+ function _describeConfigSource(source) {
194
+ if (!source) {
195
+ return 'environment variables';
196
+ }
197
+ if (source.kind === 'profile') {
198
+ return `profile "${source.name}" (${profilesConfigPath()})`;
199
+ }
200
+ if (source.kind === 'global') {
201
+ return `global credentials (${globalConfigPath()})`;
202
+ }
203
+ if (source.kind === 'local') {
204
+ return `local file ${source.path ?? '.zeyos/auth.json'}`;
205
+ }
206
+ return source.kind ?? 'unknown';
207
+ }
208
+
209
+ function _loginCommand(source, values) {
210
+ const profile = values.profile ?? (source?.kind === 'profile' ? source.name : null);
211
+ if (profile) {
212
+ return `zeyos login --profile ${_quoteArg(profile)} --force`;
213
+ }
214
+ if (source?.kind === 'global') {
215
+ return 'zeyos login --global --force';
216
+ }
217
+ return 'zeyos login --force';
218
+ }
219
+
220
+ async function _maybeReauthenticate(source, values) {
221
+ if (!_canPromptForReauthentication(source, values)) {
222
+ return false;
223
+ }
224
+
225
+ const command = _loginCommand(source, values);
226
+ const confirmed = await _confirm(`Re-authenticate now (${command})? [y/N] `);
227
+ if (!confirmed) return false;
228
+
229
+ await runLogin(_loginValues(source, values));
230
+ return true;
231
+ }
232
+
233
+ function _canPromptForReauthentication(source, values) {
234
+ return Boolean(source) &&
235
+ !values.json &&
236
+ !values.yaml &&
237
+ process.stdin.isTTY &&
238
+ process.stderr.isTTY &&
239
+ !process.env.ZEYOS_TOKEN &&
240
+ !process.env.ZEYOS_REFRESH_TOKEN;
241
+ }
242
+
243
+ function _loginValues(source, values) {
244
+ return {
245
+ ...values,
246
+ force: true,
247
+ profile: values.profile ?? (source?.kind === 'profile' ? source.name : undefined),
248
+ global: source?.kind === 'global' ? true : values.global
249
+ };
250
+ }
251
+
252
+ function _confirm(prompt) {
253
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
254
+ return new Promise(resolve => {
255
+ rl.question(prompt, answer => {
256
+ rl.close();
257
+ resolve(answer.trim().toLowerCase() === 'y');
258
+ });
259
+ });
260
+ }
261
+
262
+ function _authErrorDetail(err) {
263
+ const body = _stringifyErrorBody(err?.body).trim();
264
+ if (body) {
265
+ return body;
266
+ }
267
+ return String(err?.message ?? '').trim();
268
+ }
269
+
270
+ function _stringifyErrorBody(body) {
271
+ if (body == null) {
272
+ return '';
273
+ }
274
+ if (typeof body === 'string') {
275
+ return body;
276
+ }
277
+ try {
278
+ return JSON.stringify(body);
279
+ } catch {
280
+ return String(body);
281
+ }
282
+ }
283
+
284
+ function _quoteArg(value) {
285
+ const text = String(value);
286
+ if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(text)) {
287
+ return text;
288
+ }
289
+ return `'${text.replaceAll("'", "'\\''")}'`;
290
+ }
package/lib/config.mjs CHANGED
@@ -187,6 +187,21 @@ export function clearTokens(scope = 'local') {
187
187
  if (path) _writeJson(path, _stripTokens(_readJson(path)));
188
188
  }
189
189
 
190
+ /** Remove all credential/session fields from the resolved legacy local auth file. */
191
+ export function clearLocalCredentialsForSource(source) {
192
+ if (!source || source.kind !== 'local') return false;
193
+ const path = source.path ?? _findLocalPath();
194
+ if (!path) return false;
195
+
196
+ const current = _readJson(path);
197
+ const hadCredentials = CRED_KEYS.some((key) => Object.prototype.hasOwnProperty.call(current, key));
198
+ if (!hadCredentials) return false;
199
+
200
+ const next = _stripCredentials(current);
201
+ _writeJson(path, next);
202
+ return true;
203
+ }
204
+
190
205
  /**
191
206
  * Persist refreshed tokens back to wherever the active credentials came from.
192
207
  * @param {ConfigSource|null} source
@@ -325,6 +340,11 @@ export function localConfigPath() { return _findLocalPath(); }
325
340
  export function globalConfigPath() { return GLOBAL_FILE; }
326
341
  export function profilesConfigPath() { return PROFILES_FILE; }
327
342
 
343
+ /** Read the legacy global credentials file directly, without applying the cascade. */
344
+ export function loadGlobalConfig() {
345
+ return _readGlobal();
346
+ }
347
+
328
348
  // ── Internals ────────────────────────────────────────────────────────────────
329
349
 
330
350
  function _fromEnv() {
@@ -368,6 +388,14 @@ function _stripTokens(o) {
368
388
  return rest;
369
389
  }
370
390
 
391
+ function _stripCredentials(o) {
392
+ const out = { ...o };
393
+ for (const key of CRED_KEYS) {
394
+ delete out[key];
395
+ }
396
+ return out;
397
+ }
398
+
371
399
  function _readGlobal() {
372
400
  return existsSync(GLOBAL_FILE) ? _readJson(GLOBAL_FILE) : {};
373
401
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeyos/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Command-line interface for the ZeyOS API",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -39,7 +39,7 @@
39
39
  "node": ">=18.3"
40
40
  },
41
41
  "dependencies": {
42
- "@zeyos/client": "^0.4.0"
42
+ "@zeyos/client": "^0.5.0"
43
43
  },
44
44
  "scripts": {
45
45
  "test": "node --test test/offline.mjs"