confluence-cli 1.19.0 → 1.21.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
@@ -20,7 +20,7 @@ program
20
20
  .option('-d, --domain <domain>', 'Confluence domain')
21
21
  .option('-p, --api-path <path>', 'REST API path')
22
22
  .option('-a, --auth-type <type>', 'Authentication type (basic or bearer)')
23
- .option('-e, --email <email>', 'Email for basic auth')
23
+ .option('-e, --email <email>', 'Email or username for basic auth')
24
24
  .option('-t, --token <token>', 'API token')
25
25
  .action(async (options) => {
26
26
  await initConfig(options);
@@ -140,6 +140,50 @@ program
140
140
  }
141
141
  });
142
142
 
143
+ // Install skill command
144
+ program
145
+ .command('install-skill')
146
+ .description('Copy Claude Code skill files into your project\'s .claude/skills/ directory')
147
+ .option('--dest <directory>', 'Target directory', './.claude/skills/confluence')
148
+ .option('-y, --yes', 'Skip confirmation prompt')
149
+ .action(async (options) => {
150
+ const fs = require('fs');
151
+ const path = require('path');
152
+
153
+ const skillSrc = path.join(__dirname, '..', '.claude', 'skills', 'confluence', 'SKILL.md');
154
+
155
+ if (!fs.existsSync(skillSrc)) {
156
+ console.error(chalk.red('Error: skill file not found in package. Try reinstalling confluence-cli.'));
157
+ process.exit(1);
158
+ }
159
+
160
+ const destDir = path.resolve(options.dest);
161
+ const destFile = path.join(destDir, 'SKILL.md');
162
+
163
+ if (fs.existsSync(destFile) && !options.yes) {
164
+ const { confirmed } = await inquirer.prompt([
165
+ {
166
+ type: 'confirm',
167
+ name: 'confirmed',
168
+ default: true,
169
+ message: `Overwrite existing skill file at ${destFile}?`
170
+ }
171
+ ]);
172
+
173
+ if (!confirmed) {
174
+ console.log(chalk.yellow('Cancelled.'));
175
+ return;
176
+ }
177
+ }
178
+
179
+ fs.mkdirSync(destDir, { recursive: true });
180
+ fs.copyFileSync(skillSrc, destFile);
181
+
182
+ console.log(chalk.green('✅ Skill installed successfully!'));
183
+ console.log(`Location: ${chalk.gray(destFile)}`);
184
+ console.log(chalk.yellow('Claude Code will now pick up confluence-cli knowledge from this file.'));
185
+ });
186
+
143
187
  // Create command
144
188
  program
145
189
  .command('create <title> <spaceKey>')
@@ -419,6 +463,7 @@ program
419
463
  .option('-p, --pattern <glob>', 'Filter attachments by filename (e.g., "*.png")')
420
464
  .option('-d, --download', 'Download matching attachments')
421
465
  .option('--dest <directory>', 'Directory to save downloads (default: current directory)', '.')
