nexusapp-cli 3.1.0 → 3.1.1
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 +6 -2
- package/src/client.ts +0 -68
- package/src/commands/auth.ts +0 -186
- package/src/commands/bucket.ts +0 -261
- package/src/commands/database.ts +0 -305
- package/src/commands/deploy.ts +0 -904
- package/src/commands/domain.ts +0 -167
- package/src/commands/exec.ts +0 -154
- package/src/commands/managedDb.ts +0 -170
- package/src/commands/member.ts +0 -168
- package/src/commands/project.ts +0 -81
- package/src/commands/secret.ts +0 -117
- package/src/commands/token.ts +0 -173
- package/src/commands/volume.ts +0 -113
- package/src/config.ts +0 -56
- package/src/index.ts +0 -39
- package/src/output.ts +0 -65
- package/tsconfig.json +0 -19
package/src/commands/project.ts
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
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
|
-
}
|
package/src/commands/secret.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
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/commands/token.ts
DELETED
|
@@ -1,173 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import { client, apiError, unwrap } from '../client.js';
|
|
5
|
-
import { printTable, printJson, success, errorMsg, timeAgo } from '../output.js';
|
|
6
|
-
|
|
7
|
-
function parseDuration(str: string): Date {
|
|
8
|
-
const match = str.match(/^(\d+)(d|h|m)$/);
|
|
9
|
-
if (!match) {
|
|
10
|
-
throw new Error(`Invalid duration "${str}". Use format like: 90d, 4h, 30m`);
|
|
11
|
-
}
|
|
12
|
-
const value = parseInt(match[1], 10);
|
|
13
|
-
const unit = match[2];
|
|
14
|
-
const msMap: Record<string, number> = { d: 86400000, h: 3600000, m: 60000 };
|
|
15
|
-
return new Date(Date.now() + value * msMap[unit]);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function formatScopes(scopes: string): string {
|
|
19
|
-
return scopes
|
|
20
|
-
.split(',')
|
|
21
|
-
.map((s) => s.trim())
|
|
22
|
-
.join(', ');
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function tokenStatus(t: any): string {
|
|
26
|
-
if (t.revokedAt) return chalk.red('revoked');
|
|
27
|
-
if (t.expiresAt && new Date(t.expiresAt) < new Date()) return chalk.yellow('expired');
|
|
28
|
-
return chalk.green('active');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function registerToken(program: Command): void {
|
|
32
|
-
const token = program.command('token').description('Access token management commands');
|
|
33
|
-
|
|
34
|
-
// list
|
|
35
|
-
token
|
|
36
|
-
.command('list')
|
|
37
|
-
.description('List access tokens')
|
|
38
|
-
.option('--json', 'Output raw JSON')
|
|
39
|
-
.option('--show-last-used', 'Show last used column')
|
|
40
|
-
.option('--no-expiry', 'Show only tokens with no expiry date')
|
|
41
|
-
.option('--unused-since <days>', 'Show tokens unused for N or more days')
|
|
42
|
-
.action(async (opts) => {
|
|
43
|
-
try {
|
|
44
|
-
const res = await client.get('/api/tokens');
|
|
45
|
-
let tokens: any[] = unwrap(res.data);
|
|
46
|
-
if (!Array.isArray(tokens)) tokens = [];
|
|
47
|
-
|
|
48
|
-
if (opts.noExpiry) {
|
|
49
|
-
tokens = tokens.filter((t: any) => !t.expiresAt);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (opts.unusedSince) {
|
|
53
|
-
const days = parseInt(opts.unusedSince, 10);
|
|
54
|
-
if (isNaN(days) || days < 0) {
|
|
55
|
-
errorMsg('--unused-since requires a positive integer (number of days)');
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
const cutoff = new Date(Date.now() - days * 86400000);
|
|
59
|
-
tokens = tokens.filter((t: any) => {
|
|
60
|
-
if (!t.lastUsedAt) return true;
|
|
61
|
-
return new Date(t.lastUsedAt) < cutoff;
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (opts.json) { printJson(tokens); return; }
|
|
66
|
-
if (!tokens.length) { console.log('No tokens found.'); return; }
|
|
67
|
-
|
|
68
|
-
const headers = ['ID', 'NAME', 'SCOPES', 'STATUS', 'EXPIRES', 'CREATED'];
|
|
69
|
-
if (opts.showLastUsed) headers.push('LAST USED');
|
|
70
|
-
|
|
71
|
-
printTable(
|
|
72
|
-
headers,
|
|
73
|
-
tokens.map((t: any) => {
|
|
74
|
-
const row: (string | null)[] = [
|
|
75
|
-
t.id,
|
|
76
|
-
t.name,
|
|
77
|
-
formatScopes(t.scopes || ''),
|
|
78
|
-
tokenStatus(t),
|
|
79
|
-
t.expiresAt ? timeAgo(t.expiresAt) : '—',
|
|
80
|
-
t.createdAt ? timeAgo(t.createdAt) : '—',
|
|
81
|
-
];
|
|
82
|
-
if (opts.showLastUsed) {
|
|
83
|
-
row.push(t.lastUsedAt ? timeAgo(t.lastUsedAt) : 'never');
|
|
84
|
-
}
|
|
85
|
-
return row;
|
|
86
|
-
})
|
|
87
|
-
);
|
|
88
|
-
} catch (err) {
|
|
89
|
-
errorMsg(apiError(err));
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// create
|
|
95
|
-
token
|
|
96
|
-
.command('create')
|
|
97
|
-
.description('Create a new access token')
|
|
98
|
-
.requiredOption('--name <name>', 'Token name')
|
|
99
|
-
.requiredOption('--scopes <scopes>', 'Comma-separated scopes (e.g. deploy:write,secrets:read)')
|
|
100
|
-
.option('--expires <duration>', 'Expiry duration (e.g. 90d, 4h, 30m)')
|
|
101
|
-
.option('--json', 'Output raw JSON')
|
|
102
|
-
.action(async (opts) => {
|
|
103
|
-
let expiresAt: string | undefined;
|
|
104
|
-
|
|
105
|
-
if (opts.expires) {
|
|
106
|
-
try {
|
|
107
|
-
expiresAt = parseDuration(opts.expires).toISOString();
|
|
108
|
-
} catch (e: any) {
|
|
109
|
-
errorMsg(e.message);
|
|
110
|
-
process.exit(1);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
const res = await client.post('/api/tokens', {
|
|
116
|
-
name: opts.name,
|
|
117
|
-
scopes: opts.scopes,
|
|
118
|
-
...(expiresAt ? { expiresAt } : {}),
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
const data = unwrap(res.data);
|
|
122
|
-
|
|
123
|
-
if (opts.json) { printJson(data); return; }
|
|
124
|
-
|
|
125
|
-
console.log('');
|
|
126
|
-
console.log(chalk.bold('Token created'));
|
|
127
|
-
console.log('');
|
|
128
|
-
console.log(` ${chalk.dim('ID:')} ${data.id}`);
|
|
129
|
-
console.log(` ${chalk.dim('Name:')} ${data.name}`);
|
|
130
|
-
console.log(` ${chalk.dim('Scopes:')} ${formatScopes(data.scopes)}`);
|
|
131
|
-
if (data.expiresAt) {
|
|
132
|
-
console.log(` ${chalk.dim('Expires:')} ${new Date(data.expiresAt).toLocaleDateString()}`);
|
|
133
|
-
}
|
|
134
|
-
console.log('');
|
|
135
|
-
console.log(` ${chalk.dim('Token value (shown once — copy it now):')}`)
|
|
136
|
-
console.log('');
|
|
137
|
-
console.log(` ${chalk.cyan(data.token)}`);
|
|
138
|
-
console.log('');
|
|
139
|
-
console.log(chalk.yellow(' This value will not be shown again.'));
|
|
140
|
-
console.log('');
|
|
141
|
-
} catch (err) {
|
|
142
|
-
errorMsg(apiError(err));
|
|
143
|
-
process.exit(1);
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
// revoke
|
|
148
|
-
token
|
|
149
|
-
.command('revoke <id>')
|
|
150
|
-
.description('Revoke an access token')
|
|
151
|
-
.option('--yes', 'Skip confirmation prompt')
|
|
152
|
-
.action(async (id, opts) => {
|
|
153
|
-
if (!opts.yes) {
|
|
154
|
-
const { confirm } = await inquirer.prompt([
|
|
155
|
-
{
|
|
156
|
-
type: 'confirm',
|
|
157
|
-
name: 'confirm',
|
|
158
|
-
message: `Revoke token "${id}"? This cannot be undone.`,
|
|
159
|
-
default: false,
|
|
160
|
-
},
|
|
161
|
-
]);
|
|
162
|
-
if (!confirm) { console.log('Cancelled.'); return; }
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
try {
|
|
166
|
-
await client.post(`/api/tokens/${id}/revoke`, {});
|
|
167
|
-
success(`Token ${id} revoked.`);
|
|
168
|
-
} catch (err) {
|
|
169
|
-
errorMsg(apiError(err));
|
|
170
|
-
process.exit(1);
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
}
|
package/src/commands/volume.ts
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
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
|
-
function formatBytes(bytes: number | bigint): string {
|
|
7
|
-
const n = typeof bytes === 'bigint' ? Number(bytes) : bytes;
|
|
8
|
-
if (n < 1024) return `${n} B`;
|
|
9
|
-
if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
|
|
10
|
-
if (n < 1024 * 1024 * 1024) return `${(n / (1024 * 1024)).toFixed(1)} MB`;
|
|
11
|
-
return `${(n / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function registerVolume(program: Command): void {
|
|
15
|
-
const vol = program
|
|
16
|
-
.command('volume')
|
|
17
|
-
.description('Persistent storage volumes (filesystem mounts attached to deployments)');
|
|
18
|
-
|
|
19
|
-
vol
|
|
20
|
-
.command('list')
|
|
21
|
-
.description('List volumes')
|
|
22
|
-
.option('--json', 'Output raw JSON')
|
|
23
|
-
.action(async (opts) => {
|
|
24
|
-
try {
|
|
25
|
-
const res = await client.get('/api/volumes');
|
|
26
|
-
const volumes: any[] = unwrap(res.data) || [];
|
|
27
|
-
if (opts.json) { printJson(volumes); return; }
|
|
28
|
-
if (!volumes.length) { console.log('No volumes found.'); return; }
|
|
29
|
-
|
|
30
|
-
printTable(
|
|
31
|
-
['ID', 'NAME', 'SIZE', 'STATUS', 'ATTACHED TO', 'CREATED'],
|
|
32
|
-
volumes.map((v: any) => [
|
|
33
|
-
v.id,
|
|
34
|
-
v.displayName ? `${v.name} (${v.displayName})` : v.name,
|
|
35
|
-
formatBytes(v.sizeBytes),
|
|
36
|
-
v.status,
|
|
37
|
-
v.attachment ? `${v.attachment.deploymentName}:${v.attachment.mountPath}` : '—',
|
|
38
|
-
v.createdAt ? timeAgo(v.createdAt) : '—',
|
|
39
|
-
])
|
|
40
|
-
);
|
|
41
|
-
} catch (err) { errorMsg(apiError(err)); process.exit(1); }
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
vol
|
|
45
|
-
.command('create <name>')
|
|
46
|
-
.description('Create a new volume (org-scoped)')
|
|
47
|
-
.option('--display-name <name>', 'Friendly display name')
|
|
48
|
-
.option('--json', 'Output raw JSON')
|
|
49
|
-
.action(async (name, opts) => {
|
|
50
|
-
try {
|
|
51
|
-
const res = await client.post('/api/volumes', { name, displayName: opts.displayName });
|
|
52
|
-
const v = unwrap(res.data);
|
|
53
|
-
if (opts.json) { printJson(v); return; }
|
|
54
|
-
success(`Volume created: ${v.id} (${v.name})`);
|
|
55
|
-
} catch (err) { errorMsg(apiError(err)); process.exit(1); }
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
vol
|
|
59
|
-
.command('delete <id>')
|
|
60
|
-
.description('Delete a volume (must be detached first)')
|
|
61
|
-
.option('--yes', 'Skip confirmation prompt')
|
|
62
|
-
.action(async (id, opts) => {
|
|
63
|
-
if (!opts.yes) {
|
|
64
|
-
const { confirm } = await inquirer.prompt([
|
|
65
|
-
{ type: 'confirm', name: 'confirm', message: `Delete volume "${id}"? Data is destroyed.`, default: false },
|
|
66
|
-
]);
|
|
67
|
-
if (!confirm) { console.log('Cancelled.'); return; }
|
|
68
|
-
}
|
|
69
|
-
try {
|
|
70
|
-
await client.delete(`/api/volumes/${id}`);
|
|
71
|
-
success(`Volume ${id} deleted.`);
|
|
72
|
-
} catch (err) { errorMsg(apiError(err)); process.exit(1); }
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
vol
|
|
76
|
-
.command('attach <id> <deployment-id>')
|
|
77
|
-
.description('Attach a volume to a deployment (requires redeploy to take effect)')
|
|
78
|
-
.option('--mount <path>', 'Mount path inside the container', '/data')
|
|
79
|
-
.action(async (id, deploymentId, opts) => {
|
|
80
|
-
try {
|
|
81
|
-
const res = await client.post(`/api/volumes/${id}/attach`, {
|
|
82
|
-
deploymentId,
|
|
83
|
-
mountPath: opts.mount,
|
|
84
|
-
});
|
|
85
|
-
const dv = unwrap(res.data);
|
|
86
|
-
success(`Attached volume ${id} to deployment ${deploymentId} at ${dv.mountPath}.`);
|
|
87
|
-
console.log(" Run 'nexus deploy redeploy <id>' to mount it.");
|
|
88
|
-
} catch (err) { errorMsg(apiError(err)); process.exit(1); }
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
vol
|
|
92
|
-
.command('detach <id>')
|
|
93
|
-
.description('Detach a volume from its deployment')
|
|
94
|
-
.action(async (id) => {
|
|
95
|
-
try {
|
|
96
|
-
await client.post(`/api/volumes/${id}/detach`);
|
|
97
|
-
success(`Volume ${id} detached.`);
|
|
98
|
-
} catch (err) { errorMsg(apiError(err)); process.exit(1); }
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
vol
|
|
102
|
-
.command('refresh-usage <id>')
|
|
103
|
-
.description('Refresh on-disk usage for a volume')
|
|
104
|
-
.option('--json', 'Output raw JSON')
|
|
105
|
-
.action(async (id, opts) => {
|
|
106
|
-
try {
|
|
107
|
-
const res = await client.post(`/api/volumes/${id}/refresh-usage`);
|
|
108
|
-
const v = unwrap(res.data);
|
|
109
|
-
if (opts.json) { printJson(v); return; }
|
|
110
|
-
success(`${v.name}: ${formatBytes(v.sizeBytes)}`);
|
|
111
|
-
} catch (err) { errorMsg(apiError(err)); process.exit(1); }
|
|
112
|
-
});
|
|
113
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
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://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
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
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
|
-
import { registerToken } from './commands/token.js';
|
|
9
|
-
import { registerMember } from './commands/member.js';
|
|
10
|
-
import { registerDatabase } from './commands/database.js';
|
|
11
|
-
import { registerVolume } from './commands/volume.js';
|
|
12
|
-
import { registerBucket } from './commands/bucket.js';
|
|
13
|
-
import { registerManagedDb } from './commands/managedDb.js';
|
|
14
|
-
import { registerExec } from './commands/exec.js';
|
|
15
|
-
|
|
16
|
-
const program = new Command();
|
|
17
|
-
|
|
18
|
-
program
|
|
19
|
-
.name('nexus')
|
|
20
|
-
.description('NEXUS AI command-line interface')
|
|
21
|
-
.version('1.0.0');
|
|
22
|
-
|
|
23
|
-
registerAuth(program);
|
|
24
|
-
registerDeploy(program);
|
|
25
|
-
registerSecret(program);
|
|
26
|
-
registerProject(program);
|
|
27
|
-
registerDomain(program);
|
|
28
|
-
registerToken(program);
|
|
29
|
-
registerMember(program);
|
|
30
|
-
registerDatabase(program);
|
|
31
|
-
registerVolume(program);
|
|
32
|
-
registerBucket(program);
|
|
33
|
-
registerManagedDb(program);
|
|
34
|
-
registerExec(program);
|
|
35
|
-
|
|
36
|
-
program.parseAsync(process.argv).catch((err) => {
|
|
37
|
-
console.error(err.message || String(err));
|
|
38
|
-
process.exit(1);
|
|
39
|
-
});
|
package/src/output.ts
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
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
|
-
}
|