@aaronshaf/confluence-cli 0.1.15 → 1.0.1

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
@@ -1,40 +1,39 @@
1
- # cn
1
+ # confluence-cli
2
2
 
3
- CLI for syncing Confluence spaces to local markdown.
4
-
5
- ## Install
3
+ ## Installation
6
4
 
7
5
  ```bash
6
+ # Install Bun runtime
7
+ curl -fsSL https://bun.sh/install | bash
8
+
9
+ # Install confluence-cli
8
10
  bun install -g @aaronshaf/confluence-cli
9
11
  ```
10
12
 
11
13
  ## Getting Started
12
14
 
13
15
  ```bash
14
- # 1. Configure your Confluence credentials
16
+ # Configure your Confluence credentials
15
17
  cn setup
16
18
 
17
- # 2. Clone a Confluence space
18
- cn clone <SPACE_KEY>
19
+ # Search pages
20
+ cn search "authentication"
19
21
 
20
- # 3. Pull pages as markdown
21
- cd <SPACE_KEY>
22
- cn pull
23
- ```
22
+ # Open a page in the browser
23
+ cn open "Getting Started"
24
24
 
25
- The space key is the identifier in your Confluence URL:
26
- `https://yoursite.atlassian.net/wiki/spaces/<SPACE_KEY>/...`
25
+ # Create a page
26
+ cn create "My Page" --space ENG
27
27
 
28
- Credentials are stored in `~/.cn/config.json`. Space configuration is saved to `.confluence.json` in the synced directory.
28
+ # List spaces
29
+ cn spaces
30
+ ```
29
31
 
30
32
  ## Commands
31
33
 
32
34
  | Command | Description |
33
35
  |---------|-------------|
34
36
  | `cn setup` | Configure Confluence credentials |
35
- | `cn clone <SPACE_KEY>` | Clone a space to a new folder |
36
- | `cn pull` | Pull changes from Confluence as markdown |
37
- | `cn push [file]` | Push local markdown file(s) to Confluence |
38
37
  | `cn status` | Check connection and sync status |
39
38
  | `cn tree` | Display page hierarchy |
40
39
  | `cn open [page]` | Open page in browser |
@@ -42,20 +41,19 @@ Credentials are stored in `~/.cn/config.json`. Space configuration is saved to `
42
41
  | `cn search <query>` | Search pages using CQL |
43
42
  | `cn spaces` | List available spaces |
44
43
  | `cn info <id\|file>` | Show page info and labels |
45
- | `cn create <title>` | Create a new page |
44
+ | `cn create <title>` | Create a new page (pipe content via stdin) |
45
+ | `cn update <id>` | Update an existing page (pipe content via stdin) |
46
46
  | `cn delete <id>` | Delete a page |
47
47
  | `cn comments <id\|file>` | Show page comments |
48
48
  | `cn labels <id\|file>` | Manage page labels |
49
49
  | `cn move <id\|file> <parentId>` | Move a page to a new parent |
50
50
  | `cn attachments <id\|file>` | Manage page attachments |
51
+ | `cn folder <subcommand>` | Manage folders (create, list, delete, move) |
52
+ | `cn clone <SPACE_KEY>` | Clone a space to a new folder |
53
+ | `cn pull` | Pull changes from Confluence as markdown |
51
54
 
52
55
  Run `cn <command> --help` for details on each command.
53
56
 
54
- ## Requirements
55
-
56
- - Bun 1.2.0+
57
- - Confluence Cloud account
58
-
59
57
  ## Development
60
58
 
61
59
  ```bash
@@ -64,6 +62,10 @@ bun run cn --help
64
62
  bun test
65
63
  ```
66
64
 
