confluence-cli 2.1.12 → 2.3.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
@@ -7,7 +7,7 @@ A powerful command-line interface for Atlassian Confluence that allows you to re
7
7
  - 📖 **Read pages** - Get page content in text or HTML format
8
8
  - 🔍 **Search** - Find pages using Confluence's powerful search
9
9
  - â„šī¸ **Page info** - Get detailed information about pages
10
- - 🏠 **List spaces** - View all available Confluence spaces
10
+ - 🏠 **List spaces** - View available Confluence spaces
11
11
  - âœī¸ **Create pages** - Create new pages with support for Markdown, HTML, or Storage format
12
12
  - 📝 **Update pages** - Update existing page content and titles
13
13
  - đŸ—‘ī¸ **Delete pages** - Delete (or move to trash) pages by ID or URL
@@ -691,11 +691,11 @@ confluence stats
691
691
  | `read <pageId_or_url>` | Read page content | `--format <html\|text\|storage\|markdown>` |
692
692
  | `info <pageId_or_url>` | Get page information | `--format <text\|json>` |
693
693
  | `search <query>` | Search for pages | `--limit <number>` |
694
- | `spaces` | List all available spaces | |
694
+ | `spaces` | List available spaces | `--limit <number>` |
695
695
  | `find <title>` | Find a page by its title | `--space <spaceKey>` |
696
696
  | `children <pageId>` | List child pages of a page | `--recursive`, `--max-depth <number>`, `--format <list\|tree\|json>`, `--show-url`, `--show-id` |
697
- | `create <title> <spaceKey>` | Create a new page | `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>`|
698
- | `create-child <title> <parentId>` | Create a child page | `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>` |
697
+ | `create <title> <spaceKey>` | Create a new page or folder | `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>`, `--type <page\|folder>` |
698
+ | `create-child <title> <parentId>` | Create a child page or folder | `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>`, `--type <page\|folder>` |
699
699
  | `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` |
700
700
  | `update <pageId>` | Update a page's title or content | `--title <string>`, `--content <string>`, `--file <path>`, `--format <storage\|html\|markdown>` |
701
701
  | `move <pageId_or_url> <newParentId_or_url>` | Move a page to a new parent location | `--title <string>` |
@@ -739,7 +739,7 @@ confluence info 123456789
739
739
  # Search with limit
740
740
  confluence search "API documentation" --limit 3
741
741
 
742
- # List all spaces
742
+ # List spaces
743
743
  confluence spaces
744
744
 
745
745
  # Move a page to a new parent
@@ -752,6 +752,10 @@ confluence move 123456789 987654321 --title "New Title"
752
752
  confluence attachment-upload 123456789 --file ./report.pdf
753
753
  confluence attachment-delete 123456789 998877 --yes
754
754
 
755
+ # Create a folder (no content body required)
756
+ confluence create "Engineering Docs" MYSPACE --type folder
757
+ confluence create-child "Sub-folder" 123456789 --type folder
758
+
755
759
  # View usage statistics
756
760
  confluence stats
757
761
 
package/bin/confluence.js CHANGED
@@ -22,6 +22,20 @@ function assertNonEmpty(value, label) {
22
22
  }
23
23
  }
24
24
 
