confluence-cli 1.12.0 → 1.13.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,17 @@
1
+ # [1.13.0](https://github.com/pchuri/confluence-cli/compare/v1.12.1...v1.13.0) (2026-01-08)
2
+
3
+
4
+ ### Features
5
+
6
+ * add children command to list child pages ([#27](https://github.com/pchuri/confluence-cli/issues/27)) ([7e8b4ed](https://github.com/pchuri/confluence-cli/commit/7e8b4ed1b0ed69a7e1de52dfaf0c1ff36973f78b))
7
+
8
+ ## [1.12.1](https://github.com/pchuri/confluence-cli/compare/v1.12.0...v1.12.1) (2025-12-31)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * align README with CLI behavior ([#26](https://github.com/pchuri/confluence-cli/issues/26)) ([b24c7cf](https://github.com/pchuri/confluence-cli/commit/b24c7cf4a645383812a1cb7239b1db41ded77f8d))
14
+
1
15
  # [1.12.0](https://github.com/pchuri/confluence-cli/compare/v1.11.1...v1.12.0) (2025-12-31)
2
16
 
3
17
 
package/README.md CHANGED
@@ -44,12 +44,17 @@ npx confluence-cli
44
44
  confluence search "my search term"
45
45
  ```
46
46
 
47
- 4. **Create a new page:**
47
+ 4. **List child pages:**
48
+ ```bash
49
+ confluence children 123456789
50
+ ```
51
+
52
+ 5. **Create a new page:**
48
53
  ```bash
49
54
  confluence create "My New Page" SPACEKEY --content "Hello World!"
50
55
  ```
51
56
 
52
- 5. **Update a page:**
57
+ 6. **Update a page:**
53
58
  ```bash
54
59
  confluence update 123456789 --content "Updated content"
55
60
  ```
@@ -139,6 +144,27 @@ confluence export 123456789 --skip-attachments
139
144
  confluence spaces
140
145
  ```
141
146
 
147
+ ### List Child Pages
148
+ ```bash
149
+ # List direct child pages
150
+ confluence children 123456789
151
+
152
+ # List all descendants recursively
153
+ confluence children 123456789 --recursive
154
+
155
+ # Display as tree structure
156
+ confluence children 123456789 --recursive --format tree
157
+
158
+ # Show page IDs and URLs
159
+ confluence children 123456789 --show-id --show-url
160
+
161
+ # Limit recursion depth
162
+ confluence children 123456789 --recursive --max-depth 3
163
+
164
+ # Output as JSON for scripting
165
+ confluence children 123456789 --recursive --format json > children.json
166
+ ```
167
+
142
168
  ### Find a Page by Title
143
169
  ```bash
144
170
  # Find page by title
@@ -250,11 +276,15 @@ confluence stats
250
276
  | `search <query>` | Search for pages | `--limit <number>` |
251
277
  | `spaces` | List all available spaces | |
252
278
  | `find <title>` | Find a page by its title | `--space <spaceKey>` |
279
+ | `children <pageId>` | List child pages of a page | `--recursive`, `--max-depth <number>`, `--format <list\|tree\|json>`, `--show-url`, `--show-id` |
253
280
  | `create <title> <spaceKey>` | Create a new page | `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>`|
254
281
  | `create-child <title> <parentId>` | Create a child page | `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>` |
255
282
  | `copy-tree <sourcePageId> <targetParentId> [newTitle]` | Copy page tree with all children | `--max-depth <number>`, `--exclude <patterns>`, `--delay-ms <ms>`, `--copy-suffix <text>`, `--dry-run`, `--fail-on-error`, `--quiet` |
256
283
  | `update <pageId>` | Update a page's title or content | `--title <string>`, `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>` |
284
+ | `delete <pageId_or_url>` | Delete a page by ID or URL | `--yes` |
257
285
  | `edit <pageId>` | Export page content for editing | `--output <file>` |
286
+ | `attachments <pageId_or_url>` | List or download attachments for a page | `--limit <number>`, `--pattern <glob>`, `--download`, `--dest <directory>` |
287
+ | `export <pageId_or_url>` | Export a page to a directory with its attachments | `--format <html\|text\|markdown>`, `--dest <directory>`, `--file <filename>`, `--attachments-dir <name>`, `--pattern <glob>`, `--referenced-only`, `--skip-attachments` |
258
288
  | `stats` | View your usage statistics | |
259
289
 
260
290
  ## Examples
package/bin/confluence.js CHANGED
@@ -475,6 +475,7 @@ program
475
475
  .option('--file <filename>', 'Content filename (default: page.<ext>)')
476
476
  .option('--attachments-dir <name>', 'Subdirectory for attachments', 'attachments')
477
477
  .option('--pattern <glob>', 'Filter attachments by filename (e.g., "*.png")')
478
+ .option('--referenced-only', 'Download only attachments referenced in the page content')
478
479
  .option('--skip-attachments', 'Do not download attachments')
479
480
  .action(async (pageId, options) => {
480
481
  const analytics = new Analytics();
@@ -489,9 +490,14 @@ program
489
490
  const contentExt = formatExt[format] || 'txt';
490
491
 
491
492
  const pageInfo = await client.getPageInfo(pageId);
492
- // Read page with attachment extraction enabled
493
- const content = await client.readPage(pageId, format, { extractReferencedAttachments: true });
494
- const referencedAttachments = client._referencedAttachments || new Set();
493
+ const content = await client.readPage(
494
+ pageId,
495
+ format,
496
+ options.referencedOnly ? { extractReferencedAttachments: true } : {}
497
+ );
498
+ const referencedAttachments = options.referencedOnly
499
+ ? (client._referencedAttachments || new Set())
500
+ : null;
495
501
 
496
502
  const baseDir = path.resolve(options.dest || '.');
497
503
  const folderName = sanitizeTitle(pageInfo.title || 'page');
@@ -510,13 +516,13 @@ program
510
516
  const pattern = options.pattern ? options.pattern.trim() : null;
511
517
  const allAttachments = await client.getAllAttachments(pageId);
512
518
 
513
- // Filter: only referenced attachments (unless pattern is specified, then use pattern)
514
519
  let filtered;
515
520
  if (pattern) {
516
521
  filtered = allAttachments.filter(att => client.matchesPattern(att.title, pattern));
522
+ } else if (options.referencedOnly) {
523
+ filtered = allAttachments.filter(att => referencedAttachments?.has(att.title));
517
524
  } else {
518
- // Only download attachments that are referenced in the page content
519
- filtered = allAttachments.filter(att => referencedAttachments.has(att.title));
525
+ filtered = allAttachments;
520
526
  }
521
527
 
522
528
  if (filtered.length === 0) {
@@ -700,4 +706,161 @@ program
700
706
  }
701
707
  });
702
708
 
703
- program.parse();
709
+ // List children command
710
+ program
711
+ .command('children <pageId>')
712
+ .description('List child pages of a Confluence page')
713
+ .option('-r, --recursive', 'List all descendants recursively', false)
714
+ .option('--max-depth <number>', 'Maximum depth for recursive listing', '10')
715
+ .option('--format <format>', 'Output format (list, tree, json)', 'list')
716
+ .option('--show-url', 'Show page URLs', false)
717
+ .option('--show-id', 'Show page IDs', false)
718
+ .action(async (pageId, options) => {
719
+ const analytics = new Analytics();
720
+ try {
721
+ const config = getConfig();
722
+ const client = new ConfluenceClient(config);
723
+
724
+ // Extract page ID from URL if needed
725
+ const resolvedPageId = await client.extractPageId(pageId);
726
+
727
+ // Get children
728
+ let children;
729
+ if (options.recursive) {
730
+ const maxDepth = parseInt(options.maxDepth) || 10;
731
+ children = await client.getAllDescendantPages(resolvedPageId, maxDepth);
732
+ } else {
733
+ children = await client.getChildPages(resolvedPageId);
734
+ }
735
+
736
+ if (children.length === 0) {
737
+ console.log(chalk.yellow('No child pages found.'));
738
+ analytics.track('children', true);
739
+ return;
740
+ }
741
+
742
+ // Format output
743
+ const format = options.format.toLowerCase();
744
+
745
+ if (format === 'json') {
746
+ // JSON output
747
+ const output = {
748
+ pageId: resolvedPageId,
749
+ childCount: children.length,
750
+ children: children.map(page => ({
751
+ id: page.id,
752
+ title: page.title,
753
+ type: page.type,
754
+ status: page.status,
755
+ spaceKey: page.space?.key,
756
+ url: `https://${config.domain}/wiki/spaces/${page.space?.key}/pages/${page.id}`,
757
+ parentId: page.parentId || resolvedPageId
758
+ }))
759
+ };
760
+ console.log(JSON.stringify(output, null, 2));
761
+ } else if (format === 'tree' && options.recursive) {
762
+ // Tree format (only for recursive mode)
763
+ const pageInfo = await client.getPageInfo(resolvedPageId);
764
+ console.log(chalk.blue(`📁 ${pageInfo.title}`));
765
+
766
+ // Build tree structure
767
+ const tree = buildTree(children, resolvedPageId);
768
+ printTree(tree, config, options, 1);
769
+
770
+ console.log('');
771
+ console.log(chalk.gray(`Total: ${children.length} child page${children.length === 1 ? '' : 's'}`));
772
+ } else {
773
+ // List format (default)
774
+ console.log(chalk.blue('Child pages:'));
775
+ console.log('');
776
+
777
+ children.forEach((page, index) => {
778
+ let output = `${index + 1}. ${chalk.green(page.title)}`;
779
+
780
+ if (options.showId) {
781
+ output += ` ${chalk.gray(`(ID: ${page.id})`)}`;
782
+ }
783
+
784
+ if (options.showUrl) {
785
+ const url = `https://${config.domain}/wiki/spaces/${page.space?.key}/pages/${page.id}`;
786
+ output += `\n ${chalk.gray(url)}`;
787
+ }
788
+
789
+ if (options.recursive && page.parentId && page.parentId !== resolvedPageId) {
790
+ output += ` ${chalk.dim('(nested)')}`;
791
+ }
792
+
793
+ console.log(output);
794
+ });
795
+
796
+ console.log('');
797
+ console.log(chalk.gray(`Total: ${children.length} child page${children.length === 1 ? '' : 's'}`));
798
+ }
799
+
800
+ analytics.track('children', true);
801
+ } catch (error) {
802
+ analytics.track('children', false);
803
+ console.error(chalk.red('Error:'), error.message);
804
+ process.exit(1);
805
+ }
806
+ });
807
+
808
+ // Helper function to build tree structure
809
+ function buildTree(pages, rootId) {
810
+ const tree = [];
811
+ const pageMap = new Map();
812
+
813
+ // Create a map of all pages
814
+ pages.forEach(page => {
815
+ pageMap.set(page.id, { ...page, children: [] });
816
+ });
817
+
818
+ // Build tree structure
819
+ pages.forEach(page => {
820
+ const node = pageMap.get(page.id);
821
+ const parentId = page.parentId || rootId;
822
+
823
+ if (parentId === rootId) {
824
+ tree.push(node);
825
+ } else {
826
+ const parent = pageMap.get(parentId);
827
+ if (parent) {
828
+ parent.children.push(node);
829
+ }
830
+ }
831
+ });
832
+
833
+ return tree;
834
+ }
835
+
836
+ // Helper function to print tree
837
+ function printTree(nodes, config, options, depth = 1) {
838
+ nodes.forEach((node, index) => {
839
+ const isLast = index === nodes.length - 1;
840
+ const indent = ' '.repeat(depth - 1);
841
+ const prefix = isLast ? '└── ' : '├── ';
842
+
843
+ let output = `${indent}${prefix}📄 ${chalk.green(node.title)}`;
844
+
845
+ if (options.showId) {
846
+ output += ` ${chalk.gray(`(ID: ${node.id})`)}`;
847
+ }
848
+
849
+ if (options.showUrl) {
850
+ const url = `https://${config.domain}/wiki/spaces/${node.space?.key}/pages/${node.id}`;
851
+ output += `\n${indent}${isLast ? ' ' : '│ '}${chalk.gray(url)}`;
852
+ }
853
+
854
+ console.log(output);
855
+
856
+ if (node.children && node.children.length > 0) {
857
+ printTree(node.children, config, options, depth + 1);
858
+ }
859
+ });
860
+ }
861
+
862
+ if (process.argv.length <= 2) {
863
+ program.help({ error: false });
864
+ }
865
+
866
+ program.parse(process.argv);
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "1.12.0",
3
+ "version": "1.13.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": {
7
- "confluence": "bin/index.js"
7
+ "confluence": "bin/index.js",
8
+ "confluence-cli": "bin/index.js"
8
9
  },
9
10
  "scripts": {
10
11
  "start": "node bin/confluence.js",