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 +9 -5
- package/bin/confluence.js +41 -16
- package/lib/confluence-client.js +39 -33
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/plugins/confluence/skills/confluence/SKILL.md +11 -8
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
|
|
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
|
|
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
|
|
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
|
|
143
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|
package/lib/confluence-client.js
CHANGED
|
@@ -437,12 +437,12 @@ class ConfluenceClient {
|
|
|
437
437
|
}
|
|
438
438
|
|
|
439
439
|
/**
|
|
440
|
-
* Get
|
|
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
|
|
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:
|
|
1212
|
+
type: type,
|
|
1222
1213
|
title: title,
|
|
1223
1214
|
space: {
|
|
1224
1215
|
key: spaceKey
|
|
1225
|
-
}
|
|
1226
|
-
|
|
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:
|
|
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
|
-
|
|
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;
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "confluence-cli",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
9
|
+
"version": "2.3.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"axios": "^1.15.0",
|
package/package.json
CHANGED
|
@@ -221,7 +221,7 @@ confluence search --cql 'siteSearch ~ "deployment pipeline" and space = "MYSPACE
|
|
|
221
221
|
|
|
222
222
|
### `spaces`
|
|
223
223
|
|
|
224
|
-
List
|
|
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
|
---
|