25
+ const VALID_TYPES = ['page', 'folder'];
26
+
27
+ function assertValidType(type) {
28
+ if (!VALID_TYPES.includes(type)) {
29
+ throw new Error(`Invalid type "${type}". Valid: ${VALID_TYPES.join(', ')}`);
30
+ }
31
+ }
32
+
33
+ function assertNoBodyForFolder(type, options) {
34
+ if (type === 'folder' && (options.file || options.content)) {
35
+ throw new Error('--file/--content is not allowed with --type folder (folders have no body).');
36
+ }
37
+ }
38
+
25
39
  function handleCommandError(analytics, commandName, error) {
26
40
  analytics.track(commandName, false);
27
41
  console.error(chalk.red('Error:'), error.message);
@@ -139,13 +153,14 @@ program
139
153
  // List spaces command
140
154
  program
141
155
  .command('spaces')
142
- .description('List all Confluence spaces')
143
- .action(async () => {
156
+ .description('List Confluence spaces')
157
+ .option('-l, --limit <limit>', 'Limit number of results', '500')
158
+ .action(async (options) => {
144
159
  const analytics = new Analytics();
145
160
  try {
146
161
  const config = getConfig(getProfileName());
147
162
  const client = new ConfluenceClient(config);
148
- const spaces = await client.getSpaces();
163
+ const spaces = await client.getSpaces(parseInt(options.limit));
149
164
 
150
165
  console.log(chalk.blue('Available spaces:'));
151
166
  spaces.forEach(space => {
@@ -218,15 +233,18 @@ program
218
233
  // Create command
219
234
  program
220
235
  .command('create <title> <spaceKey>')
221
- .description('Create a new Confluence page')
236
+ .description('Create a new Confluence page or folder')
222
237
  .option('-f, --file <file>', 'Read content from file')
223
238
  .option('-c, --content <content>', 'Page content as string')
224
239
  .option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
240
+ .option('--type <type>', 'Content type (page, folder)', 'page')
225
241
  .action(async (title, spaceKey, options) => {
226
242
  const analytics = new Analytics();
227
243
  try {
228
244
  assertNonEmpty(title, 'title');
229
245
  assertNonEmpty(spaceKey, 'spaceKey');
246
+ assertValidType(options.type);
247
+ assertNoBodyForFolder(options.type, options);
230
248
 
231
249
  const config = getConfig(getProfileName());
232
250
  assertWritable(config);
@@ -242,13 +260,14 @@ program
242
260
  content = fs.readFileSync(options.file, 'utf8');
243
261
  } else if (options.content) {
244
262
  content = options.content;
245
- } else {
263
+ } else if (options.type !== 'folder') {
246
264
  throw new Error('Either --file or --content option is required');
247
265
  }
248
266
 
249
- const result = await client.createPage(title, spaceKey, content, options.format);
250
-
251
- console.log(chalk.green('✅ Page created successfully!'));
267
+ const result = await client.createPage(title, spaceKey, content, options.format, options.type);
268
+
269
+ const label = options.type === 'folder' ? 'Folder' : 'Page';
270
+ console.log(chalk.green(`✅ ${label} created successfully!`));
252
271
  console.log(`Title: ${chalk.blue(result.title)}`);
253
272
  console.log(`ID: ${chalk.blue(result.id)}`);
254
273
  console.log(`Space: ${chalk.blue(result.space.name)} (${result.space.key})`);
@@ -263,15 +282,18 @@ program
263
282
  // Create child page command
264
283
  program
265
284
  .command('create-child <title> <parentId>')
266
- .description('Create a new Confluence page as a child of another page')
285
+ .description('Create a new Confluence page or folder as a child of another page')
267
286
  .option('-f, --file <file>', 'Read content from file')
268
287
  .option('-c, --content <content>', 'Page content as string')
269
288
  .option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
289
+ .option('--type <type>', 'Content type (page, folder)', 'page')
270
290
  .action(async (title, parentId, options) => {
271
291
  const analytics = new Analytics();
272
292
  try {
273
293
  assertNonEmpty(title, 'title');
274
294
  assertNonEmpty(parentId, 'parentId');
295
+ assertValidType(options.type);
296
+ assertNoBodyForFolder(options.type, options);
275
297
 
276
298
  const config = getConfig(getProfileName());
277
299
  assertWritable(config);
@@ -280,9 +302,9 @@ program
280
302
  // Get parent page info to get space key
281
303
  const parentInfo = await client.getPageInfo(parentId);
282
304
  const spaceKey = parentInfo.space.key;
283
-
305
+
284
306
  let content = '';
285
-
307
+
286
308
  if (options.file) {
287
309
  const fs = require('fs');
288
310
  if (!fs.existsSync(options.file)) {
@@ -291,13 +313,14 @@ program
291
313
  content = fs.readFileSync(options.file, 'utf8');
292
314
  } else if (options.content) {
293
315
  content = options.content;
294
- } else {
316
+ } else if (options.type !== 'folder') {
295
317
  throw new Error('Either --file or --content option is required');
296
318
  }
297
-
298
- const result = await client.createChildPage(title, spaceKey, parentId, content, options.format);
299
-
300
- console.log(chalk.green('✅ Child page created successfully!'));
319
+
320
+ const result = await client.createChildPage(title, spaceKey, parentId, content, options.format, options.type);
321
+
322
+ const label = options.type === 'folder' ? 'Folder' : 'Child page';
323
+ console.log(chalk.green(`✅ ${label} created successfully!`));
301
324
  console.log(`Title: ${chalk.blue(result.title)}`);
302
325
  console.log(`ID: ${chalk.blue(result.id)}`);
303
326
  console.log(`Parent: ${chalk.blue(parentInfo.title)} (${parentId})`);
@@ -2083,6 +2106,8 @@ module.exports = {
2083
2106
  sanitizeFilename,
2084
2107
  assertWritable,
2085
2108
  assertNonEmpty,
2109
+ assertValidType,
2110
+ assertNoBodyForFolder,
2086
2111
  handleCommandError,
2087
2112
  },
2088
2113
  };
@@ -437,12 +437,12 @@ class ConfluenceClient {
437
437
  }
438
438
 
439
439
  /**
440
- * Get all spaces
440
+ * Get spaces
441
441
  */
442
- async getSpaces() {
442
+ async getSpaces(limit = 500) {
443
443
  const response = await this.client.get('/space', {
444
444
  params: {
445
- limit: 500
445
+ limit
446
446
  }
447
447
  });
448
448
 
@@ -1207,29 +1207,32 @@ class ConfluenceClient {
1207
1207
  /**
1208
1208
  * Create a new Confluence page
1209
1209
  */
1210
- async createPage(title, spaceKey, content, format = 'storage') {
1211
- let storageContent = content;
1212
-
1213
- if (format === 'markdown') {
1214
- storageContent = this.markdownToStorage(content);
1215
- } else if (format === 'html') {
1216
- // Convert HTML directly to storage format (no macro wrapper)
1217
- storageContent = content;
1218
- }
1219
-
1210
+ async createPage(title, spaceKey, content, format = 'storage', type = 'page') {
1220
1211
  const pageData = {
1221
- type: 'page',
1212
+ type: type,
1222
1213
  title: title,
1223
1214
  space: {
1224
1215
  key: spaceKey
1225
- },
1226
- body: {
1216
+ }
1217
+ };
1218
+
1219
+ if (type !== 'folder') {
1220
+ let storageContent = content;
1221
+
1222
+ if (format === 'markdown') {
1223
+ storageContent = this.markdownToStorage(content);
1224
+ } else if (format === 'html') {
1225
+ // Convert HTML directly to storage format (no macro wrapper)
1226
+ storageContent = content;
1227
+ }
1228
+
1229
+ pageData.body = {
1227
1230
  storage: {
1228
1231
  value: storageContent,
1229
1232
  representation: 'storage'
1230
1233
  }
1231
- }
1232
- };
1234
+ };
1235
+ }
1233
1236
 
1234
1237
  const response = await this.client.post('/content', pageData);
1235
1238
  return response.data;
@@ -1238,18 +1241,9 @@ class ConfluenceClient {
1238
1241
  /**
1239
1242
  * Create a new Confluence page as a child of another page
1240
1243
  */
1241
- async createChildPage(title, spaceKey, parentId, content, format = 'storage') {
1242
- let storageContent = content;
1243
-
1244
- if (format === 'markdown') {
1245
- storageContent = this.markdownToStorage(content);
1246
- } else if (format === 'html') {
1247
- // Convert HTML directly to storage format (no macro wrapper)
1248
- storageContent = content;
1249
- }
1250
-
1244
+ async createChildPage(title, spaceKey, parentId, content, format = 'storage', type = 'page') {
1251
1245
  const pageData = {
1252
- type: 'page',
1246
+ type: type,
1253
1247
  title: title,
1254
1248
  space: {
1255
1249
  key: spaceKey
@@ -1258,14 +1252,26 @@ class ConfluenceClient {
1258
1252
  {
1259
1253
  id: parentId
1260
1254
  }
1261
- ],
1262
- body: {
1255
+ ]
1256
+ };
1257
+
1258
+ if (type !== 'folder') {
1259
+ let storageContent = content;
1260
+
1261
+ if (format === 'markdown') {
1262
+ storageContent = this.markdownToStorage(content);
1263
+ } else if (format === 'html') {
1264
+ // Convert HTML directly to storage format (no macro wrapper)
1265
+ storageContent = content;
1266
+ }
1267
+
1268
+ pageData.body = {
1263
1269
  storage: {
1264
1270
  value: storageContent,
1265
1271
  representation: 'storage'
1266
1272
  }
1267
- }
1268
- };
1273
+ };
1274
+ }
1269
1275
 
1270
1276
  const response = await this.client.post('/content', pageData);
1271
1277
  return response.data;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "2.1.12",
3
+ "version": "2.3.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "confluence-cli",
9
- "version": "2.1.12",
9
+ "version": "2.3.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "axios": "^1.15.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "2.1.12",
3
+ "version": "2.3.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": {
@@ -221,7 +221,7 @@ confluence search --cql 'siteSearch ~ "deployment pipeline" and space = "MYSPACE
221
221
 
222
222
  ### `spaces`
223
223
 
224
- List all accessible Confluence spaces (key and name).
224
+ List accessible Confluence spaces (key and name).
225
225
 
226
226
  ```sh
227
227
  confluence spaces
@@ -255,10 +255,10 @@ confluence children 123456789 --recursive --format tree --show-id
255
255
 
256
256
  ### `create <title> <spaceKey>`
257
257
 
258
- Create a new top-level page in a space.
258
+ Create a new top-level page or folder in a space.
259
259
 
260
260
  ```sh
261
- confluence create <title> <spaceKey> [--content <string>] [--file <path>] [--format storage|html|markdown]
261
+ confluence create <title> <spaceKey> [--content <string>] [--file <path>] [--format storage|html|markdown] [--type page|folder]
262
262
  ```
263
263
 
264
264
  | Option | Default | Description |
@@ -266,31 +266,34 @@ confluence create <title> <spaceKey> [--content <string>] [--file <path>] [--for
266
266
  | `--content` | — | Inline content string |
267
267
  | `--file` | — | Path to content file |
268
268
  | `--format` | `storage` | Content format |
269
+ | `--type` | `page` | Content type — `page` (default) or `folder`. Folders have no body. |
269
270
 
270
- Either `--content` or `--file` is required.
271
+ Either `--content` or `--file` is required for pages. Folders take no content — passing `--content` or `--file` with `--type folder` is rejected.
271
272
 
272
273
  ```sh
273
274
  confluence create "Project Overview" MYSPACE --content "# Hello" --format markdown
274
275
  confluence create "Release Notes" MYSPACE --file ./notes.md --format markdown
276
+ confluence create "Engineering Docs" MYSPACE --type folder
275
277
  ```
276
278
 
277
- Outputs the new page ID and URL on success.
279
+ Outputs the new page (or folder) ID and URL on success.
278
280
 
279
281
  ---
280
282
 
281
283
  ### `create-child <title> <parentId>`
282
284
 
283
- Create a child page under an existing page. Inherits the parent's space automatically.
285
+ Create a child page or folder under an existing page. Inherits the parent's space automatically.
284
286
 
285
287
  ```sh
286
- confluence create-child <title> <parentId> [--content <string>] [--file <path>] [--format storage|html|markdown]
288
+ confluence create-child <title> <parentId> [--content <string>] [--file <path>] [--format storage|html|markdown] [--type page|folder]
287
289
  ```
288
290
 
289
- Options are identical to `create`. Either `--content` or `--file` is required.
291
+ Options are identical to `create`. Either `--content` or `--file` is required for pages; folders take no content.
290
292
 
291
293
  ```sh
292
294
  confluence create-child "Chapter 1" 123456789 --content "Content here" --format markdown
293
295
  confluence create-child "API Guide" 123456789 --file ./api.md --format markdown
296
+ confluence create-child "Sub-folder" 123456789 --type folder
294
297
  ```
295
298
 
296
299
  ---