@upfluxhq/cli 0.1.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # @upfluxhq/cli
2
+
3
+ Command-line interface for managing Upflux OTA updates.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g @upfluxhq/cli
9
+ # or
10
+ pnpm add -g @upfluxhq/cli
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ # 1. Authenticate with your publish keys (supports multiple!)
17
+ upflux login --key <staging-publish-key>
18
+ upflux login --key <production-publish-key>
19
+
20
+ # 2. Upload a release (will prompt if multiple deployments)
21
+ upflux release --platform android --label v1.0.0 --mandatory
22
+
23
+ # 3. Check release status
24
+ upflux status
25
+ ```
26
+
27
+ ## Commands
28
+
29
+ ### `login`
30
+
31
+ Authenticate with the Upflux server. **Supports multiple credentials** - each login adds a new deployment.
32
+
33
+ ```bash
34
+ upflux login --key <publishKey>
35
+ ```
36
+
37
+ | Option | Description |
38
+ |--------|-------------|
39
+ | `-k, --key <publishKey>` | Publish key for authentication |
40
+
41
+ **Example output with multiple logins:**
42
+ ```
43
+ ✓ Login successful!
44
+ App ID: app-970384d3
45
+ Platform: android
46
+ Deployment: production
47
+
48
+ Stored credentials (2):
49
+ → 1. production (app-970384d3)
50
+ 2. staging (app-970384d3)
51
+ ```
52
+
53
+ ---
54
+
55
+ ### `logout`
56
+
57
+ Remove stored credentials.
58
+
59
+ ```bash
60
+ upflux logout [options]
61
+ ```
62
+
63
+ | Option | Description |
64
+ |--------|-------------|
65
+ | `--all` | Remove all credentials |
66
+
67
+ **Example:**
68
+ ```bash
69
+ # Interactive selection to remove one
70
+ upflux logout
71
+
72
+ # Remove all credentials
73
+ upflux logout --all
74
+ ```
75
+
76
+ ---
77
+
78
+ ### `release`
79
+
80
+ Bundle and upload a new release. **Prompts to select deployment** if multiple are stored.
81
+
82
+ ```bash
83
+ upflux release [options]
84
+ ```
85
+
86
+ | Option | Required | Description |
87
+ |--------|----------|-------------|
88
+ | `-l, --label <label>` | ✓ | Release label (e.g., v1.0.0) |
89
+ | `-p, --platform <platform>` | | Target platform (`ios` or `android`, auto-detected) |
90
+ | `-a, --app <appId>` | | Application ID (auto-detected) |
91
+ | `-d, --deployment <deploymentId>` | | Deployment ID (prompts if multiple) |
92
+ | `-e, --entry-file <path>` | | Entry file for bundler (default: `index.js`) |
93
+ | `-o, --output-dir <path>` | | Output directory for bundle (default: `./dist`) |
94
+ | `-b, --bundle <path>` | | Skip bundling, use existing bundle file |
95
+ | `--assets <paths...>` | | Skip auto-detection, use these asset files |
96
+ | `-m, --mandatory` | | Mark update as mandatory |
97
+ | `-r, --rollout <number>` | | Rollout percentage (0-100, default: 100) |
98
+ | `-v, --version-range <range>` | | Binary version range (e.g., >=1.0.0) |
99
+ | `--dev` | | Create development bundle |
100
+ | `--skip-bundle` | | Skip bundling, use existing files in output dir |
101
+
102
+ **Example with deployment selection:**
103
+ ```
104
+ $ upflux release --platform android --label v1.2.0
105
+
106
+ ? Select deployment to release to:
107
+ ❯ production (app-970384d3)
108
+ staging (app-970384d3)
109
+ ```
110
+
111
+ ---
112
+
113
+ ### `status`
114
+
115
+ Check release status for an app or deployment.
116
+
117
+ ```bash
118
+ upflux status [options]
119
+ ```
120
+
121
+ | Option | Description |
122
+ |--------|-------------|
123
+ | `-a, --app <appId>` | Filter by application ID |
124
+ | `-d, --deployment <deploymentId>` | Filter by deployment ID |
125
+ | `-n, --limit <number>` | Number of releases to show (default: 10) |
126
+
127
+ ---
128
+
129
+ ### `generate-manifest`
130
+
131
+ Fetch and display the manifest for a specific release.
132
+
133
+ ```bash
134
+ upflux generate-manifest [options]
135
+ ```
136
+
137
+ | Option | Required | Description |
138
+ |--------|----------|-------------|
139
+ | `-r, --release-id <id>` | ✓ | Release ID (MongoDB ObjectId) |
140
+ | `-o, --output <path>` | | Save manifest to file |
141
+
142
+ ---
143
+
144
+ ## Configuration
145
+
146
+ ### Environment Variables
147
+
148
+ | Variable | Description | Default |
149
+ |----------|-------------|---------|
150
+ | `UPFLUX_API_URL` | API server URL | `http://localhost:3000/api` |
151
+
152
+ ### Auth File
153
+
154
+ The CLI stores credentials in `.upflux` in the current directory:
155
+
156
+ ```json
157
+ {
158
+ "credentials": [
159
+ {
160
+ "key": "pk_xxx...",
161
+ "appId": "app-970384d3",
162
+ "platform": "android",
163
+ "deploymentId": "6933e683aa32fe18a1311bbb",
164
+ "deploymentName": "production"
165
+ },
166
+ {
167
+ "key": "pk_yyy...",
168
+ "appId": "app-970384d3",
169
+ "platform": "android",
170
+ "deploymentId": "6933e683aa32fe18a1311ccc",
171
+ "deploymentName": "staging"
172
+ }
173
+ ],
174
+ "activeIndex": 0
175
+ }
176
+ ```
177
+
178
+ ---
179
+
180
+ ## Development
181
+
182
+ ```bash
183
+ # Install dependencies
184
+ pnpm install
185
+
186
+ # Build
187
+ pnpm run build
188
+
189
+ # Run locally
190
+ node dist/index.js --help
191
+ ```
192
+
193
+ ## License
194
+
195
+ MIT
@@ -0,0 +1 @@
1
+ export declare const apiClient: import("axios").AxiosInstance;
@@ -0,0 +1,43 @@
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.apiClient = void 0;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const configStore_1 = require("../config/configStore");
9
+ /**
10
+ * Default API base URL (can be overridden via environment variable)
11
+ */
12
+ const DEFAULT_BASE_URL = "https://api.upflux.in/api"; //"http://localhost:3000/api";
13
+ console.log(process.env.UPFLUX_API_URL || DEFAULT_BASE_URL);
14
+ exports.apiClient = axios_1.default.create({
15
+ baseURL: process.env.UPFLUX_API_URL || DEFAULT_BASE_URL,
16
+ });
17
+ /**
18
+ * Request interceptor to add publish key authentication.
19
+ * Uses x-publish-key header as expected by the backend middleware.
20
+ */
21
+ exports.apiClient.interceptors.request.use((config) => {
22
+ const key = configStore_1.configStore.getAuthKey();
23
+ if (key) {
24
+ config.headers["x-publish-key"] = key;
25
+ }
26
+ return config;
27
+ });
28
+ /**
29
+ * Response interceptor for error handling
30
+ */
31
+ exports.apiClient.interceptors.response.use((response) => response, (error) => {
32
+ var _a;
33
+ if (error.response) {
34
+ // Server responded with error status
35
+ const message = ((_a = error.response.data) === null || _a === void 0 ? void 0 : _a.error) || error.response.statusText;
36
+ error.message = `API Error (${error.response.status}): ${message}`;
37
+ }
38
+ else if (error.request) {
39
+ // Request made but no response
40
+ error.message = "Unable to reach API server. Is it running?";
41
+ }
42
+ return Promise.reject(error);
43
+ });
@@ -0,0 +1,12 @@
1
+ import { Command } from "commander";
2
+ /**
3
+ * Generate Manifest Command
4
+ *
5
+ * Fetches the release manifest from the API and optionally saves to file.
6
+ *
7
+ * Usage:
8
+ * upflux generate-manifest --release-id <mongoId>
9
+ * upflux generate-manifest --release-id <mongoId> --output ./manifest.json
10
+ */
11
+ declare const generateManifest: Command;
12
+ export default generateManifest;
@@ -0,0 +1,152 @@
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
+ const commander_1 = require("commander");
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const chalk_1 = __importDefault(require("chalk"));
43
+ const apiClient_1 = require("../client/apiClient");
44
+ /**
45
+ * Validate MongoDB ObjectId format
46
+ */
47
+ function isValidObjectId(id) {
48
+ return /^[a-fA-F0-9]{24}$/.test(id);
49
+ }
50
+ /**
51
+ * Validate output path is writable
52
+ */
53
+ function isWritablePath(filePath) {
54
+ try {
55
+ const dir = path.dirname(filePath);
56
+ // Check if directory exists or can be created
57
+ if (!fs.existsSync(dir)) {
58
+ fs.mkdirSync(dir, { recursive: true });
59
+ }
60
+ // Check if we can write to this location
61
+ fs.accessSync(dir, fs.constants.W_OK);
62
+ return true;
63
+ }
64
+ catch (_a) {
65
+ return false;
66
+ }
67
+ }
68
+ /**
69
+ * Generate Manifest Command
70
+ *
71
+ * Fetches the release manifest from the API and optionally saves to file.
72
+ *
73
+ * Usage:
74
+ * upflux generate-manifest --release-id <mongoId>
75
+ * upflux generate-manifest --release-id <mongoId> --output ./manifest.json
76
+ */
77
+ const generateManifest = new commander_1.Command("generate-manifest")
78
+ .description("Generate a release manifest file")
79
+ .requiredOption("-r, --release-id <id>", "Release ID (MongoDB ObjectId)")
80
+ .option("-o, --output <path>", "Output file path (optional, prints to stdout if not specified)")
81
+ .action(async (opts) => {
82
+ var _a, _b, _c;
83
+ try {
84
+ const releaseId = (_a = opts.releaseId) === null || _a === void 0 ? void 0 : _a.trim();
85
+ // Validation 1: Release ID is provided
86
+ if (!releaseId) {
87
+ console.error(chalk_1.default.red("✗ Release ID is required"));
88
+ console.error(chalk_1.default.gray("Use 'upflux status' to list available releases and their IDs"));
89
+ process.exit(1);
90
+ }
91
+ // Validation 2: Release ID format (MongoDB ObjectId)
92
+ if (!isValidObjectId(releaseId)) {
93
+ console.error(chalk_1.default.red("✗ Invalid release ID format"));
94
+ console.error(chalk_1.default.gray("Release ID should be a 24-character MongoDB ObjectId"));
95
+ console.error(chalk_1.default.gray("Example: 693507a19f6d6a79267633e8"));
96
+ console.error(chalk_1.default.gray("\nUse 'upflux status' to find valid release IDs"));
97
+ process.exit(1);
98
+ }
99
+ // Validation 3: Output path is writable (if provided)
100
+ if (opts.output) {
101
+ const outputPath = path.resolve(opts.output);
102
+ if (!isWritablePath(outputPath)) {
103
+ console.error(chalk_1.default.red(`✗ Cannot write to: ${outputPath}`));
104
+ console.error(chalk_1.default.gray("Check that the directory exists and you have write permissions"));
105
+ process.exit(1);
106
+ }
107
+ }
108
+ console.log(chalk_1.default.blue(`⚡ Fetching manifest for release ${releaseId}...\n`));
109
+ // Fetch manifest from API
110
+ const response = await apiClient_1.apiClient.get(`/releases/${releaseId}/manifest`);
111
+ const manifest = response.data;
112
+ // Validation 4: Manifest data is valid
113
+ if (!manifest || !manifest.releaseLabel || !manifest.bundleUrl) {
114
+ console.error(chalk_1.default.red("✗ Received invalid manifest data from server"));
115
+ process.exit(1);
116
+ }
117
+ // Pretty-print the manifest
118
+ const manifestJson = JSON.stringify(manifest, null, 2);
119
+ if (opts.output) {
120
+ // Write to file
121
+ const outputPath = path.resolve(opts.output);
122
+ fs.writeFileSync(outputPath, manifestJson);
123
+ console.log(chalk_1.default.green(`✓ Manifest saved to: ${outputPath}`));
124
+ }
125
+ else {
126
+ // Print to stdout
127
+ console.log(chalk_1.default.white("Manifest:"));
128
+ console.log(chalk_1.default.gray("─────────────────────────────────"));
129
+ console.log(manifestJson);
130
+ console.log(chalk_1.default.gray("─────────────────────────────────"));
131
+ }
132
+ // Summary
133
+ console.log(chalk_1.default.green("\n✓ Manifest generated successfully!"));
134
+ console.log(` Label: ${manifest.releaseLabel}`);
135
+ console.log(` Bundle URL: ${manifest.bundleUrl}`);
136
+ console.log(` Assets: ${((_b = manifest.assets) === null || _b === void 0 ? void 0 : _b.length) || 0} file(s)`);
137
+ if (manifest.mandatory) {
138
+ console.log(chalk_1.default.red(` Mandatory: Yes`));
139
+ }
140
+ }
141
+ catch (error) {
142
+ if (((_c = error.response) === null || _c === void 0 ? void 0 : _c.status) === 404) {
143
+ console.error(chalk_1.default.red(`✗ Release not found: ${opts.releaseId}`));
144
+ console.error(chalk_1.default.gray("Use 'upflux status' to list available releases."));
145
+ }
146
+ else {
147
+ console.error(chalk_1.default.red(`✗ Failed to fetch manifest: ${error.message}`));
148
+ }
149
+ process.exit(1);
150
+ }
151
+ });
152
+ exports.default = generateManifest;
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ declare const login: Command;
3
+ export default login;
@@ -0,0 +1,124 @@
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
+ const commander_1 = require("commander");
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const configStore_1 = require("../config/configStore");
9
+ const apiClient_1 = require("../client/apiClient");
10
+ /**
11
+ * Validate publish key format
12
+ * Expected format: upflux_pub_<prefix>_<secret>
13
+ */
14
+ function validateKeyFormat(key) {
15
+ // Basic format check
16
+ if (!key || typeof key !== "string") {
17
+ return false;
18
+ }
19
+ // Check minimum length (at least 20 chars for a valid key)
20
+ if (key.length < 20) {
21
+ return false;
22
+ }
23
+ // Check for common publish key prefixes
24
+ const validPrefixes = ["upflux_pub_", "upflux_cli_", "pk_"];
25
+ const hasValidPrefix = validPrefixes.some(prefix => key.startsWith(prefix));
26
+ // Also allow generic keys that are alphanumeric with underscores
27
+ const isAlphanumeric = /^[a-zA-Z0-9_-]+$/.test(key);
28
+ return hasValidPrefix || isAlphanumeric;
29
+ }
30
+ /**
31
+ * Verify key with the server
32
+ */
33
+ async function verifyKeyWithServer(key) {
34
+ var _a, _b;
35
+ try {
36
+ // Temporarily set the key for this request
37
+ const response = await apiClient_1.apiClient.get("/keys/verify", {
38
+ headers: { "x-publish-key": key }
39
+ });
40
+ return {
41
+ valid: true,
42
+ appId: response.data.appId,
43
+ platform: response.data.platform,
44
+ deploymentId: response.data.deploymentId,
45
+ deploymentName: response.data.deploymentName,
46
+ };
47
+ }
48
+ catch (error) {
49
+ if (((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === 401) {
50
+ return { valid: false, error: "Invalid or expired key" };
51
+ }
52
+ if (((_b = error.response) === null || _b === void 0 ? void 0 : _b.status) === 403) {
53
+ return { valid: false, error: "Key does not have publish permissions" };
54
+ }
55
+ // Network error - fail the verification
56
+ return { valid: false, error: "Unable to reach server. Check your connection and try again." };
57
+ }
58
+ }
59
+ const login = new commander_1.Command("login")
60
+ .description("Authenticate with Upflux server")
61
+ .requiredOption("-k, --key <publishKey>", "Publish key for authentication")
62
+ .action(async (opts) => {
63
+ var _a;
64
+ try {
65
+ const key = (_a = opts.key) === null || _a === void 0 ? void 0 : _a.trim();
66
+ // Validation 1: Key is provided
67
+ if (!key) {
68
+ console.error(chalk_1.default.red("✗ Publish key is required"));
69
+ console.error(chalk_1.default.gray("Usage: upflux login --key <your-publish-key>"));
70
+ process.exit(1);
71
+ }
72
+ // Validation 2: Key format
73
+ if (!validateKeyFormat(key)) {
74
+ console.error(chalk_1.default.red("✗ Invalid key format"));
75
+ console.error(chalk_1.default.gray("Key should be at least 20 characters and alphanumeric"));
76
+ process.exit(1);
77
+ }
78
+ console.log(chalk_1.default.blue("🔐 Verifying with server..."));
79
+ // Validation 3: Server verification (mandatory)
80
+ const verification = await verifyKeyWithServer(key);
81
+ if (!verification.valid) {
82
+ console.error(chalk_1.default.red(`✗ ${verification.error}`));
83
+ process.exit(1);
84
+ }
85
+ if (!verification.appId || !verification.deploymentId) {
86
+ console.error(chalk_1.default.red("✗ Key verification returned incomplete data"));
87
+ process.exit(1);
88
+ }
89
+ // Create credential and save
90
+ const credential = {
91
+ key,
92
+ appId: verification.appId,
93
+ platform: verification.platform,
94
+ deploymentId: verification.deploymentId,
95
+ deploymentName: verification.deploymentName,
96
+ };
97
+ configStore_1.configStore.addCredential(credential);
98
+ console.log(chalk_1.default.green("✓ Login successful!"));
99
+ // Display saved credential info
100
+ const deploymentDisplay = verification.deploymentName
101
+ ? `${verification.deploymentName}`
102
+ : verification.deploymentId;
103
+ console.log(chalk_1.default.gray(` App ID: ${verification.appId}`));
104
+ if (verification.platform) {
105
+ console.log(chalk_1.default.gray(` Platform: ${verification.platform}`));
106
+ }
107
+ console.log(chalk_1.default.gray(` Deployment: ${deploymentDisplay}`));
108
+ // Show all stored credentials
109
+ const allCreds = configStore_1.configStore.getCredentials();
110
+ if (allCreds.length > 1) {
111
+ console.log(chalk_1.default.gray(`\n Stored credentials (${allCreds.length}):`));
112
+ allCreds.forEach((cred, i) => {
113
+ const name = cred.deploymentName || cred.deploymentId;
114
+ const marker = cred.deploymentId === verification.deploymentId ? chalk_1.default.green("→ ") : " ";
115
+ console.log(chalk_1.default.gray(` ${marker}${i + 1}. ${name} (${cred.appId})`));
116
+ });
117
+ }
118
+ }
119
+ catch (error) {
120
+ console.error(chalk_1.default.red(`✗ Login failed: ${error.message}`));
121
+ process.exit(1);
122
+ }
123
+ });
124
+ exports.default = login;
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ declare const logout: Command;
3
+ export default logout;
@@ -0,0 +1,83 @@
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
+ const commander_1 = require("commander");
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const inquirer_1 = __importDefault(require("inquirer"));
9
+ const configStore_1 = require("../config/configStore");
10
+ const logout = new commander_1.Command("logout")
11
+ .description("Remove stored credentials")
12
+ .option("--all", "Remove all credentials")
13
+ .action(async (opts) => {
14
+ try {
15
+ const credentials = configStore_1.configStore.getCredentials();
16
+ if (credentials.length === 0) {
17
+ console.log(chalk_1.default.yellow("No credentials stored."));
18
+ return;
19
+ }
20
+ if (opts.all) {
21
+ // Remove all credentials
22
+ configStore_1.configStore.removeAllCredentials();
23
+ console.log(chalk_1.default.green(`✓ Removed all ${credentials.length} credential(s).`));
24
+ return;
25
+ }
26
+ if (credentials.length === 1) {
27
+ // Only one credential, ask for confirmation
28
+ const { confirm } = await inquirer_1.default.prompt([
29
+ {
30
+ type: "confirm",
31
+ name: "confirm",
32
+ message: `Remove credential for ${credentials[0].deploymentName || credentials[0].deploymentId}?`,
33
+ default: false,
34
+ }
35
+ ]);
36
+ if (confirm) {
37
+ configStore_1.configStore.removeCredential(credentials[0].deploymentId);
38
+ console.log(chalk_1.default.green("✓ Credential removed."));
39
+ }
40
+ else {
41
+ console.log(chalk_1.default.gray("Cancelled."));
42
+ }
43
+ return;
44
+ }
45
+ // Multiple credentials - show selection
46
+ const choices = credentials.map((cred, i) => ({
47
+ name: `${cred.deploymentName || cred.deploymentId} (${cred.appId})`,
48
+ value: cred.deploymentId,
49
+ }));
50
+ const { deploymentId } = await inquirer_1.default.prompt([
51
+ {
52
+ type: "list",
53
+ name: "deploymentId",
54
+ message: "Select credential to remove:",
55
+ choices: [
56
+ ...choices,
57
+ new inquirer_1.default.Separator(),
58
+ { name: "Cancel", value: null },
59
+ ],
60
+ }
61
+ ]);
62
+ if (!deploymentId) {
63
+ console.log(chalk_1.default.gray("Cancelled."));
64
+ return;
65
+ }
66
+ configStore_1.configStore.removeCredential(deploymentId);
67
+ console.log(chalk_1.default.green("✓ Credential removed."));
68
+ // Show remaining credentials
69
+ const remaining = configStore_1.configStore.getCredentials();
70
+ if (remaining.length > 0) {
71
+ console.log(chalk_1.default.gray(`\n Remaining credentials (${remaining.length}):`));
72
+ remaining.forEach((cred, i) => {
73
+ const name = cred.deploymentName || cred.deploymentId;
74
+ console.log(chalk_1.default.gray(` ${i + 1}. ${name} (${cred.appId})`));
75
+ });
76
+ }
77
+ }
78
+ catch (error) {
79
+ console.error(chalk_1.default.red(`✗ Logout failed: ${error.message}`));
80
+ process.exit(1);
81
+ }
82
+ });
83
+ exports.default = logout;
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ declare const release: Command;
3
+ export default release;