nexusapp-cli 1.2.4 → 2.1.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 +4 -6
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +152 -1
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/member.d.ts +3 -0
- package/dist/commands/member.d.ts.map +1 -0
- package/dist/commands/member.js +175 -0
- package/dist/commands/member.js.map +1 -0
- package/dist/commands/token.d.ts +3 -0
- package/dist/commands/token.d.ts.map +1 -0
- package/dist/commands/token.js +179 -0
- package/dist/commands/token.js.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/nexusapp-cli-2.0.0.tgz +0 -0
- package/package.json +2 -8
- package/src/commands/deploy.ts +126 -1
- package/src/commands/member.ts +168 -0
- package/src/commands/token.ts +173 -0
- package/src/index.ts +4 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerToken = registerToken;
|
|
7
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const client_js_1 = require("../client.js");
|
|
10
|
+
const output_js_1 = require("../output.js");
|
|
11
|
+
function parseDuration(str) {
|
|
12
|
+
const match = str.match(/^(\d+)(d|h|m)$/);
|
|
13
|
+
if (!match) {
|
|
14
|
+
throw new Error(`Invalid duration "${str}". Use format like: 90d, 4h, 30m`);
|
|
15
|
+
}
|
|
16
|
+
const value = parseInt(match[1], 10);
|
|
17
|
+
const unit = match[2];
|
|
18
|
+
const msMap = { d: 86400000, h: 3600000, m: 60000 };
|
|
19
|
+
return new Date(Date.now() + value * msMap[unit]);
|
|
20
|
+
}
|
|
21
|
+
function formatScopes(scopes) {
|
|
22
|
+
return scopes
|
|
23
|
+
.split(',')
|
|
24
|
+
.map((s) => s.trim())
|
|
25
|
+
.join(', ');
|
|
26
|
+
}
|
|
27
|
+
function tokenStatus(t) {
|
|
28
|
+
if (t.revokedAt)
|
|
29
|
+
return chalk_1.default.red('revoked');
|
|
30
|
+
if (t.expiresAt && new Date(t.expiresAt) < new Date())
|
|
31
|
+
return chalk_1.default.yellow('expired');
|
|
32
|
+
return chalk_1.default.green('active');
|
|
33
|
+
}
|
|
34
|
+
function registerToken(program) {
|
|
35
|
+
const token = program.command('token').description('Access token management commands');
|
|
36
|
+
// list
|
|
37
|
+
token
|
|
38
|
+
.command('list')
|
|
39
|
+
.description('List access tokens')
|
|
40
|
+
.option('--json', 'Output raw JSON')
|
|
41
|
+
.option('--show-last-used', 'Show last used column')
|
|
42
|
+
.option('--no-expiry', 'Show only tokens with no expiry date')
|
|
43
|
+
.option('--unused-since <days>', 'Show tokens unused for N or more days')
|
|
44
|
+
.action(async (opts) => {
|
|
45
|
+
try {
|
|
46
|
+
const res = await client_js_1.client.get('/api/tokens');
|
|
47
|
+
let tokens = (0, client_js_1.unwrap)(res.data);
|
|
48
|
+
if (!Array.isArray(tokens))
|
|
49
|
+
tokens = [];
|
|
50
|
+
if (opts.noExpiry) {
|
|
51
|
+
tokens = tokens.filter((t) => !t.expiresAt);
|
|
52
|
+
}
|
|
53
|
+
if (opts.unusedSince) {
|
|
54
|
+
const days = parseInt(opts.unusedSince, 10);
|
|
55
|
+
if (isNaN(days) || days < 0) {
|
|
56
|
+
(0, output_js_1.errorMsg)('--unused-since requires a positive integer (number of days)');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
const cutoff = new Date(Date.now() - days * 86400000);
|
|
60
|
+
tokens = tokens.filter((t) => {
|
|
61
|
+
if (!t.lastUsedAt)
|
|
62
|
+
return true;
|
|
63
|
+
return new Date(t.lastUsedAt) < cutoff;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (opts.json) {
|
|
67
|
+
(0, output_js_1.printJson)(tokens);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!tokens.length) {
|
|
71
|
+
console.log('No tokens found.');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const headers = ['ID', 'NAME', 'SCOPES', 'STATUS', 'EXPIRES', 'CREATED'];
|
|
75
|
+
if (opts.showLastUsed)
|
|
76
|
+
headers.push('LAST USED');
|
|
77
|
+
(0, output_js_1.printTable)(headers, tokens.map((t) => {
|
|
78
|
+
const row = [
|
|
79
|
+
t.id,
|
|
80
|
+
t.name,
|
|
81
|
+
formatScopes(t.scopes || ''),
|
|
82
|
+
tokenStatus(t),
|
|
83
|
+
t.expiresAt ? (0, output_js_1.timeAgo)(t.expiresAt) : '—',
|
|
84
|
+
t.createdAt ? (0, output_js_1.timeAgo)(t.createdAt) : '—',
|
|
85
|
+
];
|
|
86
|
+
if (opts.showLastUsed) {
|
|
87
|
+
row.push(t.lastUsedAt ? (0, output_js_1.timeAgo)(t.lastUsedAt) : 'never');
|
|
88
|
+
}
|
|
89
|
+
return row;
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
(0, output_js_1.errorMsg)((0, client_js_1.apiError)(err));
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
// create
|
|
98
|
+
token
|
|
99
|
+
.command('create')
|
|
100
|
+
.description('Create a new access token')
|
|
101
|
+
.requiredOption('--name <name>', 'Token name')
|
|
102
|
+
.requiredOption('--scopes <scopes>', 'Comma-separated scopes (e.g. deploy:write,secrets:read)')
|
|
103
|
+
.option('--expires <duration>', 'Expiry duration (e.g. 90d, 4h, 30m)')
|
|
104
|
+
.option('--json', 'Output raw JSON')
|
|
105
|
+
.action(async (opts) => {
|
|
106
|
+
let expiresAt;
|
|
107
|
+
if (opts.expires) {
|
|
108
|
+
try {
|
|
109
|
+
expiresAt = parseDuration(opts.expires).toISOString();
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
(0, output_js_1.errorMsg)(e.message);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const res = await client_js_1.client.post('/api/tokens', {
|
|
118
|
+
name: opts.name,
|
|
119
|
+
scopes: opts.scopes,
|
|
120
|
+
...(expiresAt ? { expiresAt } : {}),
|
|
121
|
+
});
|
|
122
|
+
const data = (0, client_js_1.unwrap)(res.data);
|
|
123
|
+
if (opts.json) {
|
|
124
|
+
(0, output_js_1.printJson)(data);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
console.log('');
|
|
128
|
+
console.log(chalk_1.default.bold('Token created'));
|
|
129
|
+
console.log('');
|
|
130
|
+
console.log(` ${chalk_1.default.dim('ID:')} ${data.id}`);
|
|
131
|
+
console.log(` ${chalk_1.default.dim('Name:')} ${data.name}`);
|
|
132
|
+
console.log(` ${chalk_1.default.dim('Scopes:')} ${formatScopes(data.scopes)}`);
|
|
133
|
+
if (data.expiresAt) {
|
|
134
|
+
console.log(` ${chalk_1.default.dim('Expires:')} ${new Date(data.expiresAt).toLocaleDateString()}`);
|
|
135
|
+
}
|
|
136
|
+
console.log('');
|
|
137
|
+
console.log(` ${chalk_1.default.dim('Token value (shown once — copy it now):')}`);
|
|
138
|
+
console.log('');
|
|
139
|
+
console.log(` ${chalk_1.default.cyan(data.token)}`);
|
|
140
|
+
console.log('');
|
|
141
|
+
console.log(chalk_1.default.yellow(' This value will not be shown again.'));
|
|
142
|
+
console.log('');
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
(0, output_js_1.errorMsg)((0, client_js_1.apiError)(err));
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
// revoke
|
|
150
|
+
token
|
|
151
|
+
.command('revoke <id>')
|
|
152
|
+
.description('Revoke an access token')
|
|
153
|
+
.option('--yes', 'Skip confirmation prompt')
|
|
154
|
+
.action(async (id, opts) => {
|
|
155
|
+
if (!opts.yes) {
|
|
156
|
+
const { confirm } = await inquirer_1.default.prompt([
|
|
157
|
+
{
|
|
158
|
+
type: 'confirm',
|
|
159
|
+
name: 'confirm',
|
|
160
|
+
message: `Revoke token "${id}"? This cannot be undone.`,
|
|
161
|
+
default: false,
|
|
162
|
+
},
|
|
163
|
+
]);
|
|
164
|
+
if (!confirm) {
|
|
165
|
+
console.log('Cancelled.');
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
await client_js_1.client.post(`/api/tokens/${id}/revoke`, {});
|
|
171
|
+
(0, output_js_1.success)(`Token ${id} revoked.`);
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
(0, output_js_1.errorMsg)((0, client_js_1.apiError)(err));
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/commands/token.ts"],"names":[],"mappings":";;;;;AA8BA,sCA8IC;AA3KD,wDAAgC;AAChC,kDAA0B;AAC1B,4CAAwD;AACxD,4CAAiF;AAEjF,SAAS,aAAa,CAAC,GAAW;IAChC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,kCAAkC,CAAC,CAAC;IAC9E,CAAC;IACD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACtB,MAAM,KAAK,GAA2B,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC;IAC5E,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,SAAS,YAAY,CAAC,MAAc;IAClC,OAAO,MAAM;SACV,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,IAAI,CAAC,IAAI,CAAC,CAAC;AAChB,CAAC;AAED,SAAS,WAAW,CAAC,CAAM;IACzB,IAAI,CAAC,CAAC,SAAS;QAAE,OAAO,eAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,CAAC,CAAC,SAAS,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,IAAI,EAAE;QAAE,OAAO,eAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACtF,OAAO,eAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;AAC/B,CAAC;AAED,SAAgB,aAAa,CAAC,OAAgB;IAC5C,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,kCAAkC,CAAC,CAAC;IAEvF,OAAO;IACP,KAAK;SACF,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,oBAAoB,CAAC;SACjC,MAAM,CAAC,QAAQ,EAAE,iBAAiB,CAAC;SACnC,MAAM,CAAC,kBAAkB,EAAE,uBAAuB,CAAC;SACnD,MAAM,CAAC,aAAa,EAAE,sCAAsC,CAAC;SAC7D,MAAM,CAAC,uBAAuB,EAAE,uCAAuC,CAAC;SACxE,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,kBAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;YAC5C,IAAI,MAAM,GAAU,IAAA,kBAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YACrC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;gBAAE,MAAM,GAAG,EAAE,CAAC;YAExC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAClB,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;YACnD,CAAC;YAED,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;gBACrB,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;gBAC5C,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,CAAC;oBAC5B,IAAA,oBAAQ,EAAC,6DAA6D,CAAC,CAAC;oBACxE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAClB,CAAC;gBACD,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,QAAQ,CAAC,CAAC;gBACtD,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE;oBAChC,IAAI,CAAC,CAAC,CAAC,UAAU;wBAAE,OAAO,IAAI,CAAC;oBAC/B,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,MAAM,CAAC;gBACzC,CAAC,CAAC,CAAC;YACL,CAAC;YAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBAAC,IAAA,qBAAS,EAAC,MAAM,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;YAC7C,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;YAEhE,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;YACzE,IAAI,IAAI,CAAC,YAAY;gBAAE,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAEjD,IAAA,sBAAU,EACR,OAAO,EACP,MAAM,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE;gBACpB,MAAM,GAAG,GAAsB;oBAC7B,CAAC,CAAC,EAAE;oBACJ,CAAC,CAAC,IAAI;oBACN,YAAY,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAC;oBAC5B,WAAW,CAAC,CAAC,CAAC;oBACd,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAA,mBAAO,EAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG;oBACxC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAA,mBAAO,EAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG;iBACzC,CAAC;gBACF,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;oBACtB,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,IAAA,mBAAO,EAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;gBAC3D,CAAC;gBACD,OAAO,GAAG,CAAC;YACb,CAAC,CAAC,CACH,CAAC;QACJ,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAA,oBAAQ,EAAC,IAAA,oBAAQ,EAAC,GAAG,CAAC,CAAC,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,SAAS;IACT,KAAK;SACF,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,2BAA2B,CAAC;SACxC,cAAc,CAAC,eAAe,EAAE,YAAY,CAAC;SAC7C,cAAc,CAAC,mBAAmB,EAAE,yDAAyD,CAAC;SAC9F,MAAM,CAAC,sBAAsB,EAAE,qCAAqC,CAAC;SACrE,MAAM,CAAC,QAAQ,EAAE,iBAAiB,CAAC;SACnC,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,IAAI,SAA6B,CAAC;QAElC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC;gBACH,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;YACxD,CAAC;YAAC,OAAO,CAAM,EAAE,CAAC;gBAChB,IAAA,oBAAQ,EAAC,CAAC,CAAC,OAAO,CAAC,CAAC;gBACpB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,kBAAM,CAAC,IAAI,CAAC,aAAa,EAAE;gBAC3C,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,MAAM,EAAE,IAAI,CAAC,MAAM;gBACnB,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACpC,CAAC,CAAC;YAEH,MAAM,IAAI,GAAG,IAAA,kBAAM,EAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAE9B,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBAAC,IAAA,qBAAS,EAAC,IAAI,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;YAE3C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC;YACzC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,KAAK,eAAK,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;YACrD,OAAO,CAAC,GAAG,CAAC,KAAK,eAAK,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,KAAK,eAAK,CAAC,GAAG,CAAC,SAAS,CAAC,KAAK,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACvE,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;gBACnB,OAAO,CAAC,GAAG,CAAC,KAAK,eAAK,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,kBAAkB,EAAE,EAAE,CAAC,CAAC;YAC7F,CAAC;YACD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,KAAK,eAAK,CAAC,GAAG,CAAC,yCAAyC,CAAC,EAAE,CAAC,CAAA;YACxE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,KAAK,eAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC3C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,eAAK,CAAC,MAAM,CAAC,uCAAuC,CAAC,CAAC,CAAC;YACnE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAClB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAA,oBAAQ,EAAC,IAAA,oBAAQ,EAAC,GAAG,CAAC,CAAC,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,SAAS;IACT,KAAK;SACF,OAAO,CAAC,aAAa,CAAC;SACtB,WAAW,CAAC,wBAAwB,CAAC;SACrC,MAAM,CAAC,OAAO,EAAE,0BAA0B,CAAC;SAC3C,MAAM,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE;QACzB,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YACd,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,kBAAQ,CAAC,MAAM,CAAC;gBACxC;oBACE,IAAI,EAAE,SAAS;oBACf,IAAI,EAAE,SAAS;oBACf,OAAO,EAAE,iBAAiB,EAAE,2BAA2B;oBACvD,OAAO,EAAE,KAAK;iBACf;aACF,CAAC,CAAC;YACH,IAAI,CAAC,OAAO,EAAE,CAAC;gBAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;gBAAC,OAAO;YAAC,CAAC;QACtD,CAAC;QAED,IAAI,CAAC;YACH,MAAM,kBAAM,CAAC,IAAI,CAAC,eAAe,EAAE,SAAS,EAAE,EAAE,CAAC,CAAC;YAClD,IAAA,mBAAO,EAAC,SAAS,EAAE,WAAW,CAAC,CAAC;QAClC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAA,oBAAQ,EAAC,IAAA,oBAAQ,EAAC,GAAG,CAAC,CAAC,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,8 @@ const deploy_js_1 = require("./commands/deploy.js");
|
|
|
7
7
|
const secret_js_1 = require("./commands/secret.js");
|
|
8
8
|
const project_js_1 = require("./commands/project.js");
|
|
9
9
|
const domain_js_1 = require("./commands/domain.js");
|
|
10
|
+
const token_js_1 = require("./commands/token.js");
|
|
11
|
+
const member_js_1 = require("./commands/member.js");
|
|
10
12
|
const program = new commander_1.Command();
|
|
11
13
|
program
|
|
12
14
|
.name('nexus')
|
|
@@ -17,6 +19,8 @@ program
|
|
|
17
19
|
(0, secret_js_1.registerSecret)(program);
|
|
18
20
|
(0, project_js_1.registerProject)(program);
|
|
19
21
|
(0, domain_js_1.registerDomain)(program);
|
|
22
|
+
(0, token_js_1.registerToken)(program);
|
|
23
|
+
(0, member_js_1.registerMember)(program);
|
|
20
24
|
program.parseAsync(process.argv).catch((err) => {
|
|
21
25
|
console.error(err.message || String(err));
|
|
22
26
|
process.exit(1);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AACA,yCAAoC;AACpC,gDAAkD;AAClD,oDAAsD;AACtD,oDAAsD;AACtD,sDAAwD;AACxD,oDAAsD;AAEtD,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,iCAAiC,CAAC;KAC9C,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,IAAA,sBAAY,EAAC,OAAO,CAAC,CAAC;AACtB,IAAA,0BAAc,EAAC,OAAO,CAAC,CAAC;AACxB,IAAA,0BAAc,EAAC,OAAO,CAAC,CAAC;AACxB,IAAA,4BAAe,EAAC,OAAO,CAAC,CAAC;AACzB,IAAA,0BAAc,EAAC,OAAO,CAAC,CAAC;AAExB,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IAC7C,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AACA,yCAAoC;AACpC,gDAAkD;AAClD,oDAAsD;AACtD,oDAAsD;AACtD,sDAAwD;AACxD,oDAAsD;AACtD,kDAAoD;AACpD,oDAAsD;AAEtD,MAAM,OAAO,GAAG,IAAI,mBAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,iCAAiC,CAAC;KAC9C,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,IAAA,sBAAY,EAAC,OAAO,CAAC,CAAC;AACtB,IAAA,0BAAc,EAAC,OAAO,CAAC,CAAC;AACxB,IAAA,0BAAc,EAAC,OAAO,CAAC,CAAC;AACxB,IAAA,4BAAe,EAAC,OAAO,CAAC,CAAC;AACzB,IAAA,0BAAc,EAAC,OAAO,CAAC,CAAC;AACxB,IAAA,wBAAa,EAAC,OAAO,CAAC,CAAC;AACvB,IAAA,0BAAc,EAAC,OAAO,CAAC,CAAC;AAExB,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IAC7C,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nexusapp-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "NEXUS AI command-line interface",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,12 +11,7 @@
|
|
|
11
11
|
"dev": "ts-node src/index.ts",
|
|
12
12
|
"prepublishOnly": "npm run build"
|
|
13
13
|
},
|
|
14
|
-
"keywords": [
|
|
15
|
-
"nexusai",
|
|
16
|
-
"cli",
|
|
17
|
-
"deployments",
|
|
18
|
-
"cloud"
|
|
19
|
-
],
|
|
14
|
+
"keywords": ["nexusai", "cli", "deployments", "cloud"],
|
|
20
15
|
"license": "MIT",
|
|
21
16
|
"dependencies": {
|
|
22
17
|
"axios": "^1.6.0",
|
|
@@ -24,7 +19,6 @@
|
|
|
24
19
|
"cli-table3": "^0.6.3",
|
|
25
20
|
"commander": "^12.0.0",
|
|
26
21
|
"inquirer": "^9.2.0",
|
|
27
|
-
"nexusapp-cli": "^1.2.3",
|
|
28
22
|
"ora": "^8.0.0"
|
|
29
23
|
},
|
|
30
24
|
"devDependencies": {
|
package/src/commands/deploy.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { randomBytes } from 'crypto';
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
2
3
|
import { Command } from 'commander';
|
|
3
4
|
import inquirer from 'inquirer';
|
|
4
5
|
import { client, apiError, unwrap } from '../client.js';
|
|
@@ -48,6 +49,40 @@ async function pollUntilDone(deploymentId: string, spin: ReturnType<typeof spinn
|
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Parse a .env-style file into a key→value map.
|
|
54
|
+
* Supports: KEY=VALUE, KEY="quoted value", KEY='quoted value', # comments, blank lines.
|
|
55
|
+
* --env pairs always win over file values (caller merges file first, then pairs).
|
|
56
|
+
*/
|
|
57
|
+
function parseEnvFile(filePath: string): Record<string, string> {
|
|
58
|
+
let raw: string;
|
|
59
|
+
try {
|
|
60
|
+
raw = readFileSync(filePath, 'utf8');
|
|
61
|
+
} catch {
|
|
62
|
+
errorMsg(`Cannot read env file: ${filePath}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
const result: Record<string, string> = {};
|
|
66
|
+
for (const line of raw.split('\n')) {
|
|
67
|
+
const trimmed = line.trim();
|
|
68
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
69
|
+
const idx = trimmed.indexOf('=');
|
|
70
|
+
if (idx <= 0) continue;
|
|
71
|
+
const key = trimmed.slice(0, idx).trim();
|
|
72
|
+
let val = trimmed.slice(idx + 1);
|
|
73
|
+
// Strip inline comments after unquoted values
|
|
74
|
+
if ((val.startsWith('"') && val.includes('"', 1)) || (val.startsWith("'") && val.includes("'", 1))) {
|
|
75
|
+
const q = val[0];
|
|
76
|
+
const close = val.indexOf(q, 1);
|
|
77
|
+
val = val.slice(1, close);
|
|
78
|
+
} else {
|
|
79
|
+
val = val.split('#')[0].trim();
|
|
80
|
+
}
|
|
81
|
+
if (key) result[key] = val;
|
|
82
|
+
}
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
|
|
51
86
|
export function registerDeploy(program: Command): void {
|
|
52
87
|
const deploy = program.command('deploy').description('Deployment commands');
|
|
53
88
|
|
|
@@ -126,11 +161,13 @@ export function registerDeploy(program: Command): void {
|
|
|
126
161
|
.option('--project <id>', 'Project ID')
|
|
127
162
|
.option('--provider <provider>', 'Provider (docker|gcp_cloud_run|aws_ecs_fargate|azure_container_apps)')
|
|
128
163
|
.option('--env <pairs...>', 'Environment variables as KEY=VALUE')
|
|
164
|
+
.option('--env-file <file>', 'Load environment variables from a .env file')
|
|
129
165
|
.option('--no-health-check', 'Disable health checks for this deployment')
|
|
130
166
|
.option('--wait', 'Wait until deployment is RUNNING or FAILED')
|
|
131
167
|
.option('--json', 'Output raw JSON')
|
|
132
168
|
.action(async (opts) => {
|
|
133
169
|
const envVars: Record<string, string> = {};
|
|
170
|
+
if (opts.envFile) Object.assign(envVars, parseEnvFile(opts.envFile));
|
|
134
171
|
if (opts.env) {
|
|
135
172
|
for (const pair of opts.env) {
|
|
136
173
|
const idx = pair.indexOf('=');
|
|
@@ -170,6 +207,7 @@ export function registerDeploy(program: Command): void {
|
|
|
170
207
|
.option('--branch <branch>', 'Git branch')
|
|
171
208
|
.option('--provider <provider>', 'Provider (docker|gcp_cloud_run|aws_ecs_fargate|azure_container_apps)')
|
|
172
209
|
.option('--env <pairs...>', 'Environment variables as KEY=VALUE')
|
|
210
|
+
.option('--env-file <file>', 'Load environment variables from a .env file')
|
|
173
211
|
.option('--framework <framework>', 'Framework hint (e.g. node, python, go)')
|
|
174
212
|
.option('--build-command <cmd>', 'Custom build command')
|
|
175
213
|
.option('--start-command <cmd>', 'Custom start command')
|
|
@@ -184,6 +222,7 @@ export function registerDeploy(program: Command): void {
|
|
|
184
222
|
.option('--json', 'Output raw JSON')
|
|
185
223
|
.action(async (opts) => {
|
|
186
224
|
const envVars: Record<string, string> = {};
|
|
225
|
+
if (opts.envFile) Object.assign(envVars, parseEnvFile(opts.envFile));
|
|
187
226
|
if (opts.env) {
|
|
188
227
|
for (const pair of opts.env) {
|
|
189
228
|
const idx = pair.indexOf('=');
|
|
@@ -231,6 +270,7 @@ export function registerDeploy(program: Command): void {
|
|
|
231
270
|
.option('--name <name>', 'Override deployment name')
|
|
232
271
|
.option('--provider <provider>', 'Override provider')
|
|
233
272
|
.option('--env <pairs...>', 'Override / add environment variables as KEY=VALUE')
|
|
273
|
+
.option('--env-file <file>', 'Load environment variables from a .env file (merged with existing, --env wins)')
|
|
234
274
|
.option('--wait', 'Wait until deployment is RUNNING or FAILED')
|
|
235
275
|
.option('--yes', 'Skip confirmation prompt')
|
|
236
276
|
.option('--json', 'Output raw JSON')
|
|
@@ -251,6 +291,7 @@ export function registerDeploy(program: Command): void {
|
|
|
251
291
|
}
|
|
252
292
|
|
|
253
293
|
const baseEnvVars: Record<string, string> = { ...(deployment.envVars || {}) };
|
|
294
|
+
if (opts.envFile) Object.assign(baseEnvVars, parseEnvFile(opts.envFile));
|
|
254
295
|
if (opts.env) {
|
|
255
296
|
for (const pair of (opts.env as string[])) {
|
|
256
297
|
const idx = pair.indexOf('=');
|
|
@@ -503,6 +544,7 @@ export function registerDeploy(program: Command): void {
|
|
|
503
544
|
.option('--claude-web-cookie <cookie>', 'CLAUDE_WEB_COOKIE value')
|
|
504
545
|
.option('--provider <provider>', 'Provider (docker|gcp_cloud_run|aws_ecs_fargate|azure_container_apps)')
|
|
505
546
|
.option('--env <pairs...>', 'Additional environment variables as KEY=VALUE')
|
|
547
|
+
.option('--env-file <file>', 'Load environment variables from a .env file')
|
|
506
548
|
.option('--wait', 'Wait until deployment is RUNNING or FAILED')
|
|
507
549
|
.option('--json', 'Output raw JSON')
|
|
508
550
|
.action(async (opts) => {
|
|
@@ -516,6 +558,7 @@ export function registerDeploy(program: Command): void {
|
|
|
516
558
|
if (opts.claudeApiKey) envVars['CLAUDE_AI_SESSION_KEY'] = opts.claudeApiKey;
|
|
517
559
|
if (opts.claudeWebSession) envVars['CLAUDE_WEB_SESSION_KEY'] = opts.claudeWebSession;
|
|
518
560
|
if (opts.claudeWebCookie) envVars['CLAUDE_WEB_COOKIE'] = opts.claudeWebCookie;
|
|
561
|
+
if (opts.envFile) Object.assign(envVars, parseEnvFile(opts.envFile));
|
|
519
562
|
if (opts.env) {
|
|
520
563
|
for (const pair of opts.env as string[]) {
|
|
521
564
|
const idx = pair.indexOf('=');
|
|
@@ -527,7 +570,7 @@ export function registerDeploy(program: Command): void {
|
|
|
527
570
|
port: 18789,
|
|
528
571
|
name: opts.name,
|
|
529
572
|
envVars,
|
|
530
|
-
startCommand: 'mkdir -p /home/node/.openclaw && echo \'{"gateway":{"controlUi":{"dangerouslyAllowHostHeaderOriginFallback":true}}}\' > /home/node/.openclaw/openclaw.json && node dist/index.js gateway --bind lan --port 18789 --allow-unconfigured',
|
|
573
|
+
startCommand: 'mkdir -p /home/node/.openclaw && echo \'{"gateway":{"controlUi":{"dangerouslyAllowHostHeaderOriginFallback":true,"dangerouslyDisableDeviceAuth":true},"trustedProxies":["172.16.0.0/12","10.0.0.0/8"]}}\' > /home/node/.openclaw/openclaw.json && node dist/index.js gateway --bind lan --port 18789 --allow-unconfigured',
|
|
531
574
|
healthCheckEnabled: false, // OpenClaw gateway has no HTTP health endpoint
|
|
532
575
|
};
|
|
533
576
|
if (opts.provider) payload.provider = opts.provider;
|
|
@@ -551,6 +594,88 @@ export function registerDeploy(program: Command): void {
|
|
|
551
594
|
}
|
|
552
595
|
});
|
|
553
596
|
|
|
597
|
+
// flixty
|
|
598
|
+
deploy
|
|
599
|
+
.command('flixty')
|
|
600
|
+
.description('Deploy Flixty social media creator studio from source (github.com/nexusrun/flixty)')
|
|
601
|
+
.option('--name <name>', 'Deployment name', 'flixty')
|
|
602
|
+
.option('--session-secret <secret>', 'Express session secret (auto-generated if not set)')
|
|
603
|
+
.option('--base-url <url>', 'Public URL of the deployment (for OAuth redirect URIs)')
|
|
604
|
+
.option('--anthropic-api-key <key>', 'Anthropic API key for AI Assist')
|
|
605
|
+
.option('--x-client-id <id>', 'X/Twitter OAuth 2.0 Client ID')
|
|
606
|
+
.option('--x-client-secret <secret>', 'X/Twitter OAuth 2.0 Client Secret')
|
|
607
|
+
.option('--linkedin-client-id <id>', 'LinkedIn OAuth Client ID')
|
|
608
|
+
.option('--linkedin-client-secret <secret>', 'LinkedIn OAuth Client Secret')
|
|
609
|
+
.option('--fb-app-id <id>', 'Facebook App ID')
|
|
610
|
+
.option('--fb-app-secret <secret>', 'Facebook App Secret')
|
|
611
|
+
.option('--tiktok-client-key <key>', 'TikTok Client Key')
|
|
612
|
+
.option('--tiktok-client-secret <secret>', 'TikTok Client Secret')
|
|
613
|
+
.option('--google-client-id <id>', 'Google Client ID (YouTube)')
|
|
614
|
+
.option('--google-client-secret <secret>', 'Google Client Secret')
|
|
615
|
+
.option('--provider <provider>', 'Provider (docker|gcp_cloud_run|aws_ecs_fargate|azure_container_apps)')
|
|
616
|
+
.option('--env <pairs...>', 'Additional environment variables as KEY=VALUE')
|
|
617
|
+
.option('--env-file <file>', 'Load environment variables from a .env file')
|
|
618
|
+
.option('--wait', 'Wait until deployment is RUNNING or FAILED')
|
|
619
|
+
.option('--json', 'Output raw JSON')
|
|
620
|
+
.action(async (opts) => {
|
|
621
|
+
const sessionSecret = opts.sessionSecret || randomBytes(32).toString('hex');
|
|
622
|
+
const envVars: Record<string, string> = {
|
|
623
|
+
SESSION_SECRET: sessionSecret,
|
|
624
|
+
PORT: '3000',
|
|
625
|
+
NODE_ENV: 'production',
|
|
626
|
+
};
|
|
627
|
+
if (opts.baseUrl) envVars['BASE_URL'] = opts.baseUrl;
|
|
628
|
+
if (opts.anthropicApiKey) envVars['ANTHROPIC_API_KEY'] = opts.anthropicApiKey;
|
|
629
|
+
if (opts.xClientId) envVars['X_CLIENT_ID'] = opts.xClientId;
|
|
630
|
+
if (opts.xClientSecret) envVars['X_CLIENT_SECRET'] = opts.xClientSecret;
|
|
631
|
+
if (opts.linkedinClientId) envVars['LINKEDIN_CLIENT_ID'] = opts.linkedinClientId;
|
|
632
|
+
if (opts.linkedinClientSecret) envVars['LINKEDIN_CLIENT_SECRET'] = opts.linkedinClientSecret;
|
|
633
|
+
if (opts.fbAppId) envVars['FB_APP_ID'] = opts.fbAppId;
|
|
634
|
+
if (opts.fbAppSecret) envVars['FB_APP_SECRET'] = opts.fbAppSecret;
|
|
635
|
+
if (opts.tiktokClientKey) envVars['TIKTOK_CLIENT_KEY'] = opts.tiktokClientKey;
|
|
636
|
+
if (opts.tiktokClientSecret) envVars['TIKTOK_CLIENT_SECRET'] = opts.tiktokClientSecret;
|
|
637
|
+
if (opts.googleClientId) envVars['GOOGLE_CLIENT_ID'] = opts.googleClientId;
|
|
638
|
+
if (opts.googleClientSecret) envVars['GOOGLE_CLIENT_SECRET'] = opts.googleClientSecret;
|
|
639
|
+
if (opts.envFile) Object.assign(envVars, parseEnvFile(opts.envFile));
|
|
640
|
+
if (opts.env) {
|
|
641
|
+
for (const pair of opts.env as string[]) {
|
|
642
|
+
const idx = pair.indexOf('=');
|
|
643
|
+
if (idx > 0) envVars[pair.slice(0, idx)] = pair.slice(idx + 1);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
const payload: Record<string, any> = {
|
|
647
|
+
sourceType: 'repo',
|
|
648
|
+
repoUrl: 'https://github.com/nexusrun/flixty.git',
|
|
649
|
+
name: opts.name,
|
|
650
|
+
environment: 'PRODUCTION',
|
|
651
|
+
startCommand: 'node server.js',
|
|
652
|
+
envVars,
|
|
653
|
+
healthCheckEnabled: true,
|
|
654
|
+
};
|
|
655
|
+
if (opts.provider) payload.provider = opts.provider;
|
|
656
|
+
|
|
657
|
+
try {
|
|
658
|
+
const res = await client.post('/api/gpt/deploy/source', payload);
|
|
659
|
+
const d = res.data;
|
|
660
|
+
if (opts.json) { printJson({ ...d, sessionSecret }); return; }
|
|
661
|
+
if (opts.wait) {
|
|
662
|
+
const spin = spinner('Deploying Flixty...');
|
|
663
|
+
await pollUntilDone(d.id, spin);
|
|
664
|
+
} else {
|
|
665
|
+
success(`Flixty queued: ${d.name || d.id}`);
|
|
666
|
+
console.log(` Session secret: ${sessionSecret}`);
|
|
667
|
+
console.log(` Port: 3000`);
|
|
668
|
+
if (!opts.baseUrl) {
|
|
669
|
+
console.log(` Note: once running, redeploy with --base-url <public-url> for OAuth to work`);
|
|
670
|
+
}
|
|
671
|
+
console.log(` Run 'nexus deploy status ${d.id} --watch' to track progress`);
|
|
672
|
+
}
|
|
673
|
+
} catch (err) {
|
|
674
|
+
errorMsg(apiError(err));
|
|
675
|
+
process.exit(1);
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
|
|
554
679
|
// status
|
|
555
680
|
deploy
|
|
556
681
|
.command('status <name-or-id>')
|
|
@@ -0,0 +1,168 @@
|
|
|
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
|
+
const VALID_ROLES = ['OWNER', 'ADMIN', 'MEMBER', 'DEPLOYMENT_MANAGER', 'AUDITOR', 'BILLING_MANAGER'];
|
|
8
|
+
|
|
9
|
+
const ROLE_LABELS: Record<string, string> = {
|
|
10
|
+
OWNER: 'Owner',
|
|
11
|
+
ADMIN: 'Admin',
|
|
12
|
+
MEMBER: 'Developer',
|
|
13
|
+
DEPLOYMENT_MANAGER: 'Deployment Manager',
|
|
14
|
+
AUDITOR: 'Auditor',
|
|
15
|
+
BILLING_MANAGER: 'Billing Manager',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function roleLabel(role: string): string {
|
|
19
|
+
return ROLE_LABELS[role] || role;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function statusBadge(status: string): string {
|
|
23
|
+
if (status === 'active') return chalk.green(status);
|
|
24
|
+
if (status === 'suspended') return chalk.yellow(status);
|
|
25
|
+
return chalk.gray(status);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function registerMember(program: Command): void {
|
|
29
|
+
const member = program.command('member').description('Team member management commands');
|
|
30
|
+
|
|
31
|
+
// list
|
|
32
|
+
member
|
|
33
|
+
.command('list')
|
|
34
|
+
.description('List all team members in the organization')
|
|
35
|
+
.option('--json', 'Output raw JSON')
|
|
36
|
+
.action(async (opts) => {
|
|
37
|
+
try {
|
|
38
|
+
const res = await client.get('/api/organizations/users');
|
|
39
|
+
let members: any[] = unwrap(res.data);
|
|
40
|
+
if (!Array.isArray(members)) members = [];
|
|
41
|
+
|
|
42
|
+
if (opts.json) { printJson(members); return; }
|
|
43
|
+
if (!members.length) { console.log('No members found.'); return; }
|
|
44
|
+
|
|
45
|
+
printTable(
|
|
46
|
+
['ID', 'NAME', 'EMAIL', 'ROLE', 'STATUS', 'JOINED'],
|
|
47
|
+
members.map((m: any) => [
|
|
48
|
+
m.id,
|
|
49
|
+
m.name || '—',
|
|
50
|
+
m.email,
|
|
51
|
+
roleLabel(m.role),
|
|
52
|
+
statusBadge(m.status),
|
|
53
|
+
m.createdAt ? timeAgo(m.createdAt) : '—',
|
|
54
|
+
])
|
|
55
|
+
);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
errorMsg(apiError(err));
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// invite
|
|
63
|
+
member
|
|
64
|
+
.command('invite <email>')
|
|
65
|
+
.description('Invite a new member to the organization')
|
|
66
|
+
.requiredOption('--role <role>', `Role to assign (${VALID_ROLES.filter(r => r !== 'OWNER').join(', ')})`)
|
|
67
|
+
.option('--json', 'Output raw JSON')
|
|
68
|
+
.action(async (email, opts) => {
|
|
69
|
+
const role = opts.role.toUpperCase();
|
|
70
|
+
|
|
71
|
+
if (!VALID_ROLES.includes(role) || role === 'OWNER') {
|
|
72
|
+
errorMsg(`Invalid role "${opts.role}". Valid roles: ${VALID_ROLES.filter(r => r !== 'OWNER').join(', ')}`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const res = await client.post('/api/organizations/users/invite', { email, role });
|
|
78
|
+
const data = unwrap(res.data);
|
|
79
|
+
const user = data.user || data;
|
|
80
|
+
|
|
81
|
+
if (opts.json) { printJson(data); return; }
|
|
82
|
+
|
|
83
|
+
success(`Invited ${email} as ${roleLabel(role)}.`);
|
|
84
|
+
console.log(` ${chalk.dim('User ID:')} ${user.id}`);
|
|
85
|
+
console.log(` ${chalk.dim('An email with login credentials has been sent.')}`);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
errorMsg(apiError(err));
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// role
|
|
93
|
+
member
|
|
94
|
+
.command('role <userId> <role>')
|
|
95
|
+
.description('Update the role of a team member')
|
|
96
|
+
.option('--json', 'Output raw JSON')
|
|
97
|
+
.action(async (userId, roleArg, opts) => {
|
|
98
|
+
const role = roleArg.toUpperCase();
|
|
99
|
+
|
|
100
|
+
if (!VALID_ROLES.includes(role) || role === 'OWNER') {
|
|
101
|
+
errorMsg(`Invalid role "${roleArg}". Valid roles: ${VALID_ROLES.filter(r => r !== 'OWNER').join(', ')}`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const res = await client.patch(`/api/organizations/users/${userId}/role`, { role });
|
|
107
|
+
const updated = unwrap(res.data);
|
|
108
|
+
|
|
109
|
+
if (opts.json) { printJson(updated); return; }
|
|
110
|
+
|
|
111
|
+
success(`Updated ${updated.email} to ${roleLabel(updated.role)}.`);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
errorMsg(apiError(err));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// suspend
|
|
119
|
+
member
|
|
120
|
+
.command('suspend <userId>')
|
|
121
|
+
.description('Suspend a team member (blocks their access)')
|
|
122
|
+
.option('--yes', 'Skip confirmation prompt')
|
|
123
|
+
.option('--json', 'Output raw JSON')
|
|
124
|
+
.action(async (userId, opts) => {
|
|
125
|
+
if (!opts.yes) {
|
|
126
|
+
const { confirm } = await inquirer.prompt([
|
|
127
|
+
{
|
|
128
|
+
type: 'confirm',
|
|
129
|
+
name: 'confirm',
|
|
130
|
+
message: `Suspend member "${userId}"? They will lose access immediately.`,
|
|
131
|
+
default: false,
|
|
132
|
+
},
|
|
133
|
+
]);
|
|
134
|
+
if (!confirm) { console.log('Cancelled.'); return; }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const res = await client.post(`/api/organizations/users/${userId}/suspend`, {});
|
|
139
|
+
const updated = unwrap(res.data);
|
|
140
|
+
|
|
141
|
+
if (opts.json) { printJson(updated); return; }
|
|
142
|
+
|
|
143
|
+
success(`${updated.email} suspended.`);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
errorMsg(apiError(err));
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// activate
|
|
151
|
+
member
|
|
152
|
+
.command('activate <userId>')
|
|
153
|
+
.description('Restore access for a suspended team member')
|
|
154
|
+
.option('--json', 'Output raw JSON')
|
|
155
|
+
.action(async (userId, opts) => {
|
|
156
|
+
try {
|
|
157
|
+
const res = await client.post(`/api/organizations/users/${userId}/activate`, {});
|
|
158
|
+
const updated = unwrap(res.data);
|
|
159
|
+
|
|
160
|
+
if (opts.json) { printJson(updated); return; }
|
|
161
|
+
|
|
162
|
+
success(`${updated.email} activated.`);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
errorMsg(apiError(err));
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|