466
+ .option('-f, --format <format>', 'Output format (text, json)', 'text')
422
467
  .action(async (pageId, options) => {
423
468
  const analytics = new Analytics();
424
469
  try {
@@ -431,22 +476,47 @@ program
431
476
  throw new Error('Limit must be a positive number.');
432
477
  }
433
478
 
479
+ const format = (options.format || 'text').toLowerCase();
480
+ if (!['text', 'json'].includes(format)) {
481
+ throw new Error('Format must be one of: text, json');
482
+ }
483
+
434
484
  const attachments = await client.getAllAttachments(pageId, { maxResults });
435
485
  const filtered = pattern ? attachments.filter(att => client.matchesPattern(att.title, pattern)) : attachments;
436
486
 
437
487
  if (filtered.length === 0) {
438
- console.log(chalk.yellow('No attachments found.'));
488
+ if (format === 'json') {
489
+ console.log(JSON.stringify({ attachmentCount: 0, attachments: [] }, null, 2));
490
+ } else {
491
+ console.log(chalk.yellow('No attachments found.'));
492
+ }
439
493
  analytics.track('attachments', true);
440
494
  return;
441
495
  }
442
496
 
443
- console.log(chalk.blue(`Found ${filtered.length} attachment${filtered.length === 1 ? '' : 's'}:`));
444
- filtered.forEach((att, index) => {
445
- const sizeKb = att.fileSize ? `${Math.max(1, Math.round(att.fileSize / 1024))} KB` : 'unknown size';
446
- const typeLabel = att.mediaType || 'unknown';
447
- console.log(`${index + 1}. ${chalk.green(att.title)} (ID: ${att.id})`);
448
- console.log(` Type: ${chalk.gray(typeLabel)} • Size: ${chalk.gray(sizeKb)} • Version: ${chalk.gray(att.version)}`);
449
- });
497
+ if (format === 'json' && !options.download) {
498
+ const output = {
499
+ attachmentCount: filtered.length,
500
+ attachments: filtered.map(att => ({
501
+ id: att.id,
502
+ title: att.title,
503
+ mediaType: att.mediaType || '',
504
+ fileSize: att.fileSize,
505
+ fileSizeFormatted: att.fileSize ? `${Math.max(1, Math.round(att.fileSize / 1024))} KB` : 'unknown size',
506
+ version: att.version,
507
+ downloadLink: att.downloadLink
508
+ }))
509
+ };
510
+ console.log(JSON.stringify(output, null, 2));
511
+ } else if (!options.download) {
512
+ console.log(chalk.blue(`Found ${filtered.length} attachment${filtered.length === 1 ? '' : 's'}:`));
513
+ filtered.forEach((att, index) => {
514
+ const sizeKb = att.fileSize ? `${Math.max(1, Math.round(att.fileSize / 1024))} KB` : 'unknown size';
515
+ const typeLabel = att.mediaType || 'unknown';
516
+ console.log(`${index + 1}. ${chalk.green(att.title)} (ID: ${att.id})`);
517
+ console.log(` Type: ${chalk.gray(typeLabel)} • Size: ${chalk.gray(sizeKb)} • Version: ${chalk.gray(att.version)}`);
518
+ });
519
+ }
450
520
 
451
521
  if (options.download) {
452
522
  const fs = require('fs');
@@ -475,17 +545,28 @@ program
475
545
  writer.on('finish', resolve);
476
546
  });
477
547
 
478
- let downloaded = 0;
548
+ const downloadResults = [];
479
549
  for (const attachment of filtered) {
480
550
  const targetPath = uniquePathFor(destDir, attachment.title);
481
- // Pass the full attachment object so downloadAttachment can use downloadLink directly
482
551
  const dataStream = await client.downloadAttachment(pageId, attachment);
483
552
  await writeStream(dataStream, targetPath);
484
- downloaded += 1;
485
- console.log(`⬇️ ${chalk.green(attachment.title)} -> ${chalk.gray(targetPath)}`);
553
+ downloadResults.push({ title: attachment.title, id: attachment.id, savedTo: targetPath });
554
+ if (format !== 'json') {
555
+ console.log(`⬇️ ${chalk.green(attachment.title)} -> ${chalk.gray(targetPath)}`);
556
+ }
486
557
  }
487
558
 
488
- console.log(chalk.green(`Downloaded ${downloaded} attachment${downloaded === 1 ? '' : 's'} to ${destDir}`));
559
+ if (format === 'json') {
560
+ const output = {
561
+ attachmentCount: filtered.length,
562
+ downloaded: downloadResults.length,
563
+ destination: destDir,
564
+ attachments: downloadResults
565
+ };
566
+ console.log(JSON.stringify(output, null, 2));
567
+ } else {
568
+ console.log(chalk.green(`Downloaded ${downloadResults.length} attachment${downloadResults.length === 1 ? '' : 's'} to ${destDir}`));
569
+ }
489
570
  }
490
571
 
491
572
  analytics.track('attachments', true);
package/lib/config.js CHANGED
@@ -8,7 +8,7 @@ const CONFIG_DIR = path.join(os.homedir(), '.confluence-cli');
8
8
  const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
9
9
 
10
10
  const AUTH_CHOICES = [
11
- { name: 'Basic (email + API token)', value: 'basic' },
11
+ { name: 'Basic (credentials)', value: 'basic' },
12
12
  { name: 'Bearer token', value: 'bearer' }
13
13
  ];
14
14
 
