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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -0
  3. package/dist/bin/xtra.js +124 -0
  4. package/dist/commands/access.js +107 -0
  5. package/dist/commands/admin.js +118 -0
  6. package/dist/commands/audit.js +67 -0
  7. package/dist/commands/branch.js +216 -0
  8. package/dist/commands/checkout.js +74 -0
  9. package/dist/commands/ci.js +330 -0
  10. package/dist/commands/completion.js +227 -0
  11. package/dist/commands/diff.js +163 -0
  12. package/dist/commands/doctor.js +176 -0
  13. package/dist/commands/env.js +70 -0
  14. package/dist/commands/export.js +84 -0
  15. package/dist/commands/generate.js +180 -0
  16. package/dist/commands/history.js +77 -0
  17. package/dist/commands/import.js +122 -0
  18. package/dist/commands/init.js +162 -0
  19. package/dist/commands/integration.js +188 -0
  20. package/dist/commands/local.js +198 -0
  21. package/dist/commands/login.js +176 -0
  22. package/dist/commands/login.test.js +51 -0
  23. package/dist/commands/logs.js +121 -0
  24. package/dist/commands/profile.js +184 -0
  25. package/dist/commands/project.js +98 -0
  26. package/dist/commands/rollback.js +96 -0
  27. package/dist/commands/rotate.js +94 -0
  28. package/dist/commands/run.js +215 -0
  29. package/dist/commands/scan.js +127 -0
  30. package/dist/commands/secrets.js +265 -0
  31. package/dist/commands/simulate.js +92 -0
  32. package/dist/commands/status.js +94 -0
  33. package/dist/commands/template.js +276 -0
  34. package/dist/commands/ui.js +218 -0
  35. package/dist/commands/watch.js +121 -0
  36. package/dist/lib/api.js +172 -0
  37. package/dist/lib/api.test.js +89 -0
  38. package/dist/lib/audit.js +136 -0
  39. package/dist/lib/config.js +42 -0
  40. package/dist/lib/config.test.js +47 -0
  41. package/dist/lib/crypto.js +50 -0
  42. package/dist/lib/manifest.js +52 -0
  43. package/dist/lib/profiles.js +103 -0
  44. package/package.json +67 -0
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.completionCommand = void 0;
40
+ const commander_1 = require("commander");
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const os = __importStar(require("os"));
44
+ const chalk_1 = __importDefault(require("chalk"));
45
+ // ─── All CLI top-level commands ───────────────────────────────────────────────
46
+ const ALL_COMMANDS = [
47
+ "login", "run", "secrets", "generate", "status", "diff", "rollback",
48
+ "scan", "logs", "export", "import", "rotate", "audit", "access",
49
+ "branch", "checkout", "project", "admin", "integration", "kubernetes",
50
+ "history", "env", "ui", "completion"
51
+ ];
52
+ const SUBCOMMANDS = {
53
+ secrets: ["list", "set", "link"],
54
+ branch: ["list", "create", "delete", "update"],
55
+ admin: ["users", "roles", "role", "set-role"],
56
+ access: ["request", "list", "approve"],
57
+ audit: ["verify", "export"],
58
+ integration: ["sync", "export", "apply"],
59
+ env: ["clone"],
60
+ project: ["set", "current"],
61
+ completion: ["bash", "zsh", "powershell", "install"],
62
+ };
63
+ // ─── Bash completion script ───────────────────────────────────────────────────
64
+ function bashScript() {
65
+ const subMap = Object.entries(SUBCOMMANDS)
66
+ .map(([cmd, subs]) => ` ${cmd}) echo "${subs.join(" ")}" ;;`)
67
+ .join("\n");
68
+ return `# xtra bash completion
69
+ # Add to ~/.bashrc: source <(xtra completion bash)
70
+ _xtra_completions() {
71
+ local cur prev words
72
+ cur="\${COMP_WORDS[COMP_CWORD]}"
73
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
74
+
75
+ if [ "\${COMP_CWORD}" -eq 1 ]; then
76
+ COMPREPLY=( $(compgen -W "${ALL_COMMANDS.join(" ")}" -- "$cur") )
77
+ return
78
+ fi
79
+
80
+ local subcommands=""
81
+ case "\${COMP_WORDS[1]}" in
82
+ ${subMap}
83
+ *) ;;
84
+ esac
85
+
86
+ if [ -n "$subcommands" ]; then
87
+ COMPREPLY=( $(compgen -W "$subcommands" -- "$cur") )
88
+ fi
89
+ }
90
+ complete -F _xtra_completions xtra
91
+ `;
92
+ }
93
+ // ─── Zsh completion script ────────────────────────────────────────────────────
94
+ function zshScript() {
95
+ const subDefs = Object.entries(SUBCOMMANDS)
96
+ .map(([cmd, subs]) => ` (${cmd})\n local subcmds=(${subs.map(s => `'${s}'`).join(" ")})\n _describe 'subcommand' subcmds ;;`)
97
+ .join("\n");
98
+ return `#compdef xtra
99
+ # xtra zsh completion
100
+ # Add to ~/.zshrc: source <(xtra completion zsh)
101
+ _xtra() {
102
+ local state
103
+ _arguments \\
104
+ '1: :->command' \\
105
+ '*: :->args'
106
+
107
+ case $state in
108
+ command)
109
+ local commands=(${ALL_COMMANDS.map(c => `'${c}'`).join(" ")})
110
+ _describe 'command' commands ;;
111
+ args)
112
+ case $words[2] in
113
+ ${subDefs}
114
+ esac ;;
115
+ esac
116
+ }
117
+ _xtra
118
+ `;
119
+ }
120
+ // ─── PowerShell completion script ────────────────────────────────────────────
121
+ function powershellScript() {
122
+ const subMap = Object.entries(SUBCOMMANDS)
123
+ .map(([cmd, subs]) => ` '${cmd}' { $subCommands = @(${subs.map(s => `'${s}'`).join(", ")}) }`)
124
+ .join("\n");
125
+ return `# xtra PowerShell completion
126
+ # Add to $PROFILE: xtra completion powershell | Invoke-Expression
127
+ Register-ArgumentCompleter -Native -CommandName xtra -ScriptBlock {
128
+ param($wordToComplete, $commandAst, $cursorPosition)
129
+ $tokens = $commandAst.CommandElements
130
+ $topCommands = @(${ALL_COMMANDS.map(c => `'${c}'`).join(", ")})
131
+
132
+ if ($tokens.Count -le 2) {
133
+ $topCommands | Where-Object { $_ -like "$wordToComplete*" } |
134
+ ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
135
+ return
136
+ }
137
+
138
+ $subCommands = @()
139
+ switch ($tokens[1]) {
140
+ ${subMap}
141
+ }
142
+ $subCommands | Where-Object { $_ -like "$wordToComplete*" } |
143
+ ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) }
144
+ }
145
+ `;
146
+ }
147
+ // ─── Install helper ───────────────────────────────────────────────────────────
148
+ function installCompletion(shell) {
149
+ const home = os.homedir();
150
+ let rcFile;
151
+ let snippet;
152
+ let script;
153
+ switch (shell) {
154
+ case "bash":
155
+ rcFile = path.join(home, ".bashrc");
156
+ snippet = `\n# xtra CLI completion\nsource <(xtra completion bash)\n`;
157
+ script = bashScript();
158
+ break;
159
+ case "zsh":
160
+ rcFile = path.join(home, ".zshrc");
161
+ snippet = `\n# xtra CLI completion\nsource <(xtra completion zsh)\n`;
162
+ script = zshScript();
163
+ break;
164
+ case "powershell":
165
+ rcFile = path.join(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
166
+ snippet = `\n# xtra CLI completion\nxtra completion powershell | Invoke-Expression\n`;
167
+ script = powershellScript();
168
+ break;
169
+ default:
170
+ console.error(chalk_1.default.red(`Unsupported shell: ${shell}. Use bash, zsh, or powershell.`));
171
+ process.exit(1);
172
+ }
173
+ // Write the completion script to a temp file path for reference
174
+ const scriptPath = path.join(home, `.xtra-completion.${shell}`);
175
+ fs.writeFileSync(scriptPath, script, "utf8");
176
+ // Append sourcing snippet to rc file if not already there
177
+ let rcContent = "";
178
+ try {
179
+ rcContent = fs.readFileSync(rcFile, "utf8");
180
+ }
181
+ catch (_) { }
182
+ if (rcContent.includes("xtra completion")) {
183
+ console.log(chalk_1.default.yellow(`Completion already installed in ${rcFile}. Skipping.`));
184
+ }
185
+ else {
186
+ fs.appendFileSync(rcFile, snippet, "utf8");
187
+ console.log(chalk_1.default.green(`✅ Completion installed!\n`));
188
+ console.log(` Script saved to: ${chalk_1.default.cyan(scriptPath)}`);
189
+ console.log(` Sourced in: ${chalk_1.default.cyan(rcFile)}`);
190
+ console.log(`\nRestart your shell or run: ${chalk_1.default.bold(`source ${rcFile}`)}`);
191
+ }
192
+ }
193
+ // ─── Commander command ────────────────────────────────────────────────────────
194
+ exports.completionCommand = new commander_1.Command("completion")
195
+ .description("Generate shell completion scripts (bash, zsh, powershell)")
196
+ .argument("[shell]", "Target shell: bash | zsh | powershell", "bash")
197
+ .option("--install", "Automatically install completion into your shell profile")
198
+ .addHelpText("after", `
199
+ Examples:
200
+ $ xtra completion bash # Print bash completion script
201
+ $ xtra completion zsh # Print zsh completion script
202
+ $ xtra completion powershell # Print PowerShell completion script
203
+ $ source <(xtra completion bash) # Activate in current session
204
+ $ xtra completion bash --install # Auto-install to ~/.bashrc
205
+ $ xtra completion powershell --install # Auto-install to $PROFILE
206
+ `)
207
+ .action((shell, options) => {
208
+ const normalizedShell = shell.toLowerCase();
209
+ if (options.install) {
210
+ installCompletion(normalizedShell);
211
+ return;
212
+ }
213
+ switch (normalizedShell) {
214
+ case "bash":
215
+ process.stdout.write(bashScript());
216
+ break;
217
+ case "zsh":
218
+ process.stdout.write(zshScript());
219
+ break;
220
+ case "powershell":
221
+ process.stdout.write(powershellScript());
222
+ break;
223
+ default:
224
+ console.error(chalk_1.default.red(`Unknown shell: '${shell}'. Use bash, zsh, or powershell.`));
225
+ process.exit(1);
226
+ }
227
+ });
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.diffCommand = void 0;
40
+ const commander_1 = require("commander");
41
+ const api_1 = require("../lib/api");
42
+ const chalk_1 = __importDefault(require("chalk"));
43
+ const ora_1 = __importDefault(require("ora"));
44
+ const config_1 = require("../lib/config");
45
+ const crypto_1 = require("../lib/crypto");
46
+ const diff = __importStar(require("diff"));
47
+ exports.diffCommand = new commander_1.Command("diff")
48
+ .description("Show differences between local cache and remote secrets")
49
+ .argument("[env1]", "Source Environment")
50
+ .argument("[env2]", "Target Environment")
51
+ .option("-p, --project <projectId>", "Project ID")
52
+ .option("-e, --env <environment>", "Environment (dev, stg, prod)", "dev")
53
+ .option("-b, --branch <branchName>", "Branch Name")
54
+ .option("--show", "Show actual value differences (sensitive)", false)
55
+ .action(async (env1, env2, options) => {
56
+ let { project, env, branch, show } = options;
57
+ // Use config fallback
58
+ if (!project) {
59
+ project = (0, config_1.getConfigValue)("project");
60
+ }
61
+ if (!branch) {
62
+ branch = (0, config_1.getConfigValue)("branch") || "main";
63
+ }
64
+ if (!project) {
65
+ console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
66
+ process.exit(1);
67
+ }
68
+ // Normalize Env Map
69
+ const envMap = { dev: "development", stg: "staging", prod: "production" };
70
+ if (env1 && env2) {
71
+ // COMPARE TWO REMOTE ENVIRONMENTS
72
+ const sourceEnv = envMap[env1] || env1;
73
+ const targetEnv = envMap[env2] || env2;
74
+ const spinner = (0, ora_1.default)(`Fetching secrets for ${sourceEnv} and ${targetEnv} (branch: ${branch})...`).start();
75
+ try {
76
+ const [sourceSecrets, targetSecrets] = await Promise.all([
77
+ api_1.api.getSecrets(project, sourceEnv, branch),
78
+ api_1.api.getSecrets(project, targetEnv, branch)
79
+ ]);
80
+ spinner.stop();
81
+ console.log(chalk_1.default.bold(`\nDiff Report (${sourceEnv} <-> ${targetEnv} @ ${branch}):\n`));
82
+ compareSecrets(sourceSecrets, targetSecrets, show);
83
+ }
84
+ catch (error) {
85
+ spinner.fail("Failed to fetch secrets");
86
+ console.error(chalk_1.default.red(error.message));
87
+ }
88
+ }
89
+ else {
90
+ // DEFAULT: COMPARE REMOTE VS LOCAL CACHE
91
+ const targetEnv = envMap[env] || env;
92
+ const spinner = (0, ora_1.default)(`Fetching remote state for ${targetEnv} (branch: ${branch})...`).start();
93
+ try {
94
+ const remoteSecrets = await api_1.api.getSecrets(project, targetEnv, branch);
95
+ let localSecrets = {};
96
+ try {
97
+ const cacheKey = `cache.${project}.${targetEnv}.${branch}`;
98
+ const encrypted = (0, config_1.getConfigValue)(cacheKey);
99
+ if (encrypted) {
100
+ const decryptedStr = (0, crypto_1.decrypt)(encrypted);
101
+ localSecrets = JSON.parse(decryptedStr);
102
+ }
103
+ }
104
+ catch (e) { }
105
+ spinner.stop();
106
+ console.log(chalk_1.default.bold(`\nDiff Report (Remote ${targetEnv} vs Local Cache):\n`));
107
+ compareSecrets(remoteSecrets, localSecrets, show); // Note: verify order or adjust message
108
+ // existing logic compared local (arg1) to remote (arg2)
109
+ // But usually diff is "What is in A that is not in B"
110
+ // Let's call a shared helper or inline it but consistent
111
+ // To match previous behavior: comparing LOCAL against REMOTE
112
+ // But wait, user wants to see what's different in Remote.
113
+ // Let's stick to the previous logic structure for consistency if I extract it.
114
+ // Actually, I'll just rewrite the comparison block to be generic.
115
+ }
116
+ catch (error) {
117
+ spinner.fail("Failed to run diff.");
118
+ console.error(chalk_1.default.red(error.message));
119
+ }
120
+ }
121
+ });
122
+ function compareSecrets(source, target, show) {
123
+ const allKeys = new Set([...Object.keys(source), ...Object.keys(target)]);
124
+ let hasDiff = false;
125
+ allKeys.forEach(key => {
126
+ const val1 = source[key];
127
+ const val2 = target[key];
128
+ if (val1 === val2)
129
+ return;
130
+ hasDiff = true;
131
+ if (val1 === undefined) {
132
+ // Present in Target, missing in Source
133
+ console.log(chalk_1.default.green(`+ ${key} (Only in Target)`));
134
+ if (show)
135
+ console.log(chalk_1.default.green(` + "${val2}"`));
136
+ }
137
+ else if (val2 === undefined) {
138
+ // Present in Source, missing in Target
139
+ console.log(chalk_1.default.red(`- ${key} (Only in Source)`));
140
+ if (show)
141
+ console.log(chalk_1.default.red(` - "${val1}"`));
142
+ }
143
+ else {
144
+ console.log(chalk_1.default.yellow(`~ ${key} (Modified)`));
145
+ if (show) {
146
+ const changes = diff.diffChars(val1, val2);
147
+ let diffStr = " ";
148
+ changes.forEach(part => {
149
+ const color = part.added ? chalk_1.default.green :
150
+ part.removed ? chalk_1.default.red : chalk_1.default.gray;
151
+ diffStr += color(part.value);
152
+ });
153
+ console.log(diffStr);
154
+ }
155
+ }
156
+ });
157
+ if (!hasDiff) {
158
+ console.log(chalk_1.default.green("✔ No differences found."));
159
+ }
160
+ else if (!show) {
161
+ console.log(chalk_1.default.gray("\n(Use --show to reveal secret values)"));
162
+ }
163
+ }
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.doctorCommand = void 0;
40
+ /**
41
+ * doctor.ts — Diagnose common xtra-cli setup issues
42
+ *
43
+ * Runs a series of self-diagnostic checks and reports
44
+ * pass / warn / fail for each one.
45
+ */
46
+ const commander_1 = require("commander");
47
+ const chalk_1 = __importDefault(require("chalk"));
48
+ const fs = __importStar(require("fs"));
49
+ const path = __importStar(require("path"));
50
+ const axios_1 = __importDefault(require("axios"));
51
+ const config_1 = require("../lib/config");
52
+ const PASS = chalk_1.default.green(" ✔");
53
+ const FAIL = chalk_1.default.red(" ✗");
54
+ const WARN = chalk_1.default.yellow(" ⚠");
55
+ async function runChecks() {
56
+ const checks = [];
57
+ const config = (0, config_1.getConfig)();
58
+ // ── 1. Node Version ─────────────────────────────────────────────────────────
59
+ const nodeMajor = parseInt(process.versions.node.split(".")[0]);
60
+ checks.push({
61
+ name: "Node.js Version",
62
+ status: nodeMajor >= 18 ? "pass" : "fail",
63
+ message: `v${process.versions.node} ${nodeMajor >= 18 ? "(OK)" : "(requires >=18)"}`,
64
+ });
65
+ // ── 2. Auth Token ────────────────────────────────────────────────────────────
66
+ const token = (0, config_1.getAuthToken)();
67
+ checks.push({
68
+ name: "Auth Token",
69
+ status: token ? "pass" : "fail",
70
+ message: token ? `Set (${token.substring(0, 8)}...)` : "Not set — run 'xtra login'",
71
+ });
72
+ // ── 3. API URL Configured ────────────────────────────────────────────────────
73
+ const apiUrl = config.apiUrl || process.env.XTRA_API_URL;
74
+ checks.push({
75
+ name: "API URL",
76
+ status: apiUrl ? "pass" : "warn",
77
+ message: apiUrl || "Using default (http://localhost:3000/api)",
78
+ });
79
+ // ── 4. API Reachability ──────────────────────────────────────────────────────
80
+ try {
81
+ const res = await axios_1.default.get(`${config.apiUrl}/health`, { timeout: 4000 });
82
+ checks.push({
83
+ name: "API Connectivity",
84
+ status: res.status === 200 ? "pass" : "warn",
85
+ message: `${config.apiUrl} → ${res.status} ${res.statusText}`,
86
+ });
87
+ }
88
+ catch (e) {
89
+ const msg = e.code === "ECONNREFUSED"
90
+ ? "Connection refused — is the server running?"
91
+ : e.code === "ETIMEDOUT"
92
+ ? "Timed out — server may be slow or unreachable"
93
+ : e.message;
94
+ checks.push({ name: "API Connectivity", status: "fail", message: msg });
95
+ }
96
+ // ── 5. Active Project ────────────────────────────────────────────────────────
97
+ const project = (0, config_1.getConfigValue)("project");
98
+ checks.push({
99
+ name: "Active Project",
100
+ status: project ? "pass" : "warn",
101
+ message: project ? project : "Not set — run 'xtra project set <id>'",
102
+ });
103
+ // ── 6. Active Branch ─────────────────────────────────────────────────────────
104
+ const branch = (0, config_1.getConfigValue)("branch");
105
+ checks.push({
106
+ name: "Active Branch",
107
+ status: "pass",
108
+ message: branch || "main (default)",
109
+ });
110
+ // ── 7. .xtrarc / xtra.json ─────────────────────────────────────────────────
111
+ const rcPath = path.join(process.cwd(), ".xtrarc");
112
+ const jsonPath = path.join(process.cwd(), "xtra.json");
113
+ const hasProjectConfig = fs.existsSync(rcPath) || fs.existsSync(jsonPath);
114
+ checks.push({
115
+ name: "Project Config (.xtrarc)",
116
+ status: hasProjectConfig ? "pass" : "warn",
117
+ message: hasProjectConfig
118
+ ? fs.existsSync(rcPath) ? ".xtrarc found" : "xtra.json found"
119
+ : "Not found — run 'xtra init' to create one",
120
+ });
121
+ // ── 8. .env file present ────────────────────────────────────────────────────
122
+ const envPath = path.join(process.cwd(), ".env");
123
+ checks.push({
124
+ name: ".env File",
125
+ status: "pass",
126
+ message: fs.existsSync(envPath) ? ".env found" : "No .env (not required if using xtra run)",
127
+ });
128
+ // ── 9. XTRA_MACHINE_TOKEN (for CI) ─────────────────────────────────────────
129
+ const machineToken = process.env.XTRA_MACHINE_TOKEN;
130
+ checks.push({
131
+ name: "CI Machine Token",
132
+ status: "pass", // Always pass — optional
133
+ message: machineToken ? "XTRA_MACHINE_TOKEN is set ✔" : "Not set (only needed for CI/CD pipelines)",
134
+ });
135
+ return checks;
136
+ }
137
+ exports.doctorCommand = new commander_1.Command("doctor")
138
+ .description("Diagnose common setup issues (connectivity, auth, config)")
139
+ .option("--json", "Output results as JSON", false)
140
+ .action(async (options) => {
141
+ if (!options.json) {
142
+ console.log(chalk_1.default.bold("\n🩺 xtra doctor — running diagnostics...\n"));
143
+ }
144
+ const checks = await runChecks();
145
+ if (options.json) {
146
+ process.stdout.write(JSON.stringify(checks, null, 2) + "\n");
147
+ return;
148
+ }
149
+ let failures = 0;
150
+ let warnings = 0;
151
+ for (const c of checks) {
152
+ const icon = c.status === "pass" ? PASS : c.status === "warn" ? WARN : FAIL;
153
+ const nameStr = chalk_1.default.bold(c.name.padEnd(22));
154
+ const msgStr = c.status === "fail"
155
+ ? chalk_1.default.red(c.message)
156
+ : c.status === "warn"
157
+ ? chalk_1.default.yellow(c.message)
158
+ : chalk_1.default.gray(c.message);
159
+ console.log(`${icon} ${nameStr}${msgStr}`);
160
+ if (c.status === "fail")
161
+ failures++;
162
+ if (c.status === "warn")
163
+ warnings++;
164
+ }
165
+ console.log();
166
+ if (failures === 0 && warnings === 0) {
167
+ console.log(chalk_1.default.green.bold(" ✅ All checks passed! Your CLI is ready to use."));
168
+ }
169
+ else {
170
+ if (failures > 0)
171
+ console.log(chalk_1.default.red(` ${failures} issue(s) need attention.`));
172
+ if (warnings > 0)
173
+ console.log(chalk_1.default.yellow(` ${warnings} warning(s) found.`));
174
+ }
175
+ console.log();
176
+ });
@@ -0,0 +1,70 @@
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.envCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const api_1 = require("../lib/api");
10
+ const config_1 = require("../lib/config");
11
+ const inquirer_1 = __importDefault(require("inquirer"));
12
+ exports.envCommand = new commander_1.Command("env");
13
+ exports.envCommand
14
+ .command("clone")
15
+ .description("Clone secrets from one environment to another")
16
+ .requiredOption("--from <env>", "Source environment (e.g. production)")
17
+ .requiredOption("--to <env>", "Destination environment (e.g. staging)")
18
+ .option("-p, --project <id>", "Project ID")
19
+ .option("-b, --branch <name>", "Branch Name")
20
+ .option("--overwrite", "Overwrite existing secrets", false)
21
+ .action(async (options) => {
22
+ try {
23
+ let projectId = options.project;
24
+ const branch = options.branch || (0, config_1.getConfigValue)("branch") || "main";
25
+ if (!projectId) {
26
+ projectId = (0, config_1.getConfigValue)("project");
27
+ if (!projectId) {
28
+ console.error(chalk_1.default.red("Error: Project ID is required. Use -p <id> or run 'xtra project set' first."));
29
+ process.exit(1);
30
+ }
31
+ }
32
+ const fromEnv = options.from.toLowerCase();
33
+ const toEnv = options.to.toLowerCase();
34
+ if (fromEnv === toEnv) {
35
+ console.error(chalk_1.default.red("Source and destination must be different."));
36
+ process.exit(1);
37
+ }
38
+ console.log(chalk_1.default.blue(`Cloning secrets from ${fromEnv} to ${toEnv} (branch: ${branch})...`));
39
+ if (options.overwrite) {
40
+ console.log(chalk_1.default.yellow("Warning: Existing secrets in destination will be overwritten!"));
41
+ }
42
+ const confirm = await inquirer_1.default.prompt([
43
+ {
44
+ type: "confirm",
45
+ name: "proceed",
46
+ message: "Are you sure you want to proceed?",
47
+ default: false
48
+ }
49
+ ]);
50
+ if (!confirm.proceed) {
51
+ console.log("Operation cancelled.");
52
+ return;
53
+ }
54
+ const result = await api_1.api.cloneEnvironment(projectId, fromEnv, toEnv, options.overwrite, branch);
55
+ if (result.success) {
56
+ console.log(chalk_1.default.green("\nClone successful!"));
57
+ console.log(`Copied: ${result.summary.copied}`);
58
+ console.log(`Updated: ${result.summary.updated} (overwrite: ${options.overwrite})`);
59
+ console.log(`Skipped: ${result.summary.skipped}`);
60
+ }
61
+ }
62
+ catch (error) {
63
+ if (error.response && error.response.data && error.response.data.error) {
64
+ console.error(chalk_1.default.red("Error cloning environment:"), error.response.data.error);
65
+ }
66
+ else {
67
+ console.error(chalk_1.default.red("Error cloning environment:"), error.message || error);
68
+ }
69
+ }
70
+ });