confluence-cli 1.6.0 → 1.7.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/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [1.7.0](https://github.com/pchuri/confluence-cli/compare/v1.6.0...v1.7.0) (2025-09-28)
2
+
3
+
4
+ ### Features
5
+
6
+ * support basic auth for Atlassian API tokens ([#12](https://github.com/pchuri/confluence-cli/issues/12)) ([e80ea9b](https://github.com/pchuri/confluence-cli/commit/e80ea9b7913d5f497b60bf72149737b6f704c6b8))
7
+
1
8
  # [1.6.0](https://github.com/pchuri/confluence-cli/compare/v1.5.0...v1.6.0) (2025-09-05)
2
9
 
3
10
 
package/README.md CHANGED
@@ -58,12 +58,19 @@ npx confluence-cli
58
58
  confluence init
59
59
  ```
60
60
 
61
+ The wizard now asks for your authentication method. Choose **Basic** to supply your Atlassian email and API token, or switch to **Bearer** when working with self-hosted/Data Center environments.
62
+
61
63
  ### Option 2: Environment Variables
62
64
  ```bash
63
65
  export CONFLUENCE_DOMAIN="your-domain.atlassian.net"
64
66
  export CONFLUENCE_API_TOKEN="your-api-token"
67
+ export CONFLUENCE_EMAIL="your.email@example.com" # required when using Atlassian Cloud
68
+ # Optional: set to 'bearer' for self-hosted/Data Center instances
69
+ export CONFLUENCE_AUTH_TYPE="basic"
65
70
  ```
66
71
 
72
+ `CONFLUENCE_AUTH_TYPE` defaults to `basic` when an email is present and falls back to `bearer` otherwise. Provide an email for Atlassian Cloud (Basic auth) or set `CONFLUENCE_AUTH_TYPE=bearer` to keep bearer-token flows for on-premises installations.
73
+
67
74
  ### Getting Your API Token
68
75
 
69
76
  1. Go to [Atlassian Account Settings](https://id.atlassian.com/manage-profile/security/api-tokens)
package/lib/config.js CHANGED
@@ -7,9 +7,26 @@ const chalk = require('chalk');
7
7
  const CONFIG_DIR = path.join(os.homedir(), '.confluence-cli');
8
8
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
9
9
 
10
- /**
11
- * Initialize configuration
12
- */
10
+ const AUTH_CHOICES = [
11
+ { name: 'Basic (email + API token)', value: 'basic' },
12
+ { name: 'Bearer token', value: 'bearer' }
13
+ ];
14
+
15
+ const requiredInput = (label) => (input) => {
16
+ if (!input || !input.trim()) {
17
+ return `${label} is required`;
18
+ }
19
+ return true;
20
+ };
21
+
22
+ const normalizeAuthType = (rawValue, hasEmail) => {
23
+ const normalized = (rawValue || '').trim().toLowerCase();
24
+ if (normalized === 'basic' || normalized === 'bearer') {
25
+ return normalized;
26
+ }
27
+ return hasEmail ? 'basic' : 'bearer';
28
+ };
29
+
13
30
  async function initConfig() {
14
31
  console.log(chalk.blue('šŸš€ Confluence CLI Configuration'));
15
32
  console.log('Please provide your Confluence connection details:\n');
@@ -19,70 +36,103 @@ async function initConfig() {
19
36
  type: 'input',
20
37
  name: 'domain',
21
38
  message: 'Confluence domain (e.g., yourcompany.atlassian.net):',
22
- validate: (input) => {
23
- if (!input.trim()) {
24
- return 'Domain is required';
25
- }
26
- return true;
27
- }
39
+ validate: requiredInput('Domain')
40
+ },
41
+ {
42
+ type: 'list',
43
+ name: 'authType',
44
+ message: 'Authentication method:',
45
+ choices: AUTH_CHOICES,
46
+ default: 'basic'
47
+ },
48
+ {
49
+ type: 'input',
50
+ name: 'email',
51
+ message: 'Confluence email (used with API token):',
52
+ when: (responses) => responses.authType === 'basic',
53
+ validate: requiredInput('Email')
28
54
  },
29
55
  {
30
56
  type: 'password',
31
57
  name: 'token',
32
58
  message: 'API Token:',
33
- validate: (input) => {
34
- if (!input.trim()) {
35
- return 'API Token is required';
36
- }
37
- return true;
38
- }
59
+ validate: requiredInput('API Token')
39
60
  }
40
61
  ]);
41
62
 
42
- // Create config directory if it doesn't exist
43
63
  if (!fs.existsSync(CONFIG_DIR)) {
44
64
  fs.mkdirSync(CONFIG_DIR, { recursive: true });
45
65
  }
46
66
 
47
- // Save configuration
48
67
  const config = {
49
68
  domain: answers.domain.trim(),
50
- token: answers.token.trim()
69
+ token: answers.token.trim(),
70
+ authType: answers.authType,
71
+ email: answers.authType === 'basic' ? answers.email.trim() : undefined
51
72
  };
52
73
 
53
74
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
54
-
75
+
55
76
  console.log(chalk.green('āœ… Configuration saved successfully!'));
56
77
  console.log(`Config file location: ${chalk.gray(CONFIG_FILE)}`);
57
78
  console.log(chalk.yellow('\nšŸ’” Tip: You can regenerate this config anytime by running "confluence init"'));
58
79
  }
59
80
 
60
- /**
61
- * Get configuration
62
- */
63
81
  function getConfig() {
64
- // First check for environment variables
65
82
  const envDomain = process.env.CONFLUENCE_DOMAIN || process.env.CONFLUENCE_HOST;
66
83
  const envToken = process.env.CONFLUENCE_API_TOKEN;
84
+ const envEmail = process.env.CONFLUENCE_EMAIL;
85
+ const envAuthType = process.env.CONFLUENCE_AUTH_TYPE;
67
86
 
68
87
  if (envDomain && envToken) {
88
+ const authType = normalizeAuthType(envAuthType, Boolean(envEmail));
89
+
90
+ if (authType === 'basic' && !envEmail) {
91
+ console.error(chalk.red('āŒ Basic authentication requires CONFLUENCE_EMAIL.'));
92
+ console.log(chalk.yellow('Set CONFLUENCE_EMAIL or switch to bearer auth by setting CONFLUENCE_AUTH_TYPE=bearer.'));
93
+ process.exit(1);
94
+ }
95
+
69
96
  return {
70
- domain: envDomain,
71
- token: envToken
97
+ domain: envDomain.trim(),
98
+ token: envToken.trim(),
99
+ email: envEmail ? envEmail.trim() : undefined,
100
+ authType
72
101
  };
73
102
  }
74
103
 
75
- // Check for config file
76
104
  if (!fs.existsSync(CONFIG_FILE)) {
77
105
  console.error(chalk.red('āŒ No configuration found!'));
78
106
  console.log(chalk.yellow('Please run "confluence init" to set up your configuration.'));
79
- console.log(chalk.gray('Or set environment variables: CONFLUENCE_DOMAIN and CONFLUENCE_API_TOKEN'));
107
+ console.log(chalk.gray('Or set environment variables: CONFLUENCE_DOMAIN, CONFLUENCE_API_TOKEN, and CONFLUENCE_EMAIL.'));
80
108
  process.exit(1);
81
109
  }
82
110
 
83
111
  try {
84
- const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
85
- return config;
112
+ const storedConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
113
+ const trimmedDomain = (storedConfig.domain || '').trim();
114
+ const trimmedToken = (storedConfig.token || '').trim();
115
+ const trimmedEmail = storedConfig.email ? storedConfig.email.trim() : undefined;
116
+ const authType = normalizeAuthType(storedConfig.authType, Boolean(trimmedEmail));
117
+
118
+ if (!trimmedDomain || !trimmedToken) {
119
+ console.error(chalk.red('āŒ Configuration file is missing required values.'));
120
+ console.log(chalk.yellow('Run "confluence init" to refresh your settings.'));
121
+ process.exit(1);
122
+ }
123
+
124
+ if (authType === 'basic' && !trimmedEmail) {
125
+ console.error(chalk.red('āŒ Basic authentication requires an email address.'));
126
+ console.log(chalk.yellow('Please rerun "confluence init" to add your Confluence email.'));
127
+ process.exit(1);
128
+ }
129
+
130
+ return {
131
+ domain: trimmedDomain,
132
+ token: trimmedToken,
133
+ email: trimmedEmail,
134
+ authType
135
+ };
86
136
  } catch (error) {
87
137
  console.error(chalk.red('āŒ Error reading configuration file:'), error.message);
88
138
  console.log(chalk.yellow('Please run "confluence init" to recreate your configuration.'));
@@ -4,21 +4,34 @@ const MarkdownIt = require('markdown-it');
4
4
 
5
5
  class ConfluenceClient {
6
6
  constructor(config) {
7
- this.baseURL = `https://${config.domain}/rest/api`;
8
- this.token = config.token;
9
7
  this.domain = config.domain;
8
+ this.token = config.token;
9
+ this.email = config.email;
10
+ this.authType = (config.authType || (this.email ? 'basic' : 'bearer')).toLowerCase();
11
+ this.baseURL = `https://${this.domain}/rest/api`;
10
12
  this.markdown = new MarkdownIt();
11
13
  this.setupConfluenceMarkdownExtensions();
12
-
14
+
15
+ const headers = {
16
+ 'Content-Type': 'application/json',
17
+ 'Authorization': this.authType === 'basic' ? this.buildBasicAuthHeader() : `Bearer ${this.token}`
18
+ };
19
+
13
20
  this.client = axios.create({
14
21
  baseURL: this.baseURL,
15
- headers: {
16
- 'Authorization': `Bearer ${this.token}`,
17
- 'Content-Type': 'application/json'
18
- }
22
+ headers
19
23
  });
20
24
  }
21
25
 
26
+ buildBasicAuthHeader() {
27
+ if (!this.email) {
28
+ throw new Error('Basic authentication requires an email address.');
29
+ }
30
+
31
+ const encodedCredentials = Buffer.from(`${this.email}:${this.token}`).toString('base64');
32
+ return `Basic ${encodedCredentials}`;
33
+ }
34
+
22
35
  /**
23
36
  * Extract page ID from URL or return the ID if it's already a number
24
37
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "1.6.0",
3
+ "version": "1.7.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": {
@@ -22,7 +22,7 @@
22
22
  "author": "pchuri",
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
- "axios": "^1.6.2",
25
+ "axios": "^1.12.0",
26
26
  "chalk": "^4.1.2",
27
27
  "commander": "^11.1.0",
28
28
  "html-to-text": "^9.0.5",
@@ -10,6 +10,37 @@ describe('ConfluenceClient', () => {
10
10
  });
11
11
  });
12
12
 
13
+ describe('authentication setup', () => {
14
+ test('uses bearer token headers by default', () => {
15
+ const bearerClient = new ConfluenceClient({
16
+ domain: 'test.atlassian.net',
17
+ token: 'bearer-token'
18
+ });
19
+
20
+ expect(bearerClient.client.defaults.headers.Authorization).toBe('Bearer bearer-token');
21
+ });
22
+
23
+ test('builds basic auth headers when email is provided', () => {
24
+ const basicClient = new ConfluenceClient({
25
+ domain: 'test.atlassian.net',
26
+ token: 'basic-token',
27
+ authType: 'basic',
28
+ email: 'user@example.com'
29
+ });
30
+
31
+ const encoded = Buffer.from('user@example.com:basic-token').toString('base64');
32
+ expect(basicClient.client.defaults.headers.Authorization).toBe(`Basic ${encoded}`);
33
+ });
34
+
35
+ test('throws when basic auth is missing an email', () => {
36
+ expect(() => new ConfluenceClient({
37
+ domain: 'test.atlassian.net',
38
+ token: 'missing-email',
39
+ authType: 'basic'
40
+ })).toThrow('Basic authentication requires an email address.');
41
+ });
42
+ });
43
+
13
44
  describe('extractPageId', () => {
14
45
  test('should return numeric page ID as is', () => {
15
46
  expect(client.extractPageId('123456789')).toBe('123456789');