@@ -91,7 +91,7 @@ const validateCliOptions = (options) => {
91
91
  // Check if basic auth is provided with email
92
92
  const normAuthType = options.authType ? normalizeAuthType(options.authType, Boolean(options.email)) : null;
93
93
  if (normAuthType === 'basic' && !options.email) {
94
- errors.push('--email is required when using basic authentication');
94
+ errors.push('--email is required when using basic authentication (use your username for on-premise)');
95
95
  }
96
96
 
97
97
  return errors;
@@ -175,12 +175,12 @@ const promptForMissingValues = async (providedValues) => {
175
175
  questions.push({
176
176
  type: 'input',
177
177
  name: 'email',
178
- message: 'Confluence email (used with API token):',
178
+ message: 'Email / username:',
179
179
  when: (responses) => {
180
180
  const authType = providedValues.authType || responses.authType;
181
181
  return authType === 'basic';
182
182
  },
183
- validate: requiredInput('Email')
183
+ validate: requiredInput('Email / username')
184
184
  });
185
185
  }
186
186
 
@@ -189,8 +189,8 @@ const promptForMissingValues = async (providedValues) => {
189
189
  questions.push({
190
190
  type: 'password',
191
191
  name: 'token',
192
- message: 'API Token:',
193
- validate: requiredInput('API Token')
192
+ message: 'API token / password:',
193
+ validate: requiredInput('API token / password')
194
194
  });
195
195
  }
196
196
 
@@ -258,15 +258,15 @@ async function initConfig(cliOptions = {}) {
258
258
  {
259
259
  type: 'input',
260
260
  name: 'email',
261
- message: 'Confluence email (used with API token):',
261
+ message: 'Email / username:',
262
262
  when: (responses) => responses.authType === 'basic',
263
- validate: requiredInput('Email')
263
+ validate: requiredInput('Email / username')
264
264
  },
265
265
  {
266
266
  type: 'password',
267
267
  name: 'token',
268
- message: 'API Token:',
269
- validate: requiredInput('API Token')
268
+ message: 'API token / password:',
269
+ validate: requiredInput('API token / password')
270
270
  }
271
271
  ]);
272
272
 
