xtra-cli 0.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/LICENSE +21 -0
- package/README.md +87 -0
- package/dist/bin/xtra.js +124 -0
- package/dist/commands/access.js +107 -0
- package/dist/commands/admin.js +118 -0
- package/dist/commands/audit.js +67 -0
- package/dist/commands/branch.js +216 -0
- package/dist/commands/checkout.js +74 -0
- package/dist/commands/ci.js +330 -0
- package/dist/commands/completion.js +227 -0
- package/dist/commands/diff.js +163 -0
- package/dist/commands/doctor.js +176 -0
- package/dist/commands/env.js +70 -0
- package/dist/commands/export.js +84 -0
- package/dist/commands/generate.js +180 -0
- package/dist/commands/history.js +77 -0
- package/dist/commands/import.js +122 -0
- package/dist/commands/init.js +162 -0
- package/dist/commands/integration.js +188 -0
- package/dist/commands/local.js +198 -0
- package/dist/commands/login.js +176 -0
- package/dist/commands/login.test.js +51 -0
- package/dist/commands/logs.js +121 -0
- package/dist/commands/profile.js +184 -0
- package/dist/commands/project.js +98 -0
- package/dist/commands/rollback.js +96 -0
- package/dist/commands/rotate.js +94 -0
- package/dist/commands/run.js +215 -0
- package/dist/commands/scan.js +127 -0
- package/dist/commands/secrets.js +265 -0
- package/dist/commands/simulate.js +92 -0
- package/dist/commands/status.js +94 -0
- package/dist/commands/template.js +276 -0
- package/dist/commands/ui.js +218 -0
- package/dist/commands/watch.js +121 -0
- package/dist/lib/api.js +172 -0
- package/dist/lib/api.test.js +89 -0
- package/dist/lib/audit.js +136 -0
- package/dist/lib/config.js +42 -0
- package/dist/lib/config.test.js +47 -0
- package/dist/lib/crypto.js +50 -0
- package/dist/lib/manifest.js +52 -0
- package/dist/lib/profiles.js +103 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 XtraSecurity
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# XtraSync CLI
|
|
2
|
+
|
|
3
|
+
**Secure Runtime Secret Injection**
|
|
4
|
+
|
|
5
|
+
XtraSync CLI is a powerful command-line tool designed to seamlessly and securely inject environment variables into your application at runtime. Say goodbye to scattered `.env` files and leaked secrets—XtraSync brings central secret management directly to your local development and CI/CD pipelines.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Install the CLI globally via npm:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g xtra-cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
1. **Login and Authenticate:**
|
|
18
|
+
Authenticate your machine with the XtraSecurity platform. You can log in via Single Sign-On (SSO), email, or an Access Key.
|
|
19
|
+
```bash
|
|
20
|
+
xtra login
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
2. **Initialize a Project:**
|
|
24
|
+
Bootstrap your project by creating an `.xtrarc` configuration file.
|
|
25
|
+
```bash
|
|
26
|
+
xtra init
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
3. **Run your Application securely:**
|
|
30
|
+
Inject secrets directly into your running process without ever touching a disk.
|
|
31
|
+
```bash
|
|
32
|
+
xtra run npm start
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Core Commands
|
|
36
|
+
|
|
37
|
+
### `xtra run [command]`
|
|
38
|
+
Run a command with secrets injected at runtime.
|
|
39
|
+
```bash
|
|
40
|
+
# Example: Inject production secrets and run the build script
|
|
41
|
+
xtra run -e production npm run build
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### `xtra secrets list`
|
|
45
|
+
List all secrets available for a given project and environment.
|
|
46
|
+
```bash
|
|
47
|
+
xtra secrets list -e staging
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### `xtra secrets set [KEY=VALUE...]`
|
|
51
|
+
Set one or more secrets.
|
|
52
|
+
```bash
|
|
53
|
+
xtra secrets set API_KEY=123x DB_PASS=secret
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `xtra env clone`
|
|
57
|
+
Clone secrets from one environment to another.
|
|
58
|
+
```bash
|
|
59
|
+
xtra env clone --from development --to production
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### `xtra local sync`
|
|
63
|
+
Pull cloud secrets to a `.env.local` file for offline development.
|
|
64
|
+
```bash
|
|
65
|
+
xtra local sync
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Advanced Usage
|
|
69
|
+
|
|
70
|
+
- **CI/CD Integration (`xtra ci`)**: Run headless operations in your pipelines.
|
|
71
|
+
- **Auditing (`xtra logs`)**: View your local audit trails and access history.
|
|
72
|
+
- **Profiles (`xtra profile`)**: Manage multiple connection setups and environments.
|
|
73
|
+
- **TUI Dashboard (`xtra ui`)**: Launch an interactive terminal user interface to manage secrets visually.
|
|
74
|
+
|
|
75
|
+
## Help & Documentation
|
|
76
|
+
|
|
77
|
+
For a complete list of commands and options, run:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
xtra --help
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
To see help for a specific command, use:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
xtra <command> --help
|
|
87
|
+
```
|
package/dist/bin/xtra.js
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const commander_1 = require("commander");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const login_1 = require("../commands/login");
|
|
41
|
+
const run_1 = require("../commands/run");
|
|
42
|
+
const secrets_1 = require("../commands/secrets");
|
|
43
|
+
const generate_1 = require("../commands/generate");
|
|
44
|
+
const status_1 = require("../commands/status");
|
|
45
|
+
const diff_1 = require("../commands/diff");
|
|
46
|
+
const rollback_1 = require("../commands/rollback");
|
|
47
|
+
const scan_1 = require("../commands/scan");
|
|
48
|
+
const logs_1 = require("../commands/logs");
|
|
49
|
+
const export_1 = require("../commands/export");
|
|
50
|
+
const import_1 = require("../commands/import");
|
|
51
|
+
const rotate_1 = require("../commands/rotate");
|
|
52
|
+
const audit_1 = require("../commands/audit");
|
|
53
|
+
const access_1 = require("../commands/access");
|
|
54
|
+
const branch_1 = require("../commands/branch");
|
|
55
|
+
const checkout_1 = require("../commands/checkout");
|
|
56
|
+
const project_1 = require("../commands/project");
|
|
57
|
+
const admin_1 = require("../commands/admin");
|
|
58
|
+
const integration_1 = require("../commands/integration");
|
|
59
|
+
const history_1 = require("../commands/history");
|
|
60
|
+
const env_1 = require("../commands/env");
|
|
61
|
+
const ui_1 = require("../commands/ui");
|
|
62
|
+
const completion_1 = require("../commands/completion");
|
|
63
|
+
const profile_1 = require("../commands/profile");
|
|
64
|
+
const profiles_1 = require("../lib/profiles");
|
|
65
|
+
const ci_1 = require("../commands/ci");
|
|
66
|
+
const template_1 = require("../commands/template");
|
|
67
|
+
const doctor_1 = require("../commands/doctor");
|
|
68
|
+
const init_1 = require("../commands/init");
|
|
69
|
+
const watch_1 = require("../commands/watch");
|
|
70
|
+
const simulate_1 = require("../commands/simulate");
|
|
71
|
+
const local_1 = require("../commands/local");
|
|
72
|
+
const program = new commander_1.Command();
|
|
73
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), "utf-8"));
|
|
74
|
+
program
|
|
75
|
+
.name("xtra")
|
|
76
|
+
.description("XtraSync CLI - Secure Runtime Secret Injection")
|
|
77
|
+
.version(pkg.version)
|
|
78
|
+
.option("--profile <name>", "Use a specific named profile for this command");
|
|
79
|
+
// Apply profile before any subcommand runs
|
|
80
|
+
program.hook("preSubcommand", (thisCommand) => {
|
|
81
|
+
const profileName = thisCommand.opts().profile;
|
|
82
|
+
if (profileName) {
|
|
83
|
+
try {
|
|
84
|
+
(0, profiles_1.useProfile)(profileName);
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
console.error(`\x1b[31mError: ${e.message}\x1b[0m`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
program.addCommand(login_1.loginCommand);
|
|
93
|
+
program.addCommand(run_1.runCommand);
|
|
94
|
+
program.addCommand(secrets_1.secretsCommand);
|
|
95
|
+
program.addCommand(generate_1.generateCommand);
|
|
96
|
+
program.addCommand(status_1.statusCommand);
|
|
97
|
+
program.addCommand(diff_1.diffCommand);
|
|
98
|
+
program.addCommand(rollback_1.rollbackCommand);
|
|
99
|
+
program.addCommand(scan_1.scanCommand);
|
|
100
|
+
program.addCommand(logs_1.logsCommand);
|
|
101
|
+
program.addCommand(export_1.exportCommand);
|
|
102
|
+
program.addCommand(import_1.importCommand);
|
|
103
|
+
program.addCommand(rotate_1.rotateCommand);
|
|
104
|
+
program.addCommand(audit_1.auditCommand);
|
|
105
|
+
program.addCommand(access_1.accessCommand);
|
|
106
|
+
program.addCommand(branch_1.branchCommand);
|
|
107
|
+
program.addCommand(checkout_1.checkoutCommand);
|
|
108
|
+
program.addCommand(project_1.projectCommand);
|
|
109
|
+
program.addCommand(admin_1.adminCommand);
|
|
110
|
+
program.addCommand(integration_1.integrationCommand);
|
|
111
|
+
program.addCommand(integration_1.kubernetesCommand);
|
|
112
|
+
program.addCommand(history_1.historyCommand);
|
|
113
|
+
program.addCommand(env_1.envCommand);
|
|
114
|
+
program.addCommand(ui_1.uiCommand);
|
|
115
|
+
program.addCommand(completion_1.completionCommand);
|
|
116
|
+
program.addCommand(profile_1.profileCommand);
|
|
117
|
+
program.addCommand(ci_1.ciCommand);
|
|
118
|
+
program.addCommand(template_1.templateCommand);
|
|
119
|
+
program.addCommand(doctor_1.doctorCommand);
|
|
120
|
+
program.addCommand(init_1.initCommand);
|
|
121
|
+
program.addCommand(watch_1.watchCommand);
|
|
122
|
+
program.addCommand(simulate_1.simulateCommand);
|
|
123
|
+
program.addCommand(local_1.localCommand);
|
|
124
|
+
program.parse(process.argv);
|
|
@@ -0,0 +1,107 @@
|
|
|
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.accessCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const api_1 = require("../lib/api");
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const ora_1 = __importDefault(require("ora"));
|
|
11
|
+
const table_1 = require("table");
|
|
12
|
+
const config_1 = require("../lib/config");
|
|
13
|
+
const logAuditSafe = (event, projectId, meta) => {
|
|
14
|
+
try {
|
|
15
|
+
const { logAudit } = require("../lib/audit");
|
|
16
|
+
logAudit(event, projectId, null, meta);
|
|
17
|
+
}
|
|
18
|
+
catch (e) { }
|
|
19
|
+
};
|
|
20
|
+
const safeError = (error) => error?.response?.data?.error || error?.message || "Unknown error";
|
|
21
|
+
exports.accessCommand = new commander_1.Command("access")
|
|
22
|
+
.description("Manage Just-in-Time access requests")
|
|
23
|
+
.addCommand(new commander_1.Command("request")
|
|
24
|
+
.description("Request access to a secret or project")
|
|
25
|
+
.option("-p, --project <projectId>", "Project ID")
|
|
26
|
+
.option("-s, --secret <secretId>", "Specific Secret ID (optional)")
|
|
27
|
+
.requiredOption("-d, --duration <minutes>", "Duration in minutes")
|
|
28
|
+
.requiredOption("-r, --reason <text>", "Reason for access")
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
let { project, secret, duration, reason } = options;
|
|
31
|
+
if (!project) {
|
|
32
|
+
project = (0, config_1.getConfigValue)("project");
|
|
33
|
+
}
|
|
34
|
+
if (!project) {
|
|
35
|
+
console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
const spinner = (0, ora_1.default)("Submitting access request...").start();
|
|
39
|
+
try {
|
|
40
|
+
const result = await api_1.api.requestAccess(project, reason, parseInt(duration), secret);
|
|
41
|
+
spinner.succeed(chalk_1.default.green(`Access request submitted! (ID: ${result.requestId})`));
|
|
42
|
+
console.log(chalk_1.default.yellow(`Status: ${result.status}`));
|
|
43
|
+
// Audit log
|
|
44
|
+
logAuditSafe("ACCESS_REQUESTED", project, { requestId: result.requestId, secretId: secret, duration, reason });
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
spinner.fail("Request failed");
|
|
48
|
+
console.error(chalk_1.default.red(safeError(error)));
|
|
49
|
+
}
|
|
50
|
+
}))
|
|
51
|
+
.addCommand(new commander_1.Command("list")
|
|
52
|
+
.description("List access requests")
|
|
53
|
+
.option("--pending", "Show pending approvals (for admins)", false)
|
|
54
|
+
.action(async (options) => {
|
|
55
|
+
try {
|
|
56
|
+
const mode = options.pending ? "pending" : "my";
|
|
57
|
+
const requests = await api_1.api.listAccessRequests(mode);
|
|
58
|
+
if (requests.length === 0) {
|
|
59
|
+
console.log(chalk_1.default.yellow("No requests found."));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const data = [
|
|
63
|
+
[chalk_1.default.bold("ID"), chalk_1.default.bold("User"), chalk_1.default.bold("Target"), chalk_1.default.bold("Duration"), chalk_1.default.bold("Reason"), chalk_1.default.bold("Status")]
|
|
64
|
+
];
|
|
65
|
+
requests.forEach((r) => {
|
|
66
|
+
const target = r.secret ? `Secret: ${r.secret.key}` : `Project: ${r.projectId}`;
|
|
67
|
+
data.push([
|
|
68
|
+
r.id,
|
|
69
|
+
r.user.email,
|
|
70
|
+
target,
|
|
71
|
+
`${r.duration}m`,
|
|
72
|
+
r.reason,
|
|
73
|
+
r.status === "pending" ? chalk_1.default.yellow(r.status) : r.status === "approved" ? chalk_1.default.green(r.status) : chalk_1.default.red(r.status)
|
|
74
|
+
]);
|
|
75
|
+
});
|
|
76
|
+
console.log((0, table_1.table)(data));
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error(chalk_1.default.red("Failed to list requests: " + safeError(error)));
|
|
80
|
+
}
|
|
81
|
+
}))
|
|
82
|
+
.addCommand(new commander_1.Command("approve")
|
|
83
|
+
.description("Approve or reject a request")
|
|
84
|
+
.argument("<requestId>", "Request ID")
|
|
85
|
+
.requiredOption("--decision <decision>", "approved or rejected")
|
|
86
|
+
.action(async (requestId, options) => {
|
|
87
|
+
// Validate decision value
|
|
88
|
+
const validDecisions = ["approved", "rejected"];
|
|
89
|
+
if (!validDecisions.includes(options.decision.toLowerCase())) {
|
|
90
|
+
console.error(chalk_1.default.red(`Invalid decision: '${options.decision}'. Must be 'approved' or 'rejected'.`));
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
const spinner = (0, ora_1.default)("Processing decision...").start();
|
|
94
|
+
try {
|
|
95
|
+
const result = await api_1.api.approveAccess(requestId, options.decision);
|
|
96
|
+
spinner.succeed(chalk_1.default.green(`Request ${options.decision}!`));
|
|
97
|
+
if (result.expiresAt) {
|
|
98
|
+
console.log(chalk_1.default.blue(`Access granted until: ${new Date(result.expiresAt).toLocaleString()}`));
|
|
99
|
+
}
|
|
100
|
+
// Audit log — privileged action
|
|
101
|
+
logAuditSafe(options.decision === "approved" ? "ACCESS_APPROVED" : "ACCESS_REJECTED", null, { requestId, decision: options.decision, expiresAt: result.expiresAt });
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
spinner.fail("Operation failed");
|
|
105
|
+
console.error(chalk_1.default.red(safeError(error)));
|
|
106
|
+
}
|
|
107
|
+
}));
|
|
@@ -0,0 +1,118 @@
|
|
|
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.adminCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const api_1 = require("../lib/api");
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const ora_1 = __importDefault(require("ora"));
|
|
11
|
+
const table_1 = require("table");
|
|
12
|
+
exports.adminCommand = new commander_1.Command("admin")
|
|
13
|
+
.description("Admin commands for user and role management");
|
|
14
|
+
// Role Management
|
|
15
|
+
const roleCommand = new commander_1.Command("role")
|
|
16
|
+
.description("Manage user roles and permissions");
|
|
17
|
+
const listRoles = async () => {
|
|
18
|
+
const spinner = (0, ora_1.default)("Fetching roles from server...").start();
|
|
19
|
+
try {
|
|
20
|
+
const roles = await api_1.api.getRoles();
|
|
21
|
+
spinner.stop();
|
|
22
|
+
console.log(chalk_1.default.bold("\nAvailable Roles:\n"));
|
|
23
|
+
const tableData = [
|
|
24
|
+
[chalk_1.default.bold("Role"), chalk_1.default.bold("Description"), chalk_1.default.bold("Permissions")]
|
|
25
|
+
];
|
|
26
|
+
roles.forEach((role) => {
|
|
27
|
+
tableData.push([
|
|
28
|
+
chalk_1.default.cyan(role.name),
|
|
29
|
+
role.description || "-",
|
|
30
|
+
Array.isArray(role.permissions) ? role.permissions.join(", ") : role.permissions || "-",
|
|
31
|
+
]);
|
|
32
|
+
});
|
|
33
|
+
console.log((0, table_1.table)(tableData));
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
spinner.fail("Failed to fetch roles");
|
|
37
|
+
const safeErr = error.response?.data?.error || error.message || "Unknown error";
|
|
38
|
+
console.error(chalk_1.default.red(safeErr));
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
roleCommand
|
|
42
|
+
.command("list")
|
|
43
|
+
.description("List all available roles")
|
|
44
|
+
.alias("ls")
|
|
45
|
+
.action(listRoles);
|
|
46
|
+
exports.adminCommand.addCommand(roleCommand);
|
|
47
|
+
// Legacy/Short support: `xtra admin roles` -> alias to `xtra admin role list`
|
|
48
|
+
exports.adminCommand
|
|
49
|
+
.command("roles")
|
|
50
|
+
.description("Alias for 'role list'")
|
|
51
|
+
.action(listRoles);
|
|
52
|
+
// List all users (admin only)
|
|
53
|
+
exports.adminCommand
|
|
54
|
+
.command("users")
|
|
55
|
+
.description("List all users with their roles")
|
|
56
|
+
.option("-t, --team <teamId>", "Filter by team ID")
|
|
57
|
+
.action(async (options) => {
|
|
58
|
+
const spinner = (0, ora_1.default)("Fetching users...").start();
|
|
59
|
+
try {
|
|
60
|
+
const users = await api_1.api.getUsers(options.team);
|
|
61
|
+
spinner.stop();
|
|
62
|
+
if (!users || users.length === 0) {
|
|
63
|
+
console.log(chalk_1.default.yellow("No users found."));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
console.log(chalk_1.default.bold("\nUsers:\n"));
|
|
67
|
+
const tableData = [
|
|
68
|
+
[chalk_1.default.bold("Email"), chalk_1.default.bold("Name"), chalk_1.default.bold("Role"), chalk_1.default.bold("Status")]
|
|
69
|
+
];
|
|
70
|
+
users.forEach((user) => {
|
|
71
|
+
const roleColor = user.role === "owner" || user.role === "admin"
|
|
72
|
+
? chalk_1.default.magenta
|
|
73
|
+
: user.role === "viewer"
|
|
74
|
+
? chalk_1.default.gray
|
|
75
|
+
: chalk_1.default.cyan;
|
|
76
|
+
tableData.push([
|
|
77
|
+
user.email,
|
|
78
|
+
user.name || "-",
|
|
79
|
+
roleColor(user.role),
|
|
80
|
+
user.status || "active"
|
|
81
|
+
]);
|
|
82
|
+
});
|
|
83
|
+
console.log((0, table_1.table)(tableData));
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
spinner.fail("Failed to fetch users");
|
|
87
|
+
console.error(chalk_1.default.red(error.message));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
// Set user role
|
|
91
|
+
exports.adminCommand
|
|
92
|
+
.command("set-role <email> <role>")
|
|
93
|
+
.description("Change a user's role (owner, admin, developer, viewer, guest)")
|
|
94
|
+
.option("-t, --team <teamId>", "Team ID (for team-specific role)")
|
|
95
|
+
.action(async (email, role, options) => {
|
|
96
|
+
const validRoles = ["owner", "admin", "developer", "viewer", "guest"];
|
|
97
|
+
if (!validRoles.includes(role.toLowerCase())) {
|
|
98
|
+
console.error(chalk_1.default.red(`Invalid role: ${role}`));
|
|
99
|
+
console.log(chalk_1.default.yellow(`Valid roles: ${validRoles.join(", ")}`));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
const spinner = (0, ora_1.default)(`Updating role for ${email}...`).start();
|
|
103
|
+
try {
|
|
104
|
+
await api_1.api.setUserRole(email, role.toLowerCase(), options.team);
|
|
105
|
+
spinner.succeed(chalk_1.default.green(`Role updated: ${email} → ${role}`));
|
|
106
|
+
// Audit log this privileged action
|
|
107
|
+
try {
|
|
108
|
+
const { logAudit } = require("../lib/audit");
|
|
109
|
+
logAudit("ADMIN_ROLE_CHANGE", null, null, { email, newRole: role, teamId: options.team });
|
|
110
|
+
}
|
|
111
|
+
catch (e) { }
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
spinner.fail("Failed to update role");
|
|
115
|
+
const safeErr = error.response?.data?.error || error.message || "Unknown error";
|
|
116
|
+
console.error(chalk_1.default.red(safeErr));
|
|
117
|
+
}
|
|
118
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
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.auditCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const api_1 = require("../lib/api");
|
|
9
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
10
|
+
const ora_1 = __importDefault(require("ora"));
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
exports.auditCommand = new commander_1.Command("audit")
|
|
13
|
+
.description("Manage and verify audit logs")
|
|
14
|
+
// Subcommand: Verify
|
|
15
|
+
.addCommand(new commander_1.Command("verify")
|
|
16
|
+
.description("Verify the integrity of the audit log chain (Tamper-Evident)")
|
|
17
|
+
.action(async () => {
|
|
18
|
+
const spinner = (0, ora_1.default)("Verifying audit chain integrity...").start();
|
|
19
|
+
try {
|
|
20
|
+
const result = await api_1.api.verifyAuditLogs();
|
|
21
|
+
if (result.valid) {
|
|
22
|
+
spinner.succeed(chalk_1.default.green(result.message));
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
spinner.fail(chalk_1.default.red("Verification Failed!"));
|
|
26
|
+
console.error(chalk_1.default.red(`Broken at Log ID: ${result.details?.brokenAtId}`));
|
|
27
|
+
console.error(chalk_1.default.red(`Reason: ${result.details?.reason}`));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
spinner.fail("Verification failed");
|
|
32
|
+
if (error.response?.data?.details) {
|
|
33
|
+
console.error(chalk_1.default.red(`Chain Broken: ${error.response.data.message}`));
|
|
34
|
+
console.error(chalk_1.default.yellow(`Details: ${JSON.stringify(error.response.data.details, null, 2)}`));
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
console.error(chalk_1.default.red(error.message));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}))
|
|
41
|
+
// Subcommand: Export
|
|
42
|
+
.addCommand(new commander_1.Command("export")
|
|
43
|
+
.description("Export audit logs for compliance (SOC2/ISO)")
|
|
44
|
+
.option("-f, --format <format>", "Output format (json, csv)", "json")
|
|
45
|
+
.option("--start <date>", "Start date (YYYY-MM-DD)")
|
|
46
|
+
.option("--end <date>", "End date (YYYY-MM-DD)")
|
|
47
|
+
.option("-o, --output <file>", "Output file path")
|
|
48
|
+
.action(async (options) => {
|
|
49
|
+
const { format, start, end, output } = options;
|
|
50
|
+
const spinner = (0, ora_1.default)(`Exporting audit logs (${format})...`).start();
|
|
51
|
+
try {
|
|
52
|
+
const data = await api_1.api.exportAuditLogs(format, start, end);
|
|
53
|
+
const content = typeof data === 'object' ? JSON.stringify(data, null, 2) : data;
|
|
54
|
+
if (output) {
|
|
55
|
+
fs_1.default.writeFileSync(output, content);
|
|
56
|
+
spinner.succeed(chalk_1.default.green(`Export saved to ${output}`));
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
spinner.stop();
|
|
60
|
+
console.log(content);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
spinner.fail("Export failed");
|
|
65
|
+
console.error(chalk_1.default.red(error.message));
|
|
66
|
+
}
|
|
67
|
+
}));
|