@dishantlangayan/sc-cli-core 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,8 +20,11 @@ The ScCommand abstract class extends [@oclif/core's Command class](https://githu
20
20
  ## ScConnection Class
21
21
  The ScConnection class provide abstraction functions for Solace Cloud API REST calls. It handles the access token and base URL for each REST call, avoiding the need to set these on each Command.
22
22
 
23
+ ## OrgManager
24
+ The OrgManager class provides utility functions to store and retrieve Solace Cloud authentication information from user's home directory: `~/.sc/` or `%USERPROFILE%\sc\`. The implementation uses AES-256-GCM for authenticated encryption and provides machine-bound encryption that combines OS-level security (keychain) with machine-specific identifiers, making credentials non-transferable between machines.
25
+
23
26
  ## BrokerAuthManager
24
- The BrokerAuthManager class provides utility functions to store and retrieve broker SEMP management authentication information from user's home directory: `~/.sc/` or `%USERPROFILE%\sc\`. The implementation uses AES-256-GCM for authenticated encryption and provides machine-bound encryption that combines OS-level security (keychain) with machine-specific identifiers, making credentials non-transferable between machines.
27
+ The BrokerAuthManager class provides utility functions to store and retrieve broker SEMP management authentication information similar to the `OrgManager` class. It supports Basic and OAuth authentication schemes.
25
28
 
26
29
  # Contributing
27
30
  Contributions are encouraged! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
@@ -12,12 +12,12 @@ export declare class BrokerAuthEncryption {
12
12
  private static readonly SALT_LENGTH;
13
13
  private static readonly TAG_LENGTH;
14
14
  /**
15
- * Decrypt broker storage data
15
+ * Decrypt storage data
16
16
  * @param encryptedData - Encrypted data to decrypt
17
17
  * @param key - Decryption key
18
- * @returns Decrypted broker storage
18
+ * @returns Decrypted storage
19
19
  */
20
- static decrypt(encryptedData: EncryptedData, key: Buffer): Promise<BrokerAuthStorage>;
20
+ static decrypt<T = BrokerAuthStorage>(encryptedData: EncryptedData, key: Buffer): Promise<T>;
21
21
  /**
22
22
  * Derive encryption key from password using PBKDF2
23
23
  * @param password - User password
@@ -26,12 +26,12 @@ export declare class BrokerAuthEncryption {
26
26
  */
27
27
  static deriveKey(password: string, salt: Buffer): Promise<Buffer>;
28
28
  /**
29
- * Encrypt broker storage data
30
- * @param data - Broker storage to encrypt
29
+ * Encrypt storage data
30
+ * @param data - Storage to encrypt
31
31
  * @param key - Encryption key
32
32
  * @returns Encrypted data with metadata
33
33
  */
34
- static encrypt(data: BrokerAuthStorage, key: Buffer): Promise<EncryptedData>;
34
+ static encrypt<T = BrokerAuthStorage>(data: T, key: Buffer): Promise<EncryptedData>;
35
35
  /**
36
36
  * Generate cryptographically secure random salt
37
37
  * @returns Random salt buffer
@@ -15,10 +15,10 @@ export class BrokerAuthEncryption {
15
15
  static SALT_LENGTH = 32;
16
16
  static TAG_LENGTH = 16;
17
17
  /**
18
- * Decrypt broker storage data
18
+ * Decrypt storage data
19
19
  * @param encryptedData - Encrypted data to decrypt
20
20
  * @param key - Decryption key
21
- * @returns Decrypted broker storage
21
+ * @returns Decrypted storage
22
22
  */
23
23
  static async decrypt(encryptedData, key) {
24
24
  try {
@@ -60,8 +60,8 @@ export class BrokerAuthEncryption {
60
60
  }
61
61
  }
62
62
  /**
63
- * Encrypt broker storage data
64
- * @param data - Broker storage to encrypt
63
+ * Encrypt storage data
64
+ * @param data - Storage to encrypt
65
65
  * @param key - Encryption key
66
66
  * @returns Encrypted data with metadata
67
67
  */
@@ -65,7 +65,6 @@ export class KeychainService {
65
65
  */
66
66
  loadMachineId() {
67
67
  // Use require for CommonJS module (node-machine-id)
68
- // eslint-disable-next-line @typescript-eslint/no-require-imports
69
68
  return require('node-machine-id');
70
69
  }
71
70
  }
@@ -0,0 +1,104 @@
1
+ import { KeychainService } from './keychain.js';
2
+ import { type OrgConfig } from './org-types.js';
3
+ /**
4
+ * Manager for organization storage
5
+ * Handles encrypted storage of Solace Cloud organization credentials
6
+ */
7
+ export declare class OrgManager {
8
+ private static instance;
9
+ private readonly configDir;
10
+ private readonly configFile;
11
+ private encryptionKey;
12
+ private readonly keychainService;
13
+ private machineId;
14
+ private masterKey;
15
+ private storage;
16
+ private constructor();
17
+ /**
18
+ * Get singleton instance
19
+ * @param keychainService - Optional keychain service for testing
20
+ */
21
+ static getInstance(keychainService?: KeychainService): OrgManager;
22
+ /**
23
+ * Add a new organization configuration
24
+ * @param org - Organization configuration
25
+ */
26
+ addOrg(org: OrgConfig): Promise<void>;
27
+ /**
28
+ * Clear all organization configurations
29
+ */
30
+ clearAll(): Promise<void>;
31
+ /**
32
+ * Get all organization configurations
33
+ * @returns Array of all organization configurations
34
+ */
35
+ getAllOrgs(): Promise<OrgConfig[]>;
36
+ /**
37
+ * Get the default organization
38
+ * @returns Default organization or null if no default is set
39
+ */
40
+ getDefaultOrg(): Promise<null | OrgConfig>;
41
+ /**
42
+ * Get organization configuration by orgId or alias
43
+ * @param identifier - Organization ID or alias
44
+ * @returns Organization configuration or null if not found
45
+ */
46
+ getOrg(identifier: string): Promise<null | OrgConfig>;
47
+ /**
48
+ * Initialize the org manager with encryption key derived from OS keychain and machine ID
49
+ */
50
+ initialize(): Promise<void>;
51
+ /**
52
+ * List all organization identifiers (orgId or alias if available)
53
+ * @returns Array of organization identifiers
54
+ */
55
+ listOrgs(): Promise<string[]>;
56
+ /**
57
+ * Check if organization exists
58
+ * @param identifier - Organization ID or alias
59
+ * @returns true if organization exists
60
+ */
61
+ orgExists(identifier: string): Promise<boolean>;
62
+ /**
63
+ * Remove organization configuration
64
+ * @param identifier - Organization ID or alias to remove
65
+ */
66
+ removeOrg(identifier: string): Promise<void>;
67
+ /**
68
+ * Set an organization as the default
69
+ * @param identifier - Organization ID or alias to set as default
70
+ */
71
+ setDefaultOrg(identifier: string): Promise<void>;
72
+ /**
73
+ * Update existing organization configuration
74
+ * @param identifier - Organization ID or alias to update
75
+ * @param updates - Partial updates to apply
76
+ */
77
+ updateOrg(identifier: string, updates: Partial<Omit<OrgConfig, 'orgId'>>): Promise<void>;
78
+ /**
79
+ * Ensure manager is initialized
80
+ */
81
+ private ensureInitialized;
82
+ /**
83
+ * Check if config file exists
84
+ */
85
+ private fileExists;
86
+ /**
87
+ * Load storage from encrypted file
88
+ * @param combinedKey - Combined master key and machine ID for decryption
89
+ */
90
+ private loadStorage;
91
+ /**
92
+ * Save storage to encrypted file
93
+ */
94
+ private saveStorage;
95
+ /**
96
+ * Unset the default flag on all organizations
97
+ */
98
+ private unsetAllDefaults;
99
+ /**
100
+ * Validate organization configuration
101
+ * @param org - Organization to validate
102
+ */
103
+ private validateOrg;
104
+ }
@@ -0,0 +1,336 @@
1
+ import { mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { BrokerAuthEncryption } from './auth-encryption.js';
5
+ import { KeychainService } from './keychain.js';
6
+ import { OrgError, OrgErrorCode } from './org-types.js';
7
+ const SERVICE_NAME = 'local';
8
+ const KEY_NAME = 'sc-cli';
9
+ /**
10
+ * Manager for organization storage
11
+ * Handles encrypted storage of Solace Cloud organization credentials
12
+ */
13
+ export class OrgManager {
14
+ static instance = null;
15
+ configDir;
16
+ configFile;
17
+ encryptionKey = null;
18
+ keychainService;
19
+ machineId = null;
20
+ masterKey = null;
21
+ storage = null;
22
+ constructor(keychainService) {
23
+ const homeDirectory = homedir();
24
+ this.configDir = join(homeDirectory, '.sc');
25
+ this.configFile = join(this.configDir, 'orgs.json');
26
+ this.keychainService = keychainService ?? new KeychainService();
27
+ }
28
+ /**
29
+ * Get singleton instance
30
+ * @param keychainService - Optional keychain service for testing
31
+ */
32
+ static getInstance(keychainService) {
33
+ if (!OrgManager.instance) {
34
+ OrgManager.instance = new OrgManager(keychainService);
35
+ }
36
+ return OrgManager.instance;
37
+ }
38
+ /**
39
+ * Add a new organization configuration
40
+ * @param org - Organization configuration
41
+ */
42
+ async addOrg(org) {
43
+ this.ensureInitialized();
44
+ // Validate organization
45
+ this.validateOrg(org);
46
+ // Check if organization already exists (by orgId or alias)
47
+ const existingByOrgId = this.storage.orgs.find((o) => o.orgId === org.orgId);
48
+ if (existingByOrgId) {
49
+ throw new OrgError(`Organization '${org.orgId}' already exists`, OrgErrorCode.ORG_ALREADY_EXISTS);
50
+ }
51
+ if (org.alias) {
52
+ const existingByAlias = this.storage.orgs.find((o) => o.alias === org.alias);
53
+ if (existingByAlias) {
54
+ throw new OrgError(`Organization with alias '${org.alias}' already exists`, OrgErrorCode.ORG_ALREADY_EXISTS);
55
+ }
56
+ }
57
+ // If this org is being set as default, unset any existing default
58
+ if (org.isDefault) {
59
+ this.unsetAllDefaults();
60
+ }
61
+ // Add organization
62
+ this.storage.orgs.push(org);
63
+ // Save to file
64
+ await this.saveStorage();
65
+ }
66
+ /**
67
+ * Clear all organization configurations
68
+ */
69
+ async clearAll() {
70
+ this.ensureInitialized();
71
+ this.storage.orgs = [];
72
+ await this.saveStorage();
73
+ }
74
+ /**
75
+ * Get all organization configurations
76
+ * @returns Array of all organization configurations
77
+ */
78
+ async getAllOrgs() {
79
+ this.ensureInitialized();
80
+ return [...this.storage.orgs];
81
+ }
82
+ /**
83
+ * Get the default organization
84
+ * @returns Default organization or null if no default is set
85
+ */
86
+ async getDefaultOrg() {
87
+ this.ensureInitialized();
88
+ const org = this.storage.orgs.find((o) => o.isDefault === true);
89
+ return org ?? null;
90
+ }
91
+ /**
92
+ * Get organization configuration by orgId or alias
93
+ * @param identifier - Organization ID or alias
94
+ * @returns Organization configuration or null if not found
95
+ */
96
+ async getOrg(identifier) {
97
+ this.ensureInitialized();
98
+ const org = this.storage.orgs.find((o) => o.orgId === identifier || o.alias === identifier);
99
+ return org ?? null;
100
+ }
101
+ /**
102
+ * Initialize the org manager with encryption key derived from OS keychain and machine ID
103
+ */
104
+ async initialize() {
105
+ try {
106
+ // Get machine ID
107
+ this.machineId = this.keychainService.getMachineId();
108
+ // Get or create master key from OS keychain
109
+ this.masterKey = await this.keychainService.getPassword(KEY_NAME, SERVICE_NAME);
110
+ if (!this.masterKey) {
111
+ // Generate new master key and store in OS keychain
112
+ this.masterKey = this.keychainService.generateMasterKey();
113
+ await this.keychainService.setPassword(KEY_NAME, SERVICE_NAME, this.masterKey);
114
+ }
115
+ // Combine master key with machine ID for encryption
116
+ const combinedKey = `${this.masterKey}:${this.machineId}`;
117
+ // Try to load existing storage
118
+ const fileExists = await this.fileExists();
119
+ if (fileExists) {
120
+ // Load existing file and derive key from stored salt
121
+ await this.loadStorage(combinedKey);
122
+ }
123
+ else {
124
+ // Create new storage with new salt
125
+ const salt = BrokerAuthEncryption.generateSalt();
126
+ this.encryptionKey = await BrokerAuthEncryption.deriveKey(combinedKey, salt);
127
+ this.storage = {
128
+ orgs: [],
129
+ version: '1.0.0',
130
+ };
131
+ }
132
+ }
133
+ catch (error) {
134
+ if (error instanceof OrgError) {
135
+ throw error;
136
+ }
137
+ throw new OrgError('Failed to initialize org manager', OrgErrorCode.NOT_INITIALIZED, error);
138
+ }
139
+ }
140
+ /**
141
+ * List all organization identifiers (orgId or alias if available)
142
+ * @returns Array of organization identifiers
143
+ */
144
+ async listOrgs() {
145
+ this.ensureInitialized();
146
+ return this.storage.orgs.map((o) => o.alias ?? o.orgId);
147
+ }
148
+ /**
149
+ * Check if organization exists
150
+ * @param identifier - Organization ID or alias
151
+ * @returns true if organization exists
152
+ */
153
+ async orgExists(identifier) {
154
+ this.ensureInitialized();
155
+ return this.storage.orgs.some((o) => o.orgId === identifier || o.alias === identifier);
156
+ }
157
+ /**
158
+ * Remove organization configuration
159
+ * @param identifier - Organization ID or alias to remove
160
+ */
161
+ async removeOrg(identifier) {
162
+ this.ensureInitialized();
163
+ const index = this.storage.orgs.findIndex((o) => o.orgId === identifier || o.alias === identifier);
164
+ if (index === -1) {
165
+ throw new OrgError(`Organization '${identifier}' not found`, OrgErrorCode.ORG_NOT_FOUND);
166
+ }
167
+ // Remove organization
168
+ this.storage.orgs.splice(index, 1);
169
+ // Save to file
170
+ await this.saveStorage();
171
+ }
172
+ /**
173
+ * Set an organization as the default
174
+ * @param identifier - Organization ID or alias to set as default
175
+ */
176
+ async setDefaultOrg(identifier) {
177
+ this.ensureInitialized();
178
+ const index = this.storage.orgs.findIndex((o) => o.orgId === identifier || o.alias === identifier);
179
+ if (index === -1) {
180
+ throw new OrgError(`Organization '${identifier}' not found`, OrgErrorCode.ORG_NOT_FOUND);
181
+ }
182
+ // Unset all existing defaults
183
+ this.unsetAllDefaults();
184
+ // Set this org as default
185
+ this.storage.orgs[index].isDefault = true;
186
+ // Save to file
187
+ await this.saveStorage();
188
+ }
189
+ /**
190
+ * Update existing organization configuration
191
+ * @param identifier - Organization ID or alias to update
192
+ * @param updates - Partial updates to apply
193
+ */
194
+ async updateOrg(identifier, updates) {
195
+ this.ensureInitialized();
196
+ const index = this.storage.orgs.findIndex((o) => o.orgId === identifier || o.alias === identifier);
197
+ if (index === -1) {
198
+ throw new OrgError(`Organization '${identifier}' not found`, OrgErrorCode.ORG_NOT_FOUND);
199
+ }
200
+ const currentOrg = this.storage.orgs[index];
201
+ // Check if new alias conflicts with another org
202
+ if (updates.alias && updates.alias !== currentOrg.alias) {
203
+ const conflictingOrg = this.storage.orgs.find((o) => o.alias === updates.alias && o.orgId !== currentOrg.orgId);
204
+ if (conflictingOrg) {
205
+ throw new OrgError(`Organization with alias '${updates.alias}' already exists`, OrgErrorCode.ORG_ALREADY_EXISTS);
206
+ }
207
+ }
208
+ // If setting this org as default, unset any existing default
209
+ if (updates.isDefault === true) {
210
+ this.unsetAllDefaults();
211
+ }
212
+ // Merge updates
213
+ const updated = {
214
+ ...currentOrg,
215
+ ...updates,
216
+ };
217
+ // Validate updated organization
218
+ this.validateOrg(updated);
219
+ // Update organization
220
+ this.storage.orgs[index] = updated;
221
+ // Save to file
222
+ await this.saveStorage();
223
+ }
224
+ /**
225
+ * Ensure manager is initialized
226
+ */
227
+ ensureInitialized() {
228
+ if (!this.encryptionKey || !this.storage) {
229
+ throw new OrgError('OrgManager not initialized. Call initialize() first.', OrgErrorCode.NOT_INITIALIZED);
230
+ }
231
+ }
232
+ /**
233
+ * Check if config file exists
234
+ */
235
+ async fileExists() {
236
+ try {
237
+ await readFile(this.configFile);
238
+ return true;
239
+ }
240
+ catch {
241
+ return false;
242
+ }
243
+ }
244
+ /**
245
+ * Load storage from encrypted file
246
+ * @param combinedKey - Combined master key and machine ID for decryption
247
+ */
248
+ async loadStorage(combinedKey) {
249
+ try {
250
+ const fileContent = await readFile(this.configFile, 'utf8');
251
+ const encryptedData = JSON.parse(fileContent);
252
+ // Derive key from combined key and stored salt
253
+ const salt = Buffer.from(encryptedData.salt, 'base64');
254
+ this.encryptionKey = await BrokerAuthEncryption.deriveKey(combinedKey, salt);
255
+ // Decrypt storage
256
+ this.storage = await BrokerAuthEncryption.decrypt(encryptedData, this.encryptionKey);
257
+ }
258
+ catch (error) {
259
+ if (error instanceof OrgError) {
260
+ throw error;
261
+ }
262
+ throw new OrgError('Failed to load organization storage', OrgErrorCode.FILE_READ_ERROR, error);
263
+ }
264
+ }
265
+ /**
266
+ * Save storage to encrypted file
267
+ */
268
+ async saveStorage() {
269
+ try {
270
+ if (!this.masterKey || !this.machineId) {
271
+ throw new OrgError('Org manager not initialized', OrgErrorCode.NOT_INITIALIZED);
272
+ }
273
+ // Ensure directory exists
274
+ await mkdir(this.configDir, { mode: 0o700, recursive: true });
275
+ // Generate new salt and derive key for THIS save
276
+ const combinedKey = `${this.masterKey}:${this.machineId}`;
277
+ const newSalt = BrokerAuthEncryption.generateSalt();
278
+ const newKey = await BrokerAuthEncryption.deriveKey(combinedKey, newSalt);
279
+ // Encrypt data with the new key
280
+ const encrypted = await BrokerAuthEncryption.encrypt(this.storage, newKey);
281
+ // Update the salt in encrypted data to match the salt we used for key derivation
282
+ encrypted.salt = newSalt.toString('base64');
283
+ // Store the new key for next operation
284
+ this.encryptionKey = newKey;
285
+ // Write to temp file first (atomic write)
286
+ const jsonData = JSON.stringify(encrypted, null, 2);
287
+ const tempFile = `${this.configFile}.tmp`;
288
+ await writeFile(tempFile, jsonData, { mode: 0o600 });
289
+ // Atomic rename
290
+ await rename(tempFile, this.configFile);
291
+ // Set restrictive permissions (Unix only)
292
+ if (process.platform !== 'win32') {
293
+ // Already set via writeFile mode option
294
+ }
295
+ }
296
+ catch (error) {
297
+ // Clean up temp file if it exists
298
+ try {
299
+ await unlink(`${this.configFile}.tmp`);
300
+ }
301
+ catch {
302
+ // Ignore cleanup errors
303
+ }
304
+ if (error instanceof OrgError) {
305
+ throw error;
306
+ }
307
+ throw new OrgError('Failed to save organization storage', OrgErrorCode.FILE_WRITE_ERROR, error);
308
+ }
309
+ }
310
+ /**
311
+ * Unset the default flag on all organizations
312
+ */
313
+ unsetAllDefaults() {
314
+ for (const org of this.storage.orgs) {
315
+ org.isDefault = false;
316
+ }
317
+ }
318
+ /**
319
+ * Validate organization configuration
320
+ * @param org - Organization to validate
321
+ */
322
+ validateOrg(org) {
323
+ // Validate orgId
324
+ if (!org.orgId || org.orgId.trim() === '') {
325
+ throw new OrgError('Organization ID is required', OrgErrorCode.INVALID_ORG_ID);
326
+ }
327
+ // Validate accessToken
328
+ if (!org.accessToken || org.accessToken.trim() === '') {
329
+ throw new OrgError('Access token is required', OrgErrorCode.INVALID_ACCESS_TOKEN);
330
+ }
331
+ // Validate alias if provided
332
+ if (org.alias !== undefined && org.alias.trim() === '') {
333
+ throw new OrgError('Alias cannot be empty if provided', OrgErrorCode.INVALID_ORG_ID);
334
+ }
335
+ }
336
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Organization configuration
3
+ */
4
+ export interface OrgConfig {
5
+ accessToken: string;
6
+ alias?: string;
7
+ isDefault?: boolean;
8
+ orgId: string;
9
+ }
10
+ /**
11
+ * Storage format for organization configurations
12
+ */
13
+ export interface OrgStorage {
14
+ orgs: OrgConfig[];
15
+ version: string;
16
+ }
17
+ /**
18
+ * Error codes for organization operations
19
+ */
20
+ export declare enum OrgErrorCode {
21
+ DECRYPTION_FAILED = "DECRYPTION_FAILED",
22
+ ENCRYPTION_FAILED = "ENCRYPTION_FAILED",
23
+ FILE_READ_ERROR = "FILE_READ_ERROR",
24
+ FILE_WRITE_ERROR = "FILE_WRITE_ERROR",
25
+ INVALID_ACCESS_TOKEN = "INVALID_ACCESS_TOKEN",
26
+ INVALID_ORG_ID = "INVALID_ORG_ID",
27
+ NOT_INITIALIZED = "NOT_INITIALIZED",
28
+ ORG_ALREADY_EXISTS = "ORG_ALREADY_EXISTS",
29
+ ORG_NOT_FOUND = "ORG_NOT_FOUND"
30
+ }
31
+ /**
32
+ * Custom error class for organization operations
33
+ */
34
+ export declare class OrgError extends Error {
35
+ readonly code: OrgErrorCode;
36
+ readonly cause?: Error | undefined;
37
+ constructor(message: string, code: OrgErrorCode, cause?: Error | undefined);
38
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Error codes for organization operations
3
+ */
4
+ export var OrgErrorCode;
5
+ (function (OrgErrorCode) {
6
+ OrgErrorCode["DECRYPTION_FAILED"] = "DECRYPTION_FAILED";
7
+ OrgErrorCode["ENCRYPTION_FAILED"] = "ENCRYPTION_FAILED";
8
+ OrgErrorCode["FILE_READ_ERROR"] = "FILE_READ_ERROR";
9
+ OrgErrorCode["FILE_WRITE_ERROR"] = "FILE_WRITE_ERROR";
10
+ OrgErrorCode["INVALID_ACCESS_TOKEN"] = "INVALID_ACCESS_TOKEN";
11
+ OrgErrorCode["INVALID_ORG_ID"] = "INVALID_ORG_ID";
12
+ OrgErrorCode["NOT_INITIALIZED"] = "NOT_INITIALIZED";
13
+ OrgErrorCode["ORG_ALREADY_EXISTS"] = "ORG_ALREADY_EXISTS";
14
+ OrgErrorCode["ORG_NOT_FOUND"] = "ORG_NOT_FOUND";
15
+ })(OrgErrorCode || (OrgErrorCode = {}));
16
+ /**
17
+ * Custom error class for organization operations
18
+ */
19
+ export class OrgError extends Error {
20
+ code;
21
+ cause;
22
+ constructor(message, code, cause) {
23
+ super(message);
24
+ this.code = code;
25
+ this.cause = cause;
26
+ this.name = 'OrgError';
27
+ }
28
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dishantlangayan/sc-cli-core",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Base library for the Solace Cloud CLI and plugins",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Dishant Langayan",