confluence-cli 1.25.0 → 1.26.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/bin/confluence.js CHANGED
@@ -4,7 +4,7 @@ const { program } = require('commander');
4
4
  const chalk = require('chalk');
5
5
  const inquirer = require('inquirer');
6
6
  const ConfluenceClient = require('../lib/confluence-client');
7
- const { getConfig, initConfig } = require('../lib/config');
7
+ const { getConfig, initConfig, listProfiles, setActiveProfile, deleteProfile, isValidProfileName } = require('../lib/config');
8
8
  const Analytics = require('../lib/analytics');
9
9
  const pkg = require('../package.json');
10
10
 
@@ -16,7 +16,13 @@ function buildPageUrl(config, path) {
16
16
  program
17
17
  .name('confluence')
18
18
  .description('CLI tool for Atlassian Confluence')
19
- .version(pkg.version);
19
+ .version(pkg.version)
20
+ .option('--profile <name>', 'Use a specific configuration profile');
21
+
22
+ // Helper: resolve profile name from global --profile flag
23
+ function getProfileName() {
24
+ return program.opts().profile || undefined;
25
+ }
20
26
 
21
27
  // Init command
22
28
  program
@@ -29,7 +35,8 @@ program
29
35
  .option('-e, --email <email>', 'Email or username for basic auth')
30
36
  .option('-t, --token <token>', 'API token')
31
37
  .action(async (options) => {
32
- await initConfig(options);
38
+ const profile = getProfileName();
39
+ await initConfig({ ...options, profile });
33
40
  });
34
41
 
35
42
  // Read command
@@ -40,7 +47,7 @@ program
40
47
  .action(async (pageId, options) => {
41
48
  const analytics = new Analytics();
42
49
  try {
43
- const client = new ConfluenceClient(getConfig());
50
+ const client = new ConfluenceClient(getConfig(getProfileName()));
44
51
  const content = await client.readPage(pageId, options.format);
45
52
  console.log(content);
46
53
  analytics.track('read', true);
@@ -58,7 +65,7 @@ program
58
65
  .action(async (pageId) => {
59
66
  const analytics = new Analytics();
60
67
  try {
61
- const client = new ConfluenceClient(getConfig());
68
+ const client = new ConfluenceClient(getConfig(getProfileName()));
62
69
  const info = await client.getPageInfo(pageId);
63
70
  console.log(chalk.blue('Page Information:'));
64
71
  console.log(`Title: ${chalk.green(info.title)}`);
@@ -85,7 +92,7 @@ program
85
92
  .action(async (query, options) => {
86
93
  const analytics = new Analytics();
87
94
  try {
88
- const client = new ConfluenceClient(getConfig());
95
+ const client = new ConfluenceClient(getConfig(getProfileName()));
89
96
  const results = await client.search(query, parseInt(options.limit), options.cql);
90
97
 
91
98
  if (results.length === 0) {
@@ -116,7 +123,7 @@ program
116
123
  .action(async () => {
117
124
  const analytics = new Analytics();
118
125
  try {
119
- const config = getConfig();
126
+ const config = getConfig(getProfileName());
120
127
  const client = new ConfluenceClient(config);
121
128
  const spaces = await client.getSpaces();
122
129
 
@@ -200,7 +207,7 @@ program
200
207
  .action(async (title, spaceKey, options) => {
201
208
  const analytics = new Analytics();
202
209
  try {
203
- const config = getConfig();
210
+ const config = getConfig(getProfileName());
204
211
  const client = new ConfluenceClient(config);
205
212
 
206
213
  let content = '';
@@ -243,7 +250,7 @@ program
243
250
  .action(async (title, parentId, options) => {
244
251
  const analytics = new Analytics();
245
252
  try {
246
- const config = getConfig();
253
+ const config = getConfig(getProfileName());
247
254
  const client = new ConfluenceClient(config);
248
255
 
249
256
  // Get parent page info to get space key
@@ -297,7 +304,7 @@ program
297
304
  throw new Error('At least one of --title, --file, or --content must be provided.');
298
305
  }
299
306
 
300
- const config = getConfig();
307
+ const config = getConfig(getProfileName());
301
308
  const client = new ConfluenceClient(config);
302
309
 
303
310
  let content = null; // Use null to indicate no content change
@@ -336,7 +343,7 @@ program
336
343
  .action(async (pageId, newParentId, options) => {
337
344
  const analytics = new Analytics();
338
345
  try {
339
- const config = getConfig();
346
+ const config = getConfig(getProfileName());
340
347
  const client = new ConfluenceClient(config);
341
348
  const result = await client.movePage(pageId, newParentId, options.title);
342
349
 
@@ -363,7 +370,7 @@ program
363
370
  .action(async (pageIdOrUrl, options) => {
364
371
  const analytics = new Analytics();
365
372
  try {
366
- const config = getConfig();
373
+ const config = getConfig(getProfileName());
367
374
  const client = new ConfluenceClient(config);
368
375
  const pageInfo = await client.getPageInfo(pageIdOrUrl);
369
376
 
@@ -406,7 +413,7 @@ program
406
413
  .action(async (pageId, options) => {
407
414
  const analytics = new Analytics();
408
415
  try {
409
- const config = getConfig();
416
+ const config = getConfig(getProfileName());
410
417
  const client = new ConfluenceClient(config);
411
418
  const pageData = await client.getPageForEdit(pageId);
412
419
 
@@ -443,7 +450,7 @@ program
443
450
  .action(async (title, options) => {
444
451
  const analytics = new Analytics();
445
452
  try {
446
- const config = getConfig();
453
+ const config = getConfig(getProfileName());
447
454
  const client = new ConfluenceClient(config);
448
455
  const pageInfo = await client.findPageByTitle(title, options.space);
449
456
 
@@ -473,7 +480,7 @@ program
473
480
  .action(async (pageId, options) => {
474
481
  const analytics = new Analytics();
475
482
  try {
476
- const config = getConfig();
483
+ const config = getConfig(getProfileName());
477
484
  const client = new ConfluenceClient(config);
478
485
  const maxResults = options.limit ? parseInt(options.limit, 10) : null;
479
486
  const pattern = options.pattern ? options.pattern.trim() : null;
@@ -605,7 +612,7 @@ program
605
612
 
606
613
  const fs = require('fs');
607
614
  const path = require('path');
608
- const config = getConfig();
615
+ const config = getConfig(getProfileName());
609
616
  const client = new ConfluenceClient(config);
610
617
 
611
618
  const resolvedFiles = files.map((filePath) => ({
@@ -652,7 +659,7 @@ program
652
659
  .action(async (pageId, attachmentId, options) => {
653
660
  const analytics = new Analytics();
654
661
  try {
655
- const config = getConfig();
662
+ const config = getConfig(getProfileName());
656
663
  const client = new ConfluenceClient(config);
657
664
 
658
665
  if (!options.yes) {
@@ -696,7 +703,7 @@ program
696
703
  .action(async (pageId, options) => {
697
704
  const analytics = new Analytics();
698
705
  try {
699
- const config = getConfig();
706
+ const config = getConfig(getProfileName());
700
707
  const client = new ConfluenceClient(config);
701
708
 
702
709
  const format = (options.format || 'text').toLowerCase();
@@ -766,7 +773,7 @@ program
766
773
  .action(async (pageId, key, options) => {
767
774
  const analytics = new Analytics();
768
775
  try {
769
- const config = getConfig();
776
+ const config = getConfig(getProfileName());
770
777
  const client = new ConfluenceClient(config);
771
778
 
772
779
  const format = (options.format || 'text').toLowerCase();
@@ -802,7 +809,7 @@ program
802
809
  .action(async (pageId, key, options) => {
803
810
  const analytics = new Analytics();
804
811
  try {
805
- const config = getConfig();
812
+ const config = getConfig(getProfileName());
806
813
  const client = new ConfluenceClient(config);
807
814
 
808
815
  if (!options.value && !options.file) {
@@ -858,7 +865,7 @@ program
858
865
  .action(async (pageId, key, options) => {
859
866
  const analytics = new Analytics();
860
867
  try {
861
- const config = getConfig();
868
+ const config = getConfig(getProfileName());
862
869
  const client = new ConfluenceClient(config);
863
870
 
864
871
  if (!options.yes) {
@@ -904,7 +911,7 @@ program
904
911
  .action(async (pageId, options) => {
905
912
  const analytics = new Analytics();
906
913
  try {
907
- const config = getConfig();
914
+ const config = getConfig(getProfileName());
908
915
  const client = new ConfluenceClient(config);
909
916
 
910
917
  const format = (options.format || 'text').toLowerCase();
@@ -1059,7 +1066,7 @@ program
1059
1066
  const analytics = new Analytics();
1060
1067
  let location = null;
1061
1068
  try {
1062
- const config = getConfig();
1069
+ const config = getConfig(getProfileName());
1063
1070
  const client = new ConfluenceClient(config);
1064
1071
 
1065
1072
  let content = '';
@@ -1173,7 +1180,7 @@ program
1173
1180
  .action(async (commentId, options) => {
1174
1181
  const analytics = new Analytics();
1175
1182
  try {
1176
- const config = getConfig();
1183
+ const config = getConfig(getProfileName());
1177
1184
  const client = new ConfluenceClient(config);
1178
1185
 
1179
1186
  if (!options.yes) {
@@ -1225,7 +1232,7 @@ program
1225
1232
  .action(async (pageId, options) => {
1226
1233
  const analytics = new Analytics();
1227
1234
  try {
1228
- const config = getConfig();
1235
+ const config = getConfig(getProfileName());
1229
1236
  const client = new ConfluenceClient(config);
1230
1237
  const fs = require('fs');
1231
1238
  const path = require('path');
@@ -1591,7 +1598,7 @@ program
1591
1598
  .action(async (sourcePageId, targetParentId, newTitle, options) => {
1592
1599
  const analytics = new Analytics();
1593
1600
  try {
1594
- const config = getConfig();
1601
+ const config = getConfig(getProfileName());
1595
1602
  const client = new ConfluenceClient(config);
1596
1603
 
1597
1604
  // Parse numeric flags with safe fallbacks
@@ -1711,7 +1718,7 @@ program
1711
1718
  .action(async (pageId, options) => {
1712
1719
  const analytics = new Analytics();
1713
1720
  try {
1714
- const config = getConfig();
1721
+ const config = getConfig(getProfileName());
1715
1722
  const client = new ConfluenceClient(config);
1716
1723
 
1717
1724
  // Extract page ID from URL if needed
@@ -1852,8 +1859,83 @@ function printTree(nodes, config, options, depth = 1) {
1852
1859
  });
1853
1860
  }
1854
1861
 
1862
+ // Profile management commands
1863
+ const profileCmd = program
1864
+ .command('profile')
1865
+ .description('Manage configuration profiles');
1866
+
1867
+ profileCmd
1868
+ .command('list')
1869
+ .description('List all configuration profiles')
1870
+ .action(() => {
1871
+ const { profiles } = listProfiles();
1872
+ if (profiles.length === 0) {
1873
+ console.log(chalk.yellow('No profiles configured. Run "confluence init" to create one.'));
1874
+ return;
1875
+ }
1876
+ console.log(chalk.blue('Configuration profiles:\n'));
1877
+ profiles.forEach(p => {
1878
+ const marker = p.active ? chalk.green(' (active)') : '';
1879
+ console.log(` ${p.active ? chalk.green('*') : ' '} ${chalk.cyan(p.name)}${marker} - ${chalk.gray(p.domain)}`);
1880
+ });
1881
+ });
1882
+
1883
+ profileCmd
1884
+ .command('use <name>')
1885
+ .description('Set the active configuration profile')
1886
+ .action((name) => {
1887
+ try {
1888
+ setActiveProfile(name);
1889
+ console.log(chalk.green(`Switched to profile "${name}"`));
1890
+ } catch (error) {
1891
+ console.error(chalk.red('Error:'), error.message);
1892
+ process.exit(1);
1893
+ }
1894
+ });
1895
+
1896
+ profileCmd
1897
+ .command('add <name>')
1898
+ .description('Add a new configuration profile interactively')
1899
+ .option('-d, --domain <domain>', 'Confluence domain')
1900
+ .option('--protocol <protocol>', 'Protocol (http or https)')
1901
+ .option('-p, --api-path <path>', 'REST API path')
1902
+ .option('-a, --auth-type <type>', 'Authentication type (basic or bearer)')
1903
+ .option('-e, --email <email>', 'Email or username for basic auth')
1904
+ .option('-t, --token <token>', 'API token')
1905
+ .action(async (name, options) => {
1906
+ if (!isValidProfileName(name)) {
1907
+ console.error(chalk.red('Invalid profile name. Use only letters, numbers, hyphens, and underscores.'));
1908
+ process.exit(1);
1909
+ }
1910
+ await initConfig({ ...options, profile: name });
1911
+ });
1912
+
1913
+ profileCmd
1914
+ .command('remove <name>')
1915
+ .description('Remove a configuration profile')
1916
+ .action(async (name) => {
1917
+ try {
1918
+ const { confirmed } = await inquirer.prompt([{
1919
+ type: 'confirm',
1920
+ name: 'confirmed',
1921
+ message: `Delete profile "${name}"?`,
1922
+ default: false
1923
+ }]);
1924
+ if (!confirmed) {
1925
+ console.log(chalk.yellow('Cancelled.'));
1926
+ return;
1927
+ }
1928
+ deleteProfile(name);
1929
+ console.log(chalk.green(`Profile "${name}" removed.`));
1930
+ } catch (error) {
1931
+ console.error(chalk.red('Error:'), error.message);
1932
+ process.exit(1);
1933
+ }
1934
+ });
1935
+
1855
1936
  // Exported for testing
1856
1937
  module.exports = {
1938
+ program,
1857
1939
  _test: {
1858
1940
  EXPORT_MARKER,
1859
1941
  writeExportMarker,
package/bin/index.js CHANGED
@@ -21,4 +21,9 @@ if (!nodeVersion.startsWith('v') ||
21
21
  }
22
22
 
23
23
  // Load the main CLI application
24
- require('./confluence.js');
24
+ const { program } = require('./confluence.js');
25
+
26
+ if (process.argv.length <= 2) {
27
+ program.help({ error: false });
28
+ }
29
+ program.parse(process.argv);
package/lib/config.js CHANGED
@@ -6,12 +6,15 @@ const chalk = require('chalk');
6
6
 
7
7
  const CONFIG_DIR = path.join(os.homedir(), '.confluence-cli');
8
8
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
9
+ const DEFAULT_PROFILE = 'default';
9
10
 
10
11
  const AUTH_CHOICES = [
11
12
  { name: 'Basic (credentials)', value: 'basic' },
12
13
  { name: 'Bearer token', value: 'bearer' }
13
14
  ];
14
15
 
16
+ const isValidProfileName = (name) => /^[a-zA-Z0-9_-]+$/.test(name);
17
+
15
18
  const requiredInput = (label) => (input) => {
16
19
  if (!input || !input.trim()) {
17
20
  return `${label} is required`;
@@ -68,6 +71,47 @@ const normalizeApiPath = (rawValue, domain) => {
68
71
  return withoutTrailing || inferApiPath(domain);
69
72
  };
70
73
 
74
+ // Read config file with backward compatibility for old flat format
75
+ function readConfigFile() {
76
+ if (!fs.existsSync(CONFIG_FILE)) {
77
+ return null;
78
+ }
79
+
80
+ try {
81
+ const raw = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
82
+
83
+ // Detect old flat format (has domain at top level, no profiles key)
84
+ if (raw.domain && !raw.profiles) {
85
+ const profile = {
86
+ domain: raw.domain,
87
+ protocol: raw.protocol,
88
+ apiPath: raw.apiPath,
89
+ token: raw.token,
90
+ authType: raw.authType
91
+ };
92
+ if (raw.email) {
93
+ profile.email = raw.email;
94
+ }
95
+ return {
96
+ activeProfile: DEFAULT_PROFILE,
97
+ profiles: { [DEFAULT_PROFILE]: profile }
98
+ };
99
+ }
100
+
101
+ return raw;
102
+ } catch {
103
+ return null;
104
+ }
105
+ }
106
+
107
+ // Write the full multi-profile config structure
108
+ function saveConfigFile(data) {
109
+ if (!fs.existsSync(CONFIG_DIR)) {
110
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
111
+ }
112
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(data, null, 2));
113
+ }
114
+
71
115
  // Helper function to validate CLI-provided options
72
116
  const validateCliOptions = (options) => {
73
117
  const errors = [];
@@ -115,23 +159,36 @@ const validateCliOptions = (options) => {
115
159
  };
116
160
 
117
161
  // Helper function to save configuration with validation
118
- const saveConfig = (configData) => {
119
- if (!fs.existsSync(CONFIG_DIR)) {
120
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
121
- }
122
-
162
+ const saveConfig = (configData, profileName) => {
123
163
  const config = {
124
164
  domain: configData.domain.trim(),
125
165
  protocol: normalizeProtocol(configData.protocol),
126
166
  apiPath: normalizeApiPath(configData.apiPath, configData.domain),
127
167
  token: configData.token.trim(),
128
- authType: configData.authType,
129
- email: configData.authType === 'basic' && configData.email ? configData.email.trim() : undefined
168
+ authType: configData.authType
130
169
  };
131
170
 
132
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
171
+ if (configData.authType === 'basic' && configData.email) {
172
+ config.email = configData.email.trim();
173
+ }
174
+
175
+ // Read existing config file (or create new structure)
176
+ const fileData = readConfigFile() || { activeProfile: DEFAULT_PROFILE, profiles: {} };
177
+
178
+ const targetProfile = profileName || fileData.activeProfile || DEFAULT_PROFILE;
179
+ fileData.profiles[targetProfile] = config;
180
+
181
+ // If this is the first profile, make it active
182
+ if (!fileData.activeProfile || !fileData.profiles[fileData.activeProfile]) {
183
+ fileData.activeProfile = targetProfile;
184
+ }
185
+
186
+ saveConfigFile(fileData);
133
187
 
134
188
  console.log(chalk.green('✅ Configuration saved successfully!'));
189
+ if (profileName) {
190
+ console.log(`Profile: ${chalk.cyan(targetProfile)}`);
191
+ }
135
192
  console.log(`Config file location: ${chalk.gray(CONFIG_FILE)}`);
136
193
  console.log(chalk.yellow('\n💡 Tip: You can regenerate this config anytime by running "confluence init"'));
137
194
  };
@@ -232,6 +289,14 @@ const promptForMissingValues = async (providedValues) => {
232
289
  };
233
290
 
234
291
  async function initConfig(cliOptions = {}) {
292
+ const profileName = cliOptions.profile;
293
+
294
+ // Validate profile name if provided
295
+ if (profileName && !isValidProfileName(profileName)) {
296
+ console.error(chalk.red('❌ Invalid profile name. Use only letters, numbers, hyphens, and underscores.'));
297
+ process.exit(1);
298
+ }
299
+
235
300
  // Extract provided values from CLI options
236
301
  const providedValues = {
237
302
  protocol: cliOptions.protocol,
@@ -248,6 +313,9 @@ async function initConfig(cliOptions = {}) {
248
313
  if (!hasCliOptions) {
249
314
  // Interactive mode: no CLI options provided
250
315
  console.log(chalk.blue('🚀 Confluence CLI Configuration'));
316
+ if (profileName) {
317
+ console.log(`Profile: ${chalk.cyan(profileName)}`);
318
+ }
251
319
  console.log('Please provide your Confluence connection details:\n');
252
320
 
253
321
  const answers = await inquirer.prompt([
@@ -307,7 +375,7 @@ async function initConfig(cliOptions = {}) {
307
375
  }
308
376
  ]);
309
377
 
310
- saveConfig(answers);
378
+ saveConfig(answers, profileName);
311
379
  return;
312
380
  }
313
381
 
@@ -325,8 +393,8 @@ async function initConfig(cliOptions = {}) {
325
393
  // Check if all required values are provided for non-interactive mode
326
394
  // Non-interactive requires: domain, token, and either authType or email (for inference)
327
395
  const hasRequiredValues = Boolean(
328
- providedValues.domain &&
329
- providedValues.token &&
396
+ providedValues.domain &&
397
+ providedValues.token &&
330
398
  (providedValues.authType || providedValues.email)
331
399
  );
332
400
 
@@ -341,7 +409,7 @@ async function initConfig(cliOptions = {}) {
341
409
 
342
410
  const normalizedAuthType = normalizeAuthType(inferredAuthType, Boolean(providedValues.email));
343
411
  const normalizedDomain = providedValues.domain.trim();
344
-
412
+
345
413
  // Verify basic auth has email
346
414
  if (normalizedAuthType === 'basic' && !providedValues.email) {
347
415
  console.error(chalk.red('❌ Email is required for basic authentication'));
@@ -362,7 +430,7 @@ async function initConfig(cliOptions = {}) {
362
430
  email: providedValues.email
363
431
  };
364
432
 
365
- saveConfig(configData);
433
+ saveConfig(configData, profileName);
366
434
  } catch (error) {
367
435
  console.error(chalk.red(`❌ ${error.message}`));
368
436
  process.exit(1);
@@ -373,21 +441,24 @@ async function initConfig(cliOptions = {}) {
373
441
  // Hybrid mode: some values provided, prompt for the rest
374
442
  try {
375
443
  console.log(chalk.blue('🚀 Confluence CLI Configuration'));
444
+ if (profileName) {
445
+ console.log(`Profile: ${chalk.cyan(profileName)}`);
446
+ }
376
447
  console.log('Completing configuration with interactive prompts:\n');
377
448
 
378
449
  const mergedValues = await promptForMissingValues(providedValues);
379
-
450
+
380
451
  // Normalize auth type
381
452
  mergedValues.authType = normalizeAuthType(mergedValues.authType, Boolean(mergedValues.email));
382
-
383
- saveConfig(mergedValues);
453
+
454
+ saveConfig(mergedValues, profileName);
384
455
  } catch (error) {
385
456
  console.error(chalk.red(`❌ ${error.message}`));
386
457
  process.exit(1);
387
458
  }
388
459
  }
389
460
 
390
- function getConfig() {
461
+ function getConfig(profileName) {
391
462
  const envDomain = process.env.CONFLUENCE_DOMAIN || process.env.CONFLUENCE_HOST;
392
463
  const envToken = process.env.CONFLUENCE_API_TOKEN || process.env.CONFLUENCE_PASSWORD;
393
464
  const envEmail = process.env.CONFLUENCE_EMAIL || process.env.CONFLUENCE_USERNAME;
@@ -422,15 +493,34 @@ function getConfig() {
422
493
  };
423
494
  }
424
495
 
425
- if (!fs.existsSync(CONFIG_FILE)) {
496
+ // Resolve profile: explicit param > CONFLUENCE_PROFILE env var > activeProfile > default
497
+ const resolvedProfileName = profileName
498
+ || process.env.CONFLUENCE_PROFILE
499
+ || null;
500
+
501
+ const fileData = readConfigFile();
502
+
503
+ if (!fileData) {
426
504
  console.error(chalk.red('❌ No configuration found!'));
427
505
  console.log(chalk.yellow('Please run "confluence init" to set up your configuration.'));
428
506
  console.log(chalk.gray('Or set environment variables: CONFLUENCE_DOMAIN, CONFLUENCE_API_TOKEN (or CONFLUENCE_PASSWORD), CONFLUENCE_EMAIL (or CONFLUENCE_USERNAME), and optionally CONFLUENCE_API_PATH, CONFLUENCE_PROTOCOL.'));
429
507
  process.exit(1);
430
508
  }
431
509
 
510
+ const targetProfile = resolvedProfileName || fileData.activeProfile || DEFAULT_PROFILE;
511
+ const storedConfig = fileData.profiles && fileData.profiles[targetProfile];
512
+
513
+ if (!storedConfig) {
514
+ console.error(chalk.red(`❌ Profile "${targetProfile}" not found!`));
515
+ const available = fileData.profiles ? Object.keys(fileData.profiles) : [];
516
+ if (available.length > 0) {
517
+ console.log(chalk.yellow(`Available profiles: ${available.join(', ')}`));
518
+ }
519
+ console.log(chalk.yellow('Run "confluence init --profile <name>" to create it, or "confluence profile list" to see available profiles.'));
520
+ process.exit(1);
521
+ }
522
+
432
523
  try {
433
- const storedConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
434
524
  const trimmedDomain = (storedConfig.domain || '').trim();
435
525
  const trimmedToken = (storedConfig.token || '').trim();
436
526
  const trimmedEmail = storedConfig.email ? storedConfig.email.trim() : undefined;
@@ -472,7 +562,60 @@ function getConfig() {
472
562
  }
473
563
  }
474
564
 
565
+ function listProfiles() {
566
+ const fileData = readConfigFile();
567
+ if (!fileData || !fileData.profiles || Object.keys(fileData.profiles).length === 0) {
568
+ return { activeProfile: null, profiles: [] };
569
+ }
570
+ return {
571
+ activeProfile: fileData.activeProfile,
572
+ profiles: Object.keys(fileData.profiles).map(name => ({
573
+ name,
574
+ active: name === fileData.activeProfile,
575
+ domain: fileData.profiles[name].domain
576
+ }))
577
+ };
578
+ }
579
+
580
+ function setActiveProfile(profileName) {
581
+ const fileData = readConfigFile();
582
+ if (!fileData) {
583
+ throw new Error('No configuration file found. Run "confluence init" first.');
584
+ }
585
+ if (!fileData.profiles || !fileData.profiles[profileName]) {
586
+ const available = fileData.profiles ? Object.keys(fileData.profiles) : [];
587
+ throw new Error(`Profile "${profileName}" not found. Available: ${available.join(', ')}`);
588
+ }
589
+ fileData.activeProfile = profileName;
590
+ saveConfigFile(fileData);
591
+ }
592
+
593
+ function deleteProfile(profileName) {
594
+ const fileData = readConfigFile();
595
+ if (!fileData) {
596
+ throw new Error('No configuration file found. Run "confluence init" first.');
597
+ }
598
+ if (!fileData.profiles || !fileData.profiles[profileName]) {
599
+ throw new Error(`Profile "${profileName}" not found.`);
600
+ }
601
+ if (Object.keys(fileData.profiles).length === 1) {
602
+ throw new Error('Cannot delete the only remaining profile.');
603
+ }
604
+ delete fileData.profiles[profileName];
605
+ if (fileData.activeProfile === profileName) {
606
+ fileData.activeProfile = Object.keys(fileData.profiles)[0];
607
+ }
608
+ saveConfigFile(fileData);
609
+ }
610
+
475
611
  module.exports = {
476
612
  initConfig,
477
- getConfig
613
+ getConfig,
614
+ listProfiles,
615
+ setActiveProfile,
616
+ deleteProfile,
617
+ isValidProfileName,
618
+ CONFIG_DIR,
619
+ CONFIG_FILE,
620
+ DEFAULT_PROFILE
478
621
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "1.25.0",
3
+ "version": "1.26.0",
4
4
  "description": "A command-line interface for Atlassian Confluence with page creation and editing capabilities",
5
5
  "main": "index.js",
6
6
  "bin": {