@@ -351,8 +351,8 @@ async function initConfig(cliOptions = {}) {
351
351
 
352
352
  function getConfig() {
353
353
  const envDomain = process.env.CONFLUENCE_DOMAIN || process.env.CONFLUENCE_HOST;
354
- const envToken = process.env.CONFLUENCE_API_TOKEN;
355
- const envEmail = process.env.CONFLUENCE_EMAIL;
354
+ const envToken = process.env.CONFLUENCE_API_TOKEN || process.env.CONFLUENCE_PASSWORD;
355
+ const envEmail = process.env.CONFLUENCE_EMAIL || process.env.CONFLUENCE_USERNAME;
356
356
  const envAuthType = process.env.CONFLUENCE_AUTH_TYPE;
357
357
  const envApiPath = process.env.CONFLUENCE_API_PATH;
358
358
 
@@ -368,8 +368,8 @@ function getConfig() {
368
368
  }
369
369
 
370
370
  if (authType === 'basic' && !envEmail) {
371
- console.error(chalk.red('❌ Basic authentication requires CONFLUENCE_EMAIL.'));
372
- console.log(chalk.yellow('Set CONFLUENCE_EMAIL or switch to bearer auth by setting CONFLUENCE_AUTH_TYPE=bearer.'));
371
+ console.error(chalk.red('❌ Basic authentication requires CONFLUENCE_EMAIL or CONFLUENCE_USERNAME.'));
372
+ console.log(chalk.yellow('Set CONFLUENCE_EMAIL (or CONFLUENCE_USERNAME for on-premise) or switch to bearer auth by setting CONFLUENCE_AUTH_TYPE=bearer.'));
373
373
  process.exit(1);
374
374
  }
375
375
 
@@ -385,7 +385,7 @@ function getConfig() {
385
385
  if (!fs.existsSync(CONFIG_FILE)) {
386
386
  console.error(chalk.red('❌ No configuration found!'));
387
387
  console.log(chalk.yellow('Please run "confluence init" to set up your configuration.'));
388
- console.log(chalk.gray('Or set environment variables: CONFLUENCE_DOMAIN, CONFLUENCE_API_TOKEN, CONFLUENCE_EMAIL, and optionally CONFLUENCE_API_PATH.'));
388
+ 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.'));
389
389
  process.exit(1);
390
390
  }
391
391
 
@@ -404,8 +404,8 @@ function getConfig() {
404
404
  }
405
405
 
406
406
  if (authType === 'basic' && !trimmedEmail) {
407
- console.error(chalk.red('❌ Basic authentication requires an email address.'));
408
- console.log(chalk.yellow('Please rerun "confluence init" to add your Confluence email.'));
407
+ console.error(chalk.red('❌ Basic authentication requires an email address or username.'));
408
+ console.log(chalk.yellow('Please rerun "confluence init" to add your Confluence email or username.'));
409
409
  process.exit(1);
410
410
  }
411
411
 
@@ -42,7 +42,7 @@ class ConfluenceClient {
42
42
 
43
43
  buildBasicAuthHeader() {
44
44
  if (!this.email) {
45
- throw new Error('Basic authentication requires an email address.');
45
+ throw new Error('Basic authentication requires an email address or username.');
46
46
  }
47
47
 
48
48
  const encodedCredentials = Buffer.from(`${this.email}:${this.token}`).toString('base64');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "1.19.0",
3
+ "version": "1.21.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": {
@@ -54,5 +54,10 @@
54
54
  "bugs": {
55
55
  "url": "https://github.com/pchuri/confluence-cli/issues"
56
56
  },
57
- "homepage": "https://github.com/pchuri/confluence-cli#readme"
57
+ "homepage": "https://github.com/pchuri/confluence-cli#readme",
58
+ "files": [
59
+ "bin/",
60
+ "lib/",
61
+ ".claude/skills/"
62
+ ]
58
63
  }
package/.eslintrc.js DELETED
@@ -1,23 +0,0 @@
1
- module.exports = {
2
- env: {
3
- es2021: true,
4
- node: true,
5
- jest: true
6
- },
7
- extends: [
8
- 'eslint:recommended'
9
- ],
10
- parserOptions: {
11
- ecmaVersion: 12,
12
- sourceType: 'module'
13
- },
14
- rules: {
15
- 'indent': ['error', 2],
16
- 'linebreak-style': ['error', 'unix'],
17
- 'quotes': ['error', 'single'],
18
- 'semi': ['error', 'always'],
19
- 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
20
- 'no-console': 'off',
21
- 'no-process-exit': 'off'
22
- }
23
- };
@@ -1,34 +0,0 @@
1
- ---
2
- name: Bug report
3
- about: Create a report to help us improve
4
- title: '[BUG] '
5
- labels: bug
6
- assignees: ''
7
-
8
- ---
9
-
10
- **Describe the bug**
11
- A clear and concise description of what the bug is.
12
-
13
- **To Reproduce**
14
- Steps to reproduce the behavior:
15
- 1. Run command '...'
16
- 2. See error
17
-
18
- **Expected behavior**
19
- A clear and concise description of what you expected to happen.
20
-
21
- **Screenshots**
22
- If applicable, add screenshots to help explain your problem.
23
-
24
- **Environment (please complete the following information):**
25
- - OS: [e.g. macOS, Windows, Linux]
26
- - Node.js version: [e.g. 18.17.0]
27
- - confluence-cli version: [e.g. 1.0.0]
28
- - Confluence version: [e.g. Cloud, Server 8.5]
29
-
30
- **Additional context**
31
- Add any other context about the problem here.
32
-
33
- **Error logs**
34
- If applicable, add error logs or stack traces.
@@ -1,26 +0,0 @@
1
- ---
2
- name: Feature request
3
- about: Suggest an idea for this project
4
- title: '[FEATURE] '
5
- labels: enhancement
6
- assignees: ''
7
-
8
- ---
9
-
10
- **Is your feature request related to a problem? Please describe.**
11
- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12
-
13
- **Describe the solution you'd like**
14
- A clear and concise description of what you want to happen.
15
-
16
- **Describe alternatives you've considered**
17
- A clear and concise description of any alternative solutions or features you've considered.
18
-
19
- **Use case**
20
- Describe how this feature would be used and who would benefit from it.
21
-
22
- **Additional context**
23
- Add any other context or screenshots about the feature request here.
24
-
25
- **Implementation suggestions**
26
- If you have ideas about how this could be implemented, please share them here.
@@ -1,37 +0,0 @@
1
- ---
2
- name: General Feedback
3
- about: Share your thoughts, suggestions, or general feedback about confluence-cli
4
- title: '[FEEDBACK] '
5
- labels: feedback, enhancement
6
- assignees: ''
7
-
8
- ---
9
-
10
- ## 📝 Your Feedback
11
-
12
- Thank you for taking the time to share your thoughts about confluence-cli!
13
-
14
- ### What did you try to accomplish?
15
- A clear description of what you were trying to do with confluence-cli.
16
-
17
- ### How was your experience?
18
- Tell us about your experience using the tool:
19
- - What worked well?
20
- - What was confusing or difficult?
21
- - What features are you missing?
22
-
23
- ### Your environment
24
- - OS: [e.g. macOS, Windows, Linux]
25
- - Node.js version: [e.g. 18.17.0]
26
- - confluence-cli version: [e.g. 1.0.1]
27
- - Confluence instance: [e.g. Cloud, Server, Data Center]
28
-
29
- ### Feature requests or improvements
30
- What would make confluence-cli more useful for you?
31
-
32
- ### Additional context
33
- Anything else you'd like to share about your experience with confluence-cli?
34
-
35
- ---
36
-
37
- 💡 **Tip**: You can also join our [Discussions](https://github.com/pchuri/confluence-cli/discussions) for general questions and community chat!
@@ -1,31 +0,0 @@
1
- ## Pull Request Template
2
-
3
- ### Description
4
- Brief description of what this PR does.
5
-
6
- ### Type of Change
7
- - [ ] Bug fix (non-breaking change which fixes an issue)
8
- - [ ] New feature (non-breaking change which adds functionality)
9
- - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
10
- - [ ] Documentation update
11
- - [ ] Performance improvement
12
- - [ ] Code refactoring
13
-
14
- ### Testing
15
- - [ ] Tests pass locally with my changes
16
- - [ ] I have added tests that prove my fix is effective or that my feature works
17
- - [ ] New and existing unit tests pass locally with my changes
18
-
19
- ### Checklist
20
- - [ ] My code follows the style guidelines of this project
21
- - [ ] I have performed a self-review of my own code
22
- - [ ] I have commented my code, particularly in hard-to-understand areas
23
- - [ ] I have made corresponding changes to the documentation
24
- - [ ] My changes generate no new warnings
25
- - [ ] Any dependent changes have been merged and published in downstream modules
26
-
27
- ### Screenshots (if applicable)
28
- Add screenshots to help explain your changes.
29
-
30
- ### Additional Context
31
- Add any other context about the pull request here.
@@ -1,68 +0,0 @@
1
- name: CI/CD
2
-
3
- on:
4
- push:
5
- branches: [ main, develop ]
6
- pull_request:
7
- branches: [ main ]
8
-
9
- jobs:
10
- test:
11
- runs-on: ubuntu-latest
12
-
13
- strategy:
14
- matrix:
15
- node-version: [16.x, 18.x, 20.x]
16
-
17
- steps:
18
- - uses: actions/checkout@v3
19
-
20
- - name: Use Node.js ${{ matrix.node-version }}
21
- uses: actions/setup-node@v3
22
- with:
23
- node-version: ${{ matrix.node-version }}
24
- cache: 'npm'
25
-
26
- - run: npm ci
27
- - run: npm run lint
28
- - run: npm test
29
-
30
- security:
31
- runs-on: ubuntu-latest
32
- steps:
33
- - uses: actions/checkout@v3
34
- - run: npm ci
35
- - name: Run npm audit (production only)
36
- run: npm audit --audit-level moderate --omit=dev
37
-
38
- publish:
39
- needs: [test, security]
40
- runs-on: ubuntu-latest
41
- if: github.ref == 'refs/heads/main'
42
- permissions:
43
- contents: write
44
- issues: write
45
- pull-requests: write
46
-
47
- steps:
48
- - uses: actions/checkout@v3
49
- with:
50
- fetch-depth: 0
51
- token: ${{ secrets.GITHUB_TOKEN }}
52
-
53
- - name: Setup Node.js
54
- uses: actions/setup-node@v3
55
- with:
56
- node-version: '18'
57
- registry-url: 'https://registry.npmjs.org'
58
- env:
59
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
60
-
61
- - run: npm ci
62
- - run: npm test
63
-
64
- - name: Semantic Release
65
- uses: cycjimmy/semantic-release-action@v3
66
- env:
67
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
68
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/.releaserc DELETED
@@ -1,17 +0,0 @@
1
- {
2
- "branches": ["main"],
3
- "plugins": [
4
- "@semantic-release/commit-analyzer",
5
- "@semantic-release/release-notes-generator",
6
- "@semantic-release/changelog",
7
- "@semantic-release/npm",
8
- "@semantic-release/github",
9
- [
10
- "@semantic-release/git",
11
- {
12
- "assets": ["package.json", "CHANGELOG.md"],
13
- "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
14
- }
15
- ]
16
- ]
17
- }