65
+ ## See also
66
+
67
+ - [pchuri/confluence-cli](https://github.com/pchuri/confluence-cli)
68
+
67
69
  ## License
68
70
 
69
71
  MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aaronshaf/confluence-cli",
3
- "version": "0.1.15",
3
+ "version": "1.0.1",
4
4
  "description": "Confluence CLI for syncing spaces and local markdown files",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,11 +4,13 @@ import { ConfigManager } from '../../lib/config.js';
4
4
  import { EXIT_CODES } from '../../lib/errors.js';
5
5
  import { readSpaceConfig } from '../../lib/space-config.js';
6
6
  import { openUrl } from '../utils/browser.js';
7
+ import { VALID_FORMATS, isValidFormat, readStdin } from '../utils/stdin.js';
7
8
 
8
9
  export interface CreateCommandOptions {
9
10
  space?: string;
10
11
  parent?: string;
11
12
  open?: boolean;
13
+ format?: string;
12
14
  }
13
15
 
14
16
  export async function createCommand(title: string, options: CreateCommandOptions = {}): Promise<void> {
@@ -20,6 +22,23 @@ export async function createCommand(title: string, options: CreateCommandOptions
20
22
  process.exit(EXIT_CODES.CONFIG_ERROR);
21
23
  }
22
24
 
25
+ const rawFormat = options.format ?? 'storage';
26
+ if (!isValidFormat(rawFormat)) {
27
+ console.error(chalk.red(`Invalid format: ${rawFormat}`));
28
+ console.log(chalk.gray(`Valid formats: ${VALID_FORMATS.join(', ')}`));
29
+ process.exit(EXIT_CODES.INVALID_ARGUMENTS);
30
+ }
31
+ const representation = rawFormat;
32
+
33
+ let bodyValue = '';
34
+ if (!process.stdin.isTTY) {
35
+ bodyValue = await readStdin();
36
+ if (bodyValue.trim().length === 0) {
37
+ console.error(chalk.red('Stdin is empty. Provide content to create a page with body.'));
38
+ process.exit(EXIT_CODES.INVALID_ARGUMENTS);
39
+ }
40
+ }
41
+
23
42
  const client = new ConfluenceClient(config);
24
43
  let spaceId: string | undefined;
25
44
 
@@ -41,8 +60,8 @@ export async function createCommand(title: string, options: CreateCommandOptions
41
60
  title,
42
61
  parentId: options.parent,
43
62
  body: {
44
- representation: 'storage',
45
- value: '',
63
+ representation,
64
+ value: bodyValue,
46
65
  },
47
66
  });
48
67
 
@@ -0,0 +1,189 @@
1
+ import { confirm } from '@inquirer/prompts';
2
+ import chalk from 'chalk';
3
+ import { ConfluenceClient } from '../../lib/confluence-client/index.js';
4
+ import { ConfigManager } from '../../lib/config.js';
5
+ import { EXIT_CODES } from '../../lib/errors.js';
6
+ import { escapeXml } from '../../lib/formatters.js';
7
+ import { readSpaceConfig } from '../../lib/space-config.js';
8
+
9
+ /**
10
+ * Extract a flag value from an args array, e.g. --space DOCS -> "DOCS"
11
+ */
12
+ function getFlagValue(args: string[], flag: string): string | undefined {
13
+ const idx = args.indexOf(flag);
14
+ if (idx !== -1 && idx + 1 < args.length && !args[idx + 1].startsWith('--')) {
15
+ return args[idx + 1];
16
+ }
17
+ return undefined;
18
+ }
19
+
20
+ /**
21
+ * Get positional args by stripping flags and their values
22
+ */
23
+ function getPositionals(args: string[], flagsWithValues: string[]): string[] {
24
+ const result: string[] = [];
25
+ for (let i = 0; i < args.length; i++) {
26
+ if (args[i].startsWith('--')) {
27
+ if (flagsWithValues.includes(args[i])) {
28
+ i++; // skip the value too
29
+ }
30
+ } else {
31
+ result.push(args[i]);
32
+ }
33
+ }
34
+ return result;
35
+ }
36
+
37
+ export async function folderCommand(subcommand: string, subArgs: string[], allArgs: string[]): Promise<void> {
38
+ const configManager = new ConfigManager();
39
+ const config = await configManager.getConfig();
40
+
41
+ if (!config) {
42
+ console.error(chalk.red('Not configured. Run: cn setup'));
43
+ process.exit(EXIT_CODES.CONFIG_ERROR);
44
+ }
45
+
46
+ const client = new ConfluenceClient(config);
47
+
48
+ switch (subcommand) {
49
+ case 'create': {
50
+ const positionals = getPositionals(subArgs, ['--space', '--parent']);
51
+ const title = positionals[0];
52
+ if (!title) {
53
+ console.error(chalk.red('Folder title is required.'));
54
+ console.log(chalk.gray('Usage: cn folder create <title> --space <key>'));
55
+ process.exit(EXIT_CODES.INVALID_ARGUMENTS);
56
+ }
57
+
58
+ const spaceKeyArg = getFlagValue(allArgs, '--space');
59
+ const parentId = getFlagValue(allArgs, '--parent');
60
+
61
+ let spaceId: string;
62
+
63
+ if (spaceKeyArg) {
64
+ const space = await client.getSpaceByKey(spaceKeyArg);
65
+ spaceId = space.id;
66
+ } else {
67
+ const spaceConfig = readSpaceConfig(process.cwd());
68
+ if (!spaceConfig) {
69
+ console.error(chalk.red('Not in a cloned space directory. Use --space to specify a space key.'));
70
+ process.exit(EXIT_CODES.INVALID_ARGUMENTS);
71
+ }
72
+ spaceId = spaceConfig.spaceId;
73
+ }
74
+
75
+ const folder = await client.createFolder({ spaceId, title, parentId });
76
+ console.log(`${chalk.green('Created:')} "${folder.title}" (${folder.id})`);
77
+ break;
78
+ }
79
+
80
+ case 'list': {
81
+ const spaceKeyArg = getFlagValue(allArgs, '--space');
82
+
83
+ let spaceKey: string;
84
+
85
+ if (spaceKeyArg) {
86
+ spaceKey = spaceKeyArg;
87
+ } else {
88
+ const spaceConfig = readSpaceConfig(process.cwd());
89
+ if (!spaceConfig) {
90
+ console.error(chalk.red('Not in a cloned space directory. Use --space to specify a space key.'));
91
+ process.exit(EXIT_CODES.INVALID_ARGUMENTS);
92
+ }
93
+ spaceKey = spaceConfig.spaceKey;
94
+ }
95
+
96
+ const cql = `type=folder AND space="${spaceKey.replace(/"/g, '\\"')}"`;
97
+ const PAGE_SIZE = 100;
98
+ const allResults: (typeof firstPage.results)[number][] = [];
99
+ let start = 0;
100
+
101
+ const firstPage = await client.search(cql, PAGE_SIZE, start);
102
+ allResults.push(...firstPage.results);
103
+ const total = firstPage.totalSize ?? firstPage.results.length;
104
+
105
+ while (allResults.length < total) {
106
+ start += PAGE_SIZE;
107
+ const page = await client.search(cql, PAGE_SIZE, start);
108
+ if (page.results.length === 0) break;
109
+ allResults.push(...page.results);
110
+ }
111
+
112
+ const xml = allArgs.includes('--xml');
113
+
114
+ if (xml) {
115
+ console.log('<folders>');
116
+ for (const result of allResults) {
117
+ const c = result.content;
118
+ if (c) {
119
+ console.log(` <folder>`);
120
+ console.log(` <id>${escapeXml(c.id ?? '')}</id>`);
121
+ console.log(` <title>${escapeXml(c.title ?? '')}</title>`);
122
+ console.log(` </folder>`);
123
+ }
124
+ }
125
+ console.log('</folders>');
126
+ } else {
127
+ if (allResults.length === 0) {
128
+ console.log(chalk.gray('No folders found.'));
129
+ } else {
130
+ for (const result of allResults) {
131
+ const c = result.content;
132
+ if (c) {
133
+ console.log(`${chalk.cyan(c.id)} ${c.title}`);
134
+ }
135
+ }
136
+ }
137
+ }
138
+ break;
139
+ }
140
+
141
+ case 'delete': {
142
+ const positionals = getPositionals(subArgs, []);
143
+ const folderId = positionals[0];
144
+ if (!folderId) {
145
+ console.error(chalk.red('Folder ID is required.'));
146
+ console.log(chalk.gray('Usage: cn folder delete <id>'));
147
+ process.exit(EXIT_CODES.INVALID_ARGUMENTS);
148
+ }
149
+
150
+ if (!allArgs.includes('--force')) {
151
+ const folder = await client.getFolder(folderId);
152
+ const confirmed = await confirm({
153
+ message: `Delete folder "${folder.title}" (${folderId})?`,
154
+ default: false,
155
+ });
156
+ if (!confirmed) {
157
+ console.log('Cancelled.');
158
+ return;
159
+ }
160
+ }
161
+
162
+ await client.deleteFolder(folderId);
163
+ console.log(`${chalk.green('Deleted:')} ${folderId}`);
164
+ break;
165
+ }
166
+
167
+ case 'move': {
168
+ const positionals = getPositionals(subArgs, []);
169
+ if (positionals.length < 2) {
170
+ console.error(chalk.red('Folder ID and parent ID are required.'));
171
+ console.log(chalk.gray('Usage: cn folder move <id> <parentId>'));
172
+ process.exit(EXIT_CODES.INVALID_ARGUMENTS);
173
+ }
174
+
175
+ const folderId = positionals[0] as string;
176
+ const parentId = positionals[1] as string;
177
+ const [folder, parent] = await Promise.all([client.getFolder(folderId), client.getFolder(parentId)]);
178
+
179
+ await client.movePage(folderId, parentId);
180
+ console.log(`${chalk.green('Moved:')} "${folder.title}" under "${parent.title}"`);
181
+ break;
182
+ }
183
+
184
+ default:
185
+ console.error(chalk.red(`Unknown folder subcommand: ${subcommand}`));
186
+ console.log(chalk.gray('Run "cn folder --help" for usage information.'));
187
+ process.exit(EXIT_CODES.INVALID_ARGUMENTS);
188
+ }
189
+ }
@@ -6,6 +6,8 @@ import { escapeXml } from '../../lib/formatters.js';
6
6
 
7
7
  export interface SpacesCommandOptions {
8
8
  xml?: boolean;
9
+ limit?: number;
10
+ page?: number;
9
11
  }
10
12
 
11
13
  export async function spacesCommand(options: SpacesCommandOptions = {}): Promise<void> {
@@ -17,8 +19,11 @@ export async function spacesCommand(options: SpacesCommandOptions = {}): Promise
17
19
  process.exit(EXIT_CODES.CONFIG_ERROR);
18
20
  }
19
21
 
22
+ const limit = options.limit ?? 25;
23
+ const page = options.page ?? 1;
20
24
  const client = new ConfluenceClient(config);
21
- const spaces = await client.getAllSpaces();
25
+ const response = await client.getSpaces(limit, page);
26
+ const spaces = response.results;
22
27
 
23
28
  if (options.xml) {
24
29
  console.log('<spaces>');
@@ -39,4 +44,8 @@ export async function spacesCommand(options: SpacesCommandOptions = {}): Promise
39
44
  for (const space of spaces) {
40
45
  console.log(`${chalk.bold(space.key)} ${space.name} ${chalk.gray(space.id)}`);
41
46
  }
47
+
48
+ if (response.size === limit) {
49
+ console.log(chalk.gray(`\nPage ${page}. Use --page ${page + 1} for next page, --limit to change page size.`));
50
+ }
42
51
  }
@@ -0,0 +1,72 @@
1
+ import chalk from 'chalk';
2
+ import { ConfluenceClient } from '../../lib/confluence-client/index.js';
3
+ import { ConfigManager } from '../../lib/config.js';
4
+ import { EXIT_CODES } from '../../lib/errors.js';
5
+ import { VALID_FORMATS, isValidFormat, readStdin } from '../utils/stdin.js';
6
+
7
+ export interface UpdateCommandOptions {
8
+ format?: string;
9
+ title?: string;
10
+ message?: string;
11
+ }
12
+
13
+ export async function updateCommand(pageId: string, options: UpdateCommandOptions = {}): Promise<void> {
14
+ const configManager = new ConfigManager();
15
+ const config = await configManager.getConfig();
16
+
17
+ if (!config) {
18
+ console.error(chalk.red('Not configured. Run: cn setup'));
19
+ process.exit(EXIT_CODES.CONFIG_ERROR);
20
+ }
21
+
22
+ if (process.stdin.isTTY) {
23
+ console.error(chalk.red('No content provided. Pipe content via stdin.'));
24
+ console.log(chalk.gray('Usage: echo "<p>Content</p>" | cn update <id>'));
25
+ process.exit(EXIT_CODES.INVALID_ARGUMENTS);
26
+ }
27
+
28
+ const rawFormat = options.format ?? 'storage';
29
+ if (!isValidFormat(rawFormat)) {
30
+ console.error(chalk.red(`Invalid format: ${rawFormat}`));
31
+ console.log(chalk.gray(`Valid formats: ${VALID_FORMATS.join(', ')}`));
32
+ process.exit(EXIT_CODES.INVALID_ARGUMENTS);
33
+ }
34
+ const representation = rawFormat;
35
+
36
+ const bodyValue = await readStdin();
37
+ if (bodyValue.trim().length === 0) {
38
+ console.error(chalk.red('Stdin is empty. Provide content to update the page.'));
39
+ process.exit(EXIT_CODES.INVALID_ARGUMENTS);
40
+ }
41
+
42
+ const client = new ConfluenceClient(config);
43
+ const current = await client.getPage(pageId, false);
44
+
45
+ if (!current) {
46
+ console.error(chalk.red(`Page not found: ${pageId}`));
47
+ process.exit(EXIT_CODES.GENERAL_ERROR);
48
+ }
49
+
50
+ const currentVersion = current.version?.number ?? 1;
51
+ const title = options.title ?? current.title;
52
+
53
+ const updated = await client.updatePage({
54
+ id: pageId,
55
+ status: 'current',
56
+ title,
57
+ body: {
58
+ representation,
59
+ value: bodyValue,
60
+ },
61
+ version: {
62
+ number: currentVersion + 1,
63
+ message: options.message,
64
+ },
65
+ });
66
+
67
+ console.log(`${chalk.green('Updated:')} ${chalk.bold(updated.title)} ${chalk.gray(updated.id)}`);
68
+ if (updated._links?.webui) {
69
+ const url = `${config.confluenceUrl}/wiki${updated._links.webui}`;
70
+ console.log(`URL: ${chalk.blue(url)}`);
71
+ }
72
+ }
package/src/cli/help.ts CHANGED
@@ -89,41 +89,6 @@ ${chalk.yellow('Examples:')}
89
89
  `);
90
90
  }
91
91
 
92
- export function showPushHelp(): void {
93
- console.log(`
94
- ${chalk.bold('cn push - Push local markdown files to Confluence')}
95
-
96
- ${chalk.yellow('Usage:')}
97
- cn push [file] [options]
98
-
99
- ${chalk.yellow('Description:')}
100
- Push local markdown files to Confluence.
101
-
102
- With a file argument: pushes that single file.
103
- Without arguments: scans for changed files and prompts y/n for each.
104
-
105
- ${chalk.yellow('Arguments:')}
106
- file Path to markdown file (optional)
107
-
108
- ${chalk.yellow('Options:')}
109
- --force Ignore version conflicts and overwrite
110
- --dry-run Preview changes without pushing
111
- --help Show this help message
112
-
113
- ${chalk.yellow('Examples:')}
114
- cn push Scan and prompt for all changed files
115
- cn push --dry-run Preview what would be pushed
116
- cn push ./docs/page.md Push single page
117
- cn push ./docs/page.md --force Force push (ignore version conflict)
118
-
119
- ${chalk.yellow('Notes:')}
120
- - New files (no page_id) will be created on Confluence
121
- - Modified files are detected by comparing file mtime vs synced_at
122
- - Only basic markdown elements are fully supported
123
- - Files are automatically renamed to match page titles (except index.md/README.md)
124
- `);
125
- }
126
-
127
92
  export function showStatusHelp(): void {
128
93
  console.log(`
129
94
  ${chalk.bold('cn status - Check connection and sync status')}
@@ -179,7 +144,8 @@ ${chalk.yellow('Usage:')}
179
144
  cn open [options]
180
145
 
181
146
  ${chalk.yellow('Description:')}
182
- Opens a Confluence page in your default browser.
147
+ Opens a Confluence page in your default web browser (launches a browser window).
148
+ Not suitable for non-interactive environments (CI, bots, scripts).
183
149
  Without arguments, opens the space home page.
184
150
 
185
151
  ${chalk.yellow('Arguments:')}
@@ -254,6 +220,7 @@ ${chalk.bold('cn create - Create a new Confluence page')}
254
220
 
255
221
  ${chalk.yellow('Usage:')}
256
222
  cn create <title> [options]
223
+ echo "<p>Content</p>" | cn create <title> [options]
257
224
 
258
225
  ${chalk.yellow('Arguments:')}
259
226
  title Page title (required)
@@ -261,8 +228,37 @@ ${chalk.yellow('Arguments:')}
261
228
  ${chalk.yellow('Options:')}
262
229
  --space <key> Space key (required if not in cloned dir)
263
230
  --parent <id> Parent page ID
231
+ --format <format> Body format: storage (default), wiki, atlas_doc_format
264
232
  --open Open page in browser after creation
265
233
  --help Show this help message
234
+
235
+ ${chalk.yellow('Examples:')}
236
+ cn create "My Page" --space ENG
237
+ echo "<p>Hello</p>" | cn create "My Page" --space ENG
238
+ echo "h1. Hello" | cn create "Wiki Page" --space ENG --format wiki
239
+ `);
240
+ }
241
+
242
+ export function showUpdateHelp(): void {
243
+ console.log(`
244
+ ${chalk.bold('cn update - Update an existing Confluence page')}
245
+
246
+ ${chalk.yellow('Usage:')}
247
+ echo "<p>Content</p>" | cn update <id> [options]
248
+
249
+ ${chalk.yellow('Arguments:')}
250
+ id Page ID (required)
251
+
252
+ ${chalk.yellow('Options:')}
253
+ --format <format> Body format: storage (default), wiki, atlas_doc_format
254
+ --title <title> New page title (default: keep existing title)
255
+ --message <msg> Version message
256
+ --help Show this help message
257
+
258
+ ${chalk.yellow('Examples:')}
259
+ echo "<p>Updated content</p>" | cn update 123456
260
+ echo "<p>New content</p>" | cn update 123456 --title "New Title"
261
+ echo "h1. Hello" | cn update 123456 --format wiki --message "Updated via automation"
266
262
  `);
267
263
  }
268
264
 
@@ -316,6 +312,35 @@ ${chalk.yellow('Options:')}
316
312
  `);
