@xano/cli 1.0.3-beta.6 → 1.0.3-beta.7

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
@@ -46,8 +46,19 @@ These warnings are layer 1 of broader push-safety work; ephemeral sandbox enviro
46
46
  xano auth
47
47
  xano auth --origin https://custom.xano.com
48
48
  xano auth --insecure # Skip TLS verification (self-signed certs)
49
+ xano auth --no-browser # Headless login (no local callback server)
49
50
  ```
50
51
 
52
+ The default flow starts a temporary callback server on `127.0.0.1` and waits
53
+ for the browser to redirect back to it. On remote/SSH sessions, Docker
54
+ containers, or locked-down networks where the browser can't reach the CLI's
55
+ loopback address, use `--no-browser`: the CLI prints a login URL, you open it
56
+ in any browser, and paste back the code it displays. No local server required.
57
+
58
+ If you can't run `xano auth` at all, you can always create a profile manually
59
+ with a Metadata API token from the Xano dashboard — see
60
+ [Profiles](#profiles) below.
61
+
51
62
  ### Profiles
52
63
 
53
64
  Profiles store your Xano credentials and default workspace settings.
@@ -5,6 +5,7 @@ export default class Auth extends Command {
5
5
  static flags: {
6
6
  config: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
7
  insecure: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ 'no-browser': import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
9
  origin: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
10
  };
10
11
  run(): Promise<void>;
@@ -12,6 +13,7 @@ export default class Auth extends Command {
12
13
  private fetchBranches;
13
14
  private fetchInstances;
14
15
  private fetchWorkspaces;
16
+ private promptForToken;
15
17
  private promptProfileName;
16
18
  private saveProfile;
17
19
  private selectBranch;
@@ -1,4 +1,3 @@
1
- import { ExitPromptError } from '@inquirer/core';
2
1
  import { Command, Flags } from '@oclif/core';
3
2
  import inquirer from 'inquirer';
4
3
  import * as yaml from 'js-yaml';
@@ -20,6 +19,10 @@ Authenticated as John Doe (john@example.com)
20
19
  Profile 'default' created successfully!`,
21
20
  `$ xano auth --origin https://custom.xano.com
22
21
  Opening browser for Xano login at https://custom.xano.com...`,
22
+ `$ xano auth --no-browser
23
+ To authenticate, open the following URL in any browser:
24
+ https://app.xano.com/login?dest=cli&display=code
25
+ ? Paste the code shown in your browser: ****`,
23
26
  ];
24
27
  static flags = {
25
28
  config: Flags.string({
@@ -33,6 +36,10 @@ Opening browser for Xano login at https://custom.xano.com...`,
33
36
  default: false,
34
37
  description: 'Skip TLS certificate verification (for self-signed certificates)',
35
38
  }),
39
+ 'no-browser': Flags.boolean({
40
+ default: false,
41
+ description: 'Headless login: print a URL and paste back the code shown in the browser, instead of starting a local callback server (use on remote/SSH/Docker hosts where 127.0.0.1 is not reachable from the browser)',
42
+ }),
36
43
  origin: Flags.string({
37
44
  char: 'o',
38
45
  default: 'https://app.xano.com',
@@ -48,7 +55,9 @@ Opening browser for Xano login at https://custom.xano.com...`,
48
55
  try {
49
56
  // Step 1: Get token via browser auth
50
57
  this.log('Starting authentication flow...');
51
- const token = await this.startAuthServer(flags.origin);
58
+ const token = flags['no-browser']
59
+ ? await this.promptForToken(flags.origin)
60
+ : await this.startAuthServer(flags.origin);
52
61
  // Step 2: Validate token and get user info
53
62
  this.log('');
54
63
  this.log('Validating authentication...');
@@ -113,7 +122,10 @@ Opening browser for Xano login at https://custom.xano.com...`,
113
122
  process.exit(0);
114
123
  }
115
124
  catch (error) {
116
- if (error instanceof ExitPromptError) {
125
+ // Ctrl+C at an inquirer prompt throws ExitPromptError. Match on the name
126
+ // rather than `instanceof`: inquirer bundles its own copy of
127
+ // @inquirer/core, so the thrown class won't match an imported one.
128
+ if (error?.name === 'ExitPromptError') {
117
129
  this.log('Authentication cancelled.');
118
130
  return;
119
131
  }
@@ -196,6 +208,34 @@ Opening browser for Xano login at https://custom.xano.com...`,
196
208
  return [];
197
209
  }
198
210
  }
211
+ async promptForToken(origin) {
212
+ // Headless flow: no local callback server. The login page, when opened
213
+ // without a `callback` param, renders the access token on screen for the
214
+ // user to copy. We point the browser there (best-effort) and prompt for
215
+ // the pasted code.
216
+ const authUrl = `${origin}/login?dest=cli&display=code`;
217
+ this.log('To authenticate, open the following URL in any browser:');
218
+ this.log('');
219
+ this.log(` ${authUrl}`);
220
+ this.log('');
221
+ try {
222
+ await open(authUrl);
223
+ }
224
+ catch {
225
+ // Best-effort only; the URL is already printed above for manual use.
226
+ }
227
+ const { token } = await inquirer.prompt([
228
+ {
229
+ message: 'Paste the code shown in your browser',
230
+ name: 'token',
231
+ type: 'password',
232
+ validate(input) {
233
+ return input.trim() === '' ? 'A code is required' : true;
234
+ },
235
+ },
236
+ ]);
237
+ return token.trim();
238
+ }
199
239
  async promptProfileName() {
200
240
  const { profileName } = await inquirer.prompt([
201
241
  {