nexusapp-cli 1.0.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.
Files changed (48) hide show
  1. package/bin/nexus +2 -0
  2. package/dist/client.d.ts +6 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +63 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/commands/auth.d.ts +3 -0
  7. package/dist/commands/auth.d.ts.map +1 -0
  8. package/dist/commands/auth.js +159 -0
  9. package/dist/commands/auth.js.map +1 -0
  10. package/dist/commands/deploy.d.ts +3 -0
  11. package/dist/commands/deploy.d.ts.map +1 -0
  12. package/dist/commands/deploy.js +594 -0
  13. package/dist/commands/deploy.js.map +1 -0
  14. package/dist/commands/domain.d.ts +3 -0
  15. package/dist/commands/domain.d.ts.map +1 -0
  16. package/dist/commands/domain.js +174 -0
  17. package/dist/commands/domain.js.map +1 -0
  18. package/dist/commands/project.d.ts +3 -0
  19. package/dist/commands/project.d.ts.map +1 -0
  20. package/dist/commands/project.js +92 -0
  21. package/dist/commands/project.js.map +1 -0
  22. package/dist/commands/secret.d.ts +3 -0
  23. package/dist/commands/secret.d.ts.map +1 -0
  24. package/dist/commands/secret.js +121 -0
  25. package/dist/commands/secret.js.map +1 -0
  26. package/dist/config.d.ts +10 -0
  27. package/dist/config.d.ts.map +1 -0
  28. package/dist/config.js +53 -0
  29. package/dist/config.js.map +1 -0
  30. package/dist/index.d.ts +3 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +24 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/output.d.ts +9 -0
  35. package/dist/output.d.ts.map +1 -0
  36. package/dist/output.js +71 -0
  37. package/dist/output.js.map +1 -0
  38. package/package.json +29 -0
  39. package/src/client.ts +68 -0
  40. package/src/commands/auth.ts +166 -0
  41. package/src/commands/deploy.ts +534 -0
  42. package/src/commands/domain.ts +167 -0
  43. package/src/commands/project.ts +81 -0
  44. package/src/commands/secret.ts +117 -0
  45. package/src/config.ts +56 -0
  46. package/src/index.ts +25 -0
  47. package/src/output.ts +65 -0
  48. package/tsconfig.json +19 -0
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "nexusapp-cli",
3
+ "version": "1.0.0",
4
+ "description": "NEXUS AI command-line interface",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "nexus": "./bin/nexus"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "ts-node src/index.ts",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "keywords": ["nexusai", "cli", "deployments", "cloud"],
15
+ "license": "MIT",
16
+ "dependencies": {
17
+ "axios": "^1.6.0",
18
+ "chalk": "^5.3.0",
19
+ "cli-table3": "^0.6.3",
20
+ "commander": "^12.0.0",
21
+ "inquirer": "^9.2.0",
22
+ "ora": "^8.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@types/inquirer": "^9.0.7",
26
+ "@types/node": "^20.0.0",
27
+ "typescript": "^5.0.0"
28
+ }
29
+ }
package/src/client.ts ADDED
@@ -0,0 +1,68 @@
1
+ import axios, { AxiosInstance, AxiosError } from 'axios';
2
+ import { getConfig } from './config.js';
3
+
4
+ function createClient(): AxiosInstance {
5
+ const config = getConfig();
6
+ const baseURL = config.apiUrl || 'https://api.nexusai.run';
7
+
8
+ const instance = axios.create({
9
+ baseURL,
10
+ timeout: 30000,
11
+ });
12
+
13
+ instance.interceptors.request.use((req) => {
14
+ const cfg = getConfig();
15
+ if (cfg.token) {
16
+ req.headers = req.headers || {};
17
+ req.headers['Authorization'] = `Bearer ${cfg.token}`;
18
+ }
19
+ return req;
20
+ });
21
+
22
+ instance.interceptors.response.use(
23
+ (res) => res,
24
+ (error: AxiosError) => {
25
+ if (!error.response) {
26
+ const url = baseURL;
27
+ console.error(`Cannot reach NEXUS AI API at ${url}.`);
28
+ process.exit(1);
29
+ }
30
+
31
+ const status = error.response.status;
32
+ const data = error.response.data as any;
33
+
34
+ if (status === 401) {
35
+ console.error("Session expired. Run 'nexus auth login'");
36
+ process.exit(1);
37
+ }
38
+
39
+ if (status === 403) {
40
+ console.error(data?.message || data?.error || 'Access denied.');
41
+ process.exit(1);
42
+ }
43
+
44
+ return Promise.reject(error);
45
+ }
46
+ );
47
+
48
+ return instance;
49
+ }
50
+
51
+ export const client = createClient();
52
+
53
+ /** Unwrap { success, data } envelope if present, otherwise return as-is. */
54
+ export function unwrap(responseData: any): any {
55
+ if (responseData && typeof responseData === 'object' && 'data' in responseData) {
56
+ return responseData.data;
57
+ }
58
+ return responseData;
59
+ }
60
+
61
+ export function apiError(error: unknown): string {
62
+ if (axios.isAxiosError(error)) {
63
+ const data = error.response?.data as any;
64
+ return data?.message || data?.error || error.message;
65
+ }
66
+ if (error instanceof Error) return error.message;
67
+ return String(error);
68
+ }
@@ -0,0 +1,166 @@
1
+ import { Command } from 'commander';
2
+ import http from 'http';
3
+ import { execFile } from 'child_process';
4
+ import { AddressInfo } from 'net';
5
+ import axios from 'axios';
6
+ import { getConfig, saveConfig, clearConfig } from '../config.js';
7
+ import { client, apiError, unwrap } from '../client.js';
8
+ import { success, errorMsg, printJson, printTable, spinner } from '../output.js';
9
+
10
+ function openBrowser(url: string): void {
11
+ const platform = process.platform;
12
+ if (platform === 'darwin') {
13
+ execFile('open', [url], () => {});
14
+ } else if (platform === 'win32') {
15
+ execFile('cmd', ['/c', 'start', '', url], () => {});
16
+ } else {
17
+ execFile('xdg-open', [url], () => {});
18
+ }
19
+ }
20
+
21
+ function waitForCallback(server: http.Server): Promise<{ token: string; tokenId: string; email: string }> {
22
+ return new Promise((resolve, reject) => {
23
+ const timeout = setTimeout(() => {
24
+ server.close();
25
+ reject(new Error('Timed out waiting for browser authorization (2 minutes).'));
26
+ }, 120_000);
27
+
28
+ server.on('request', (req, res) => {
29
+ const url = new URL(req.url!, `http://localhost`);
30
+ const token = url.searchParams.get('token');
31
+ const tokenId = url.searchParams.get('tokenId') || '';
32
+ const email = url.searchParams.get('email') || '';
33
+
34
+ res.writeHead(200, { 'Content-Type': 'text/html' });
35
+ res.end(`<!DOCTYPE html><html><head><title>NEXUS AI CLI</title>
36
+ <style>body{font-family:sans-serif;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#030712;color:#e5e7eb}
37
+ .box{text-align:center}.icon{font-size:3rem;margin-bottom:1rem}.title{font-size:1.25rem;font-weight:600;color:#fff;margin-bottom:.5rem}
38
+ .sub{color:#6b7280;font-size:.9rem}</style></head>
39
+ <body><div class="box"><div class="icon">✓</div><div class="title">Authorization successful</div>
40
+ <div class="sub">You can close this tab and return to your terminal.</div></div></body></html>`);
41
+
42
+ clearTimeout(timeout);
43
+ server.close();
44
+
45
+ if (!token) {
46
+ reject(new Error('No token received from browser.'));
47
+ } else {
48
+ resolve({ token, tokenId, email });
49
+ }
50
+ });
51
+ });
52
+ }
53
+
54
+ export function registerAuth(program: Command): void {
55
+ const auth = program.command('auth').description('Authentication commands');
56
+
57
+ // login
58
+ auth
59
+ .command('login')
60
+ .description('Log in to NEXUS AI via browser')
61
+ .option('--api-url <url>', 'Backend API base URL (e.g. http://localhost:3001)')
62
+ .option('--web-url <url>', 'Frontend URL (e.g. http://localhost:3002)')
63
+ .option('--token <token>', 'Use an existing nxk_* access token directly')
64
+ .action(async (opts) => {
65
+ const apiUrl = opts.apiUrl || process.env.NEXUSAI_API_URL || 'https://api.nexusai.run';
66
+ const webUrl = opts.webUrl || process.env.NEXUSAI_WEB_URL
67
+ || apiUrl.replace(':3001', ':3002').replace('api.nexusai.run', 'app.nexusai.run');
68
+
69
+ // --token shortcut: skip browser
70
+ if (opts.token) {
71
+ try {
72
+ const res = await axios.get(`${apiUrl}/api/auth/verify`, {
73
+ headers: { Authorization: `Bearer ${opts.token}` },
74
+ });
75
+ const user = res.data;
76
+ saveConfig({ apiUrl, token: opts.token, tokenId: '' });
77
+ success(`Logged in as ${user.email || user.user?.email}`);
78
+ } catch (err) {
79
+ errorMsg('Token verification failed: ' + apiError(err));
80
+ process.exit(1);
81
+ }
82
+ return;
83
+ }
84
+
85
+ // Start local callback server on a random port
86
+ const server = http.createServer();
87
+ await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
88
+ const port = (server.address() as AddressInfo).port;
89
+
90
+ const callbackUrl = `http://localhost:${port}`;
91
+ const authUrl = `${webUrl}/cli-auth?callback=${encodeURIComponent(callbackUrl)}`;
92
+
93
+ console.log('');
94
+ console.log('Opening your browser to complete login...');
95
+ console.log('');
96
+ console.log(` ${authUrl}`);
97
+ console.log('');
98
+ console.log('If the browser did not open, copy the URL above into your browser.');
99
+ console.log('');
100
+
101
+ openBrowser(authUrl);
102
+
103
+ const spin = spinner('Waiting for browser authorization...');
104
+
105
+ try {
106
+ const { token, tokenId, email } = await waitForCallback(server);
107
+ saveConfig({ apiUrl, token, tokenId });
108
+ spin.stop();
109
+ success(`Logged in as ${email}`);
110
+ } catch (err) {
111
+ spin.fail(err instanceof Error ? err.message : String(err));
112
+ process.exit(1);
113
+ }
114
+ });
115
+
116
+ // logout
117
+ auth
118
+ .command('logout')
119
+ .description('Log out and revoke access token')
120
+ .action(async () => {
121
+ const config = getConfig();
122
+ if (!config.token) {
123
+ console.log('Not logged in.');
124
+ return;
125
+ }
126
+
127
+ if (config.tokenId) {
128
+ try {
129
+ await client.post(`/api/tokens/${config.tokenId}/revoke`);
130
+ } catch {
131
+ // best-effort revocation
132
+ }
133
+ }
134
+
135
+ clearConfig();
136
+ success('Logged out.');
137
+ });
138
+
139
+ // whoami
140
+ auth
141
+ .command('whoami')
142
+ .description('Show current authenticated user')
143
+ .option('--json', 'Output raw JSON')
144
+ .action(async (opts) => {
145
+ try {
146
+ const res = await client.get('/api/auth/verify');
147
+ const user = unwrap(res.data);
148
+ if (opts.json) {
149
+ printJson(user);
150
+ return;
151
+ }
152
+ const u = user.user || user;
153
+ const email = u.email || 'unknown';
154
+ const org = u.organization?.name || u.organizationId || '—';
155
+ const role = u.role || '—';
156
+ printTable(['Field', 'Value'], [
157
+ ['Email', email],
158
+ ['Organization', org],
159
+ ['Role', role],
160
+ ]);
161
+ } catch (err) {
162
+ errorMsg(apiError(err));
163
+ process.exit(1);
164
+ }
165
+ });
166
+ }