317
313
  }
318
314
 
315
+ export function showFolderHelp(): void {
316
+ console.log(`
317
+ ${chalk.bold('cn folder - Manage Confluence folders')}
318
+
319
+ ${chalk.yellow('Usage:')}
320
+ cn folder <subcommand> [options]
321
+
322
+ ${chalk.yellow('Subcommands:')}
323
+ create <title> Create a new folder
324
+ list List folders in a space
325
+ delete <id> Delete a folder
326
+ move <id> <parentId> Move a folder to a new parent
327
+
328
+ ${chalk.yellow('Options:')}
329
+ --space <key> Space key (required for create/list if not in cloned dir)
330
+ --parent <id> Parent folder ID (for create)
331
+ --force Skip confirmation prompt (for delete)
332
+ --xml Output in XML format (for list)
333
+ --help Show this help message
334
+
335
+ ${chalk.yellow('Examples:')}
336
+ cn folder create "My Folder" --space DOCS
337
+ cn folder create "Nested" --space DOCS --parent 123456
338
+ cn folder list --space DOCS
339
+ cn folder delete 123456
340
+ cn folder move 123456 789012
341
+ `);
342
+ }
343
+
319
344
  export function showMoveHelp(): void {
320
345
  console.log(`
321
346
  ${chalk.bold('cn move - Move a page to a new parent')}
@@ -386,7 +411,7 @@ ${chalk.yellow('Commands:')}
386
411
  cn setup Configure Confluence credentials
387
412
  cn clone Clone a space to a new folder
388
413
  cn pull Pull space to local folder
389
- cn push Push local file to Confluence
414
+ cn folder Manage Confluence folders
390
415
  cn status Check connection and sync status
391
416
  cn tree Display page hierarchy
392
417
  cn open Open page in browser
@@ -395,6 +420,7 @@ ${chalk.yellow('Commands:')}
395
420
  cn spaces List available spaces
396
421
  cn info Show page info and labels
397
422
  cn create Create a new page
423
+ cn update Update an existing page
398
424
  cn delete Delete a page
399
425
  cn comments Show page comments
400
426
  cn labels Manage page labels
@@ -414,10 +440,14 @@ ${chalk.yellow('Environment Variables:')}
414
440
 
415
441
  ${chalk.yellow('Examples:')}
416
442
  cn setup Configure credentials
417
- cn clone DOCS Clone DOCS space to ./DOCS
418
- cn pull Pull changes
419
- cn tree Show page hierarchy
443
+ cn spaces List available spaces
444
+ cn search "my topic" Search across all spaces
445
+ cn search "api" --space ENG Search within a space
446
+ cn info 123456 Show page info
420
447
  cn open "My Page" Open page in browser
448
+ cn create "New Page" --space ENG Create a page
449
+ cn clone DOCS Clone DOCS space locally
450
+ cn pull Pull changes
421
451
 
422
452
  ${chalk.gray('For more information on a command, run: cn <command> --help')}
423
453
  ${chalk.gray('Confluence REST API reference: https://docs.atlassian.com/atlassian-confluence/REST/6.6.0/')}