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.
@@ -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":"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"}
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.2.4",
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": {
@@ -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
+ }