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
@@ -0,0 +1,167 @@
1
+ import { Command } from 'commander';
2
+ import inquirer from 'inquirer';
3
+ import { client, apiError, unwrap } from '../client.js';
4
+ import { printTable, printJson, success, errorMsg, timeAgo } from '../output.js';
5
+ import chalk from 'chalk';
6
+
7
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
8
+
9
+ async function resolveDeployment(nameOrId: string): Promise<any> {
10
+ if (UUID_RE.test(nameOrId)) {
11
+ try {
12
+ const res = await client.get(`/api/deployments/${nameOrId}`);
13
+ return unwrap(res.data);
14
+ } catch { /* fall through */ }
15
+ }
16
+ const listRes = await client.get('/api/deployments');
17
+ const all: any[] = Array.isArray(unwrap(listRes.data)) ? unwrap(listRes.data) : [];
18
+ const match = all.find((d) => d.name === nameOrId || d.displayName === nameOrId);
19
+ if (!match) throw new Error(`Deployment not found: "${nameOrId}"`);
20
+ const res = await client.get(`/api/deployments/${match.id}`);
21
+ return unwrap(res.data);
22
+ }
23
+
24
+ function verificationBadge(status: string): string {
25
+ switch ((status || '').toUpperCase()) {
26
+ case 'VERIFIED': return chalk.green('VERIFIED');
27
+ case 'PENDING': return chalk.yellow('PENDING');
28
+ case 'FAILED': return chalk.red('FAILED');
29
+ default: return chalk.gray(status || 'UNKNOWN');
30
+ }
31
+ }
32
+
33
+ export function registerDomain(program: Command): void {
34
+ const domain = program.command('domain').description('Custom domain management');
35
+
36
+ // list
37
+ domain
38
+ .command('list <deployment>')
39
+ .description('List custom domains for a deployment')
40
+ .option('--json', 'Output raw JSON')
41
+ .action(async (nameOrId, opts) => {
42
+ try {
43
+ const d = await resolveDeployment(nameOrId);
44
+ const res = await client.get(`/api/deployments/${d.id}/domains`);
45
+ const domains: any[] = unwrap(res.data) || [];
46
+
47
+ if (opts.json) { printJson(domains); return; }
48
+ if (!domains.length) {
49
+ console.log(`No custom domains on "${d.displayName || d.name}".`);
50
+ return;
51
+ }
52
+
53
+ printTable(
54
+ ['ID', 'DOMAIN', 'STATUS', 'SSL', 'ADDED'],
55
+ domains.map((dom: any) => [
56
+ dom.id,
57
+ dom.domain,
58
+ verificationBadge(dom.verificationStatus),
59
+ dom.sslStatus ? chalk.green('✓') : chalk.gray('—'),
60
+ dom.createdAt ? timeAgo(dom.createdAt) : '—',
61
+ ])
62
+ );
63
+ } catch (err) {
64
+ errorMsg(apiError(err));
65
+ process.exit(1);
66
+ }
67
+ });
68
+
69
+ // add
70
+ domain
71
+ .command('add <deployment> <domain>')
72
+ .description('Add a custom domain to a deployment')
73
+ .option('--json', 'Output raw JSON')
74
+ .action(async (nameOrId, domainName, opts) => {
75
+ try {
76
+ const d = await resolveDeployment(nameOrId);
77
+ const res = await client.post(`/api/deployments/${d.id}/domains`, { domain: domainName });
78
+ const dom = unwrap(res.data);
79
+
80
+ if (opts.json) { printJson(dom); return; }
81
+
82
+ success(`Domain "${dom.domain}" added to "${d.displayName || d.name}"`);
83
+ console.log('');
84
+ console.log(' Next steps to verify ownership:');
85
+ if (dom.txtRecord || dom.verificationToken) {
86
+ console.log(` 1. Add a DNS TXT record:`);
87
+ console.log(` Name: ${chalk.cyan(dom.txtRecordName || `_nexusai-verify.${domainName}`)}`);
88
+ console.log(` Value: ${chalk.cyan(dom.txtRecord || dom.verificationToken)}`);
89
+ } else {
90
+ console.log(` 1. Point your DNS to the deployment URL`);
91
+ }
92
+ console.log(` 2. Run: ${chalk.bold(`nexus domain verify ${nameOrId} ${dom.id}`)}`);
93
+ } catch (err) {
94
+ errorMsg(apiError(err));
95
+ process.exit(1);
96
+ }
97
+ });
98
+
99
+ // verify
100
+ domain
101
+ .command('verify <deployment> <domain-id>')
102
+ .description('Trigger DNS verification for a custom domain')
103
+ .option('--json', 'Output raw JSON')
104
+ .action(async (nameOrId, domainId, opts) => {
105
+ try {
106
+ const d = await resolveDeployment(nameOrId);
107
+ const res = await client.post(`/api/deployments/${d.id}/domains/${domainId}/verify`);
108
+ const result = unwrap(res.data);
109
+
110
+ if (opts.json) { printJson(result); return; }
111
+
112
+ const dom = result.domain || result;
113
+ const status = (dom.verificationStatus || '').toUpperCase();
114
+
115
+ if (status === 'VERIFIED') {
116
+ success(`Domain "${dom.domain}" verified successfully`);
117
+ if (dom.sslStatus) {
118
+ console.log(` SSL: ${chalk.green('active')}`);
119
+ }
120
+ } else {
121
+ console.log(` Status: ${verificationBadge(status)}`);
122
+ console.log('');
123
+ if (result.verificationResult?.error) {
124
+ console.log(` ${chalk.yellow('!')} ${result.verificationResult.error}`);
125
+ }
126
+ console.log(` DNS changes can take up to 48h to propagate.`);
127
+ console.log(` Run this command again once DNS has updated.`);
128
+ }
129
+ } catch (err) {
130
+ errorMsg(apiError(err));
131
+ process.exit(1);
132
+ }
133
+ });
134
+
135
+ // remove
136
+ domain
137
+ .command('remove <deployment> <domain-id>')
138
+ .description('Remove a custom domain from a deployment')
139
+ .option('--yes', 'Skip confirmation prompt')
140
+ .action(async (nameOrId, domainId, opts) => {
141
+ try {
142
+ const d = await resolveDeployment(nameOrId);
143
+
144
+ if (!opts.yes) {
145
+ // Show the domain name in the prompt
146
+ let domainName = domainId;
147
+ try {
148
+ const listRes = await client.get(`/api/deployments/${d.id}/domains`);
149
+ const domains: any[] = unwrap(listRes.data) || [];
150
+ const found = domains.find((dom) => dom.id === domainId);
151
+ if (found) domainName = found.domain;
152
+ } catch { /* use id */ }
153
+
154
+ const { confirm } = await inquirer.prompt([
155
+ { type: 'confirm', name: 'confirm', message: `Remove domain "${domainName}" from "${d.displayName || d.name}"?`, default: false },
156
+ ]);
157
+ if (!confirm) { console.log('Cancelled.'); return; }
158
+ }
159
+
160
+ await client.delete(`/api/deployments/${d.id}/domains/${domainId}`);
161
+ success(`Domain removed.`);
162
+ } catch (err) {
163
+ errorMsg(apiError(err));
164
+ process.exit(1);
165
+ }
166
+ });
167
+ }
@@ -0,0 +1,81 @@
1
+ import { Command } from 'commander';
2
+ import inquirer from 'inquirer';
3
+ import { client, apiError, unwrap } from '../client.js';
4
+ import { printTable, printJson, success, errorMsg, timeAgo } from '../output.js';
5
+
6
+ export function registerProject(program: Command): void {
7
+ const project = program.command('project').description('Project management commands');
8
+
9
+ // list
10
+ project
11
+ .command('list')
12
+ .description('List projects')
13
+ .option('--json', 'Output raw JSON')
14
+ .action(async (opts) => {
15
+ try {
16
+ const res = await client.get('/api/projects');
17
+ const raw = unwrap(res.data);
18
+ const projects = Array.isArray(raw) ? raw : raw.projects || [];
19
+
20
+ if (opts.json) { printJson(projects); return; }
21
+ if (!projects.length) { console.log('No projects found.'); return; }
22
+
23
+ printTable(
24
+ ['ID', 'NAME', 'CREATED'],
25
+ projects.map((p: any) => [
26
+ p.id,
27
+ p.name,
28
+ p.createdAt ? timeAgo(p.createdAt) : '—',
29
+ ])
30
+ );
31
+ } catch (err) {
32
+ errorMsg(apiError(err));
33
+ process.exit(1);
34
+ }
35
+ });
36
+
37
+ // create
38
+ project
39
+ .command('create')
40
+ .description('Create a new project')
41
+ .requiredOption('--name <name>', 'Project name')
42
+ .option('--json', 'Output raw JSON')
43
+ .action(async (opts) => {
44
+ try {
45
+ const res = await client.post('/api/projects', { name: opts.name });
46
+ const p = unwrap(res.data);
47
+ if (opts.json) { printJson(p); return; }
48
+ success(`Project "${p.name}" created (${p.id})`);
49
+ } catch (err) {
50
+ errorMsg(apiError(err));
51
+ process.exit(1);
52
+ }
53
+ });
54
+
55
+ // delete
56
+ project
57
+ .command('delete <id>')
58
+ .description('Delete a project')
59
+ .option('--yes', 'Skip confirmation prompt')
60
+ .action(async (id, opts) => {
61
+ if (!opts.yes) {
62
+ const { confirm } = await inquirer.prompt([
63
+ {
64
+ type: 'confirm',
65
+ name: 'confirm',
66
+ message: `Delete project "${id}"? All deployments in this project will be removed.`,
67
+ default: false,
68
+ },
69
+ ]);
70
+ if (!confirm) { console.log('Cancelled.'); return; }
71
+ }
72
+
73
+ try {
74
+ await client.delete(`/api/projects/${id}`);
75
+ success(`Project ${id} deleted.`);
76
+ } catch (err) {
77
+ errorMsg(apiError(err));
78
+ process.exit(1);
79
+ }
80
+ });
81
+ }
@@ -0,0 +1,117 @@
1
+ import { Command } from 'commander';
2
+ import inquirer from 'inquirer';
3
+ import { client, apiError, unwrap } from '../client.js';
4
+ import { printTable, printJson, success, errorMsg, timeAgo } from '../output.js';
5
+
6
+ export function registerSecret(program: Command): void {
7
+ const secret = program.command('secret').description('Secret management commands');
8
+
9
+ // list
10
+ secret
11
+ .command('list')
12
+ .description('List secrets')
13
+ .option('--environment <env>', 'Filter by environment')
14
+ .option('--json', 'Output raw JSON')
15
+ .action(async (opts) => {
16
+ try {
17
+ const params: Record<string, string> = {};
18
+ if (opts.environment) params.environment = opts.environment;
19
+
20
+ const res = await client.get('/api/secrets', { params });
21
+ const raw = unwrap(res.data);
22
+ const secrets = Array.isArray(raw) ? raw : raw.secrets || [];
23
+
24
+ if (opts.json) { printJson(secrets); return; }
25
+ if (!secrets.length) { console.log('No secrets found.'); return; }
26
+
27
+ printTable(
28
+ ['ID', 'NAME', 'ENVIRONMENT', 'CREATED'],
29
+ secrets.map((s: any) => [
30
+ s.id,
31
+ s.name,
32
+ s.environment || '—',
33
+ s.createdAt ? timeAgo(s.createdAt) : '—',
34
+ ])
35
+ );
36
+ } catch (err) {
37
+ errorMsg(apiError(err));
38
+ process.exit(1);
39
+ }
40
+ });
41
+
42
+ // create
43
+ secret
44
+ .command('create')
45
+ .description('Create a new secret')
46
+ .requiredOption('--name <name>', 'Secret name')
47
+ .requiredOption('--environment <env>', 'Target environment')
48
+ .option('--value <value>', 'Secret value (prompt if omitted)')
49
+ .action(async (opts) => {
50
+ let value = opts.value;
51
+
52
+ if (!value) {
53
+ const ans = await inquirer.prompt([
54
+ { type: 'password', name: 'value', message: 'Value:', mask: '•' },
55
+ ]);
56
+ value = ans.value;
57
+ }
58
+
59
+ try {
60
+ await client.post('/api/secrets', {
61
+ name: opts.name,
62
+ environment: opts.environment,
63
+ value,
64
+ });
65
+ success(`Secret ${opts.name} created (${opts.environment})`);
66
+ } catch (err) {
67
+ errorMsg(apiError(err));
68
+ process.exit(1);
69
+ }
70
+ });
71
+
72
+ // update
73
+ secret
74
+ .command('update <id>')
75
+ .description('Update a secret value')
76
+ .option('--value <value>', 'New secret value (prompt if omitted)')
77
+ .action(async (id, opts) => {
78
+ let value = opts.value;
79
+
80
+ if (!value) {
81
+ const ans = await inquirer.prompt([
82
+ { type: 'password', name: 'value', message: 'New value:', mask: '•' },
83
+ ]);
84
+ value = ans.value;
85
+ }
86
+
87
+ try {
88
+ await client.put(`/api/secrets/${id}`, { value });
89
+ success(`Secret ${id} updated.`);
90
+ } catch (err) {
91
+ errorMsg(apiError(err));
92
+ process.exit(1);
93
+ }
94
+ });
95
+
96
+ // delete
97
+ secret
98
+ .command('delete <id>')
99
+ .description('Delete a secret')
100
+ .option('--yes', 'Skip confirmation prompt')
101
+ .action(async (id, opts) => {
102
+ if (!opts.yes) {
103
+ const { confirm } = await inquirer.prompt([
104
+ { type: 'confirm', name: 'confirm', message: `Delete secret "${id}"?`, default: false },
105
+ ]);
106
+ if (!confirm) { console.log('Cancelled.'); return; }
107
+ }
108
+
109
+ try {
110
+ await client.delete(`/api/secrets/${id}`);
111
+ success(`Secret ${id} deleted.`);
112
+ } catch (err) {
113
+ errorMsg(apiError(err));
114
+ process.exit(1);
115
+ }
116
+ });
117
+ }
package/src/config.ts ADDED
@@ -0,0 +1,56 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import os from 'os';
4
+
5
+ export interface Config {
6
+ apiUrl: string;
7
+ token: string;
8
+ tokenId: string;
9
+ }
10
+
11
+ const CONFIG_DIR = path.join(os.homedir(), '.nexusai');
12
+ const CONFIG_PATH = path.join(CONFIG_DIR, 'config.json');
13
+
14
+ const DEFAULT_API_URL = 'https://api.nexusai.run';
15
+
16
+ export function getConfig(): Partial<Config> {
17
+ const envToken = process.env.NEXUSAI_TOKEN;
18
+ const envApiUrl = process.env.NEXUSAI_API_URL;
19
+
20
+ let fileConfig: Partial<Config> = {};
21
+ if (fs.existsSync(CONFIG_PATH)) {
22
+ try {
23
+ fileConfig = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
24
+ } catch {
25
+ // ignore malformed config
26
+ }
27
+ }
28
+
29
+ return {
30
+ apiUrl: envApiUrl || fileConfig.apiUrl || DEFAULT_API_URL,
31
+ token: envToken || fileConfig.token || '',
32
+ tokenId: fileConfig.tokenId || '',
33
+ };
34
+ }
35
+
36
+ export function saveConfig(config: Config): void {
37
+ if (!fs.existsSync(CONFIG_DIR)) {
38
+ fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
39
+ }
40
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
41
+ }
42
+
43
+ export function clearConfig(): void {
44
+ if (fs.existsSync(CONFIG_PATH)) {
45
+ fs.unlinkSync(CONFIG_PATH);
46
+ }
47
+ }
48
+
49
+ export function requireToken(): string {
50
+ const config = getConfig();
51
+ if (!config.token) {
52
+ console.error("Not logged in. Run 'nexus auth login' first.");
53
+ process.exit(1);
54
+ }
55
+ return config.token;
56
+ }
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { registerAuth } from './commands/auth.js';
4
+ import { registerDeploy } from './commands/deploy.js';
5
+ import { registerSecret } from './commands/secret.js';
6
+ import { registerProject } from './commands/project.js';
7
+ import { registerDomain } from './commands/domain.js';
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('nexus')
13
+ .description('NEXUS AI command-line interface')
14
+ .version('1.0.0');
15
+
16
+ registerAuth(program);
17
+ registerDeploy(program);
18
+ registerSecret(program);
19
+ registerProject(program);
20
+ registerDomain(program);
21
+
22
+ program.parseAsync(process.argv).catch((err) => {
23
+ console.error(err.message || String(err));
24
+ process.exit(1);
25
+ });
package/src/output.ts ADDED
@@ -0,0 +1,65 @@
1
+ import chalk from 'chalk';
2
+ import Table from 'cli-table3';
3
+ import ora, { Ora } from 'ora';
4
+
5
+ const STATUS_COLORS: Record<string, (s: string) => string> = {
6
+ RUNNING: chalk.green,
7
+ BUILDING: chalk.yellow,
8
+ DEPLOYING: chalk.yellow,
9
+ FAILED: chalk.red,
10
+ STOPPED: chalk.gray,
11
+ PENDING: chalk.blue,
12
+ QUEUED: chalk.blue,
13
+ TERMINATED: chalk.gray,
14
+ };
15
+
16
+ export function statusBadge(status: string): string {
17
+ const colorFn = STATUS_COLORS[status?.toUpperCase()] || chalk.white;
18
+ return colorFn(status || 'UNKNOWN');
19
+ }
20
+
21
+ export function printTable(headers: string[], rows: (string | undefined | null)[][]): void {
22
+ const table = new Table({
23
+ head: headers.map((h) => chalk.bold(h)),
24
+ style: { head: [], border: [] },
25
+ chars: {
26
+ top: '', 'top-mid': '', 'top-left': '', 'top-right': '',
27
+ bottom: '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '',
28
+ left: '', 'left-mid': '', mid: '', 'mid-mid': '',
29
+ right: '', 'right-mid': '', middle: ' ',
30
+ },
31
+ });
32
+
33
+ for (const row of rows) {
34
+ table.push(row.map((cell) => cell ?? chalk.gray('—')));
35
+ }
36
+
37
+ console.log(table.toString());
38
+ }
39
+
40
+ export function printJson(obj: unknown): void {
41
+ console.log(JSON.stringify(obj, null, 2));
42
+ }
43
+
44
+ export function spinner(text: string): Ora {
45
+ return ora(text).start();
46
+ }
47
+
48
+ export function timeAgo(dateStr: string): string {
49
+ const diff = Date.now() - new Date(dateStr).getTime();
50
+ const s = Math.floor(diff / 1000);
51
+ if (s < 60) return `${s}s ago`;
52
+ const m = Math.floor(s / 60);
53
+ if (m < 60) return `${m}m ago`;
54
+ const h = Math.floor(m / 60);
55
+ if (h < 24) return `${h}h ago`;
56
+ return `${Math.floor(h / 24)}d ago`;
57
+ }
58
+
59
+ export function success(msg: string): void {
60
+ console.log(chalk.green('✓') + ' ' + msg);
61
+ }
62
+
63
+ export function errorMsg(msg: string): void {
64
+ console.error(chalk.red('✗') + ' ' + msg);
65
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }