confluence-cli 1.0.0 → 1.2.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.
@@ -0,0 +1,37 @@
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!
@@ -38,15 +38,24 @@ jobs:
38
38
  needs: [test, security]
39
39
  runs-on: ubuntu-latest
40
40
  if: github.ref == 'refs/heads/main'
41
+ permissions:
42
+ contents: write
43
+ issues: write
44
+ pull-requests: write
41
45
 
42
46
  steps:
43
47
  - uses: actions/checkout@v3
48
+ with:
49
+ fetch-depth: 0
50
+ token: ${{ secrets.GITHUB_TOKEN }}
44
51
 
45
52
  - name: Setup Node.js
46
53
  uses: actions/setup-node@v3
47
54
  with:
48
55
  node-version: '18'
49
56
  registry-url: 'https://registry.npmjs.org'
57
+ env:
58
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
50
59
 
51
60
  - run: npm ci
52
61
  - run: npm test
@@ -55,4 +64,4 @@ jobs:
55
64
  uses: cycjimmy/semantic-release-action@v3
56
65
  env:
57
66
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
58
- NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
67
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/CHANGELOG.md CHANGED
@@ -1,3 +1,53 @@
1
+ # [1.2.0](https://github.com/pchuri/confluence-cli/compare/v1.1.0...v1.2.0) (2025-06-27)
2
+
3
+
4
+ ### Features
5
+
6
+ * implement page creation and update capabilities ([#2](https://github.com/pchuri/confluence-cli/issues/2)) ([b814ddf](https://github.com/pchuri/confluence-cli/commit/b814ddfd056aeac83cc7eb5d8d6db47ba9c70cdf))
7
+
8
+ # [1.2.0](https://github.com/pchuri/confluence-cli/compare/v1.1.0...v1.2.0) (2025-06-27)
9
+
10
+
11
+ ### Features
12
+
13
+ * **page management**: add page creation and update capabilities ([NEW])
14
+ - `confluence create` - Create new pages with support for Markdown, HTML, and Storage formats
15
+ - `confluence update` - Update existing page content and titles
16
+ - `confluence edit` - Export page content for editing workflow
17
+ - Support for reading content from files or inline
18
+ - Markdown to Confluence Storage format conversion
19
+ * **content formats**: support multiple input formats
20
+ - Markdown format with automatic conversion
21
+ - HTML format with Storage format wrapping
22
+ - Native Confluence Storage format
23
+ * **examples**: add sample files and demo scripts for new features
24
+
25
+ ### Breaking Changes
26
+
27
+ * None - all new features are additive
28
+
29
+ # [1.1.0](https://github.com/pchuri/confluence-cli/compare/v1.0.0...v1.1.0) (2025-06-26)
30
+
31
+
32
+ ### Features
33
+
34
+ * add analytics tracking to spaces command ([265e8f4](https://github.com/pchuri/confluence-cli/commit/265e8f42b5ba86fb50398e8b1fcfd1d85fcc54d9))
35
+ * add community feedback and analytics infrastructure ([a7ff6e8](https://github.com/pchuri/confluence-cli/commit/a7ff6e87cdc92d98f3d927ee98fac9e33aedbaae))
36
+
37
+ # 1.0.0 (2025-06-26)
38
+
39
+
40
+ ### Bug Fixes
41
+
42
+ * add explicit permissions for GitHub Actions ([fa36b29](https://github.com/pchuri/confluence-cli/commit/fa36b2974b1261c144a415ced324383b35a938fb))
43
+ * add NODE_AUTH_TOKEN for npm authentication ([2031314](https://github.com/pchuri/confluence-cli/commit/2031314ad01fc1d9b4f9557a3d1321a046cad8f3))
44
+ * resolve eslint errors and npm publish warnings ([b93285e](https://github.com/pchuri/confluence-cli/commit/b93285ee098d96c8b750dbf2be5a93f28f44706c))
45
+
46
+
47
+ ### Features
48
+
49
+ * initial release of confluence-cli ([ec04e06](https://github.com/pchuri/confluence-cli/commit/ec04e06bb0c785dcff84dabcafeeb60bf9e1658f))
50
+
1
51
  # Confluence CLI Changelog
2
52
 
3
53
  All notable changes to this project will be documented in this file.
package/README.md CHANGED
@@ -8,6 +8,9 @@ A powerful command-line interface for Atlassian Confluence that allows you to re
8
8
  - 🔍 **Search** - Find pages using Confluence's powerful search
9
9
  - ℹ️ **Page info** - Get detailed information about pages
10
10
  - 🏠 **List spaces** - View all available Confluence spaces
11
+ - ✏️ **Create pages** - Create new pages with support for Markdown, HTML, or Storage format
12
+ - 📝 **Update pages** - Update existing page content and titles
13
+ - 🛠️ **Edit workflow** - Export page content for editing and re-import
11
14
  - 🔧 **Easy setup** - Simple configuration with environment variables or interactive setup
12
15
 
13
16
  ## Installation
@@ -38,6 +41,16 @@ npx confluence-cli
38
41
  confluence search "my search term"
39
42
  ```
40
43
 
44
+ 4. **Create a new page:**
45
+ ```bash
46
+ confluence create "My New Page" SPACEKEY --content "Hello World!"
47
+ ```
48
+
49
+ 5. **Update a page:**
50
+ ```bash
51
+ confluence update 123456789 --content "Updated content"
52
+ ```
53
+
41
54
  ## Configuration
42
55
 
43
56
  ### Option 1: Interactive Setup
@@ -65,6 +78,79 @@ export CONFLUENCE_API_TOKEN="your-api-token"
65
78
  # Read by page ID
66
79
  confluence read 123456789
67
80
 
81
+ # Read in HTML format
82
+ confluence read 123456789 --format html
83
+
84
+ # Read by URL
85
+ confluence read "https://your-domain.atlassian.net/wiki/spaces/SPACE/pages/123456789/Page+Title"
86
+ ```
87
+
88
+ ### Create a New Page
89
+ ```bash
90
+ # Create with inline content
91
+ confluence create "My New Page" SPACEKEY --content "This is my page content"
92
+
93
+ # Create from a file
94
+ confluence create "Documentation" SPACEKEY --file ./content.md --format markdown
95
+
96
+ # Create with HTML content
97
+ confluence create "Rich Content" SPACEKEY --file ./content.html --format html
98
+
99
+ # Create with Storage format (Confluence native)
100
+ confluence create "Advanced Page" SPACEKEY --file ./content.xml --format storage
101
+ ```
102
+
103
+ ### Create a Child Page
104
+ ```bash
105
+ # Find parent page first
106
+ confluence find "Project Documentation" --space MYTEAM
107
+
108
+ # Create child page with inline content
109
+ confluence create-child "Meeting Notes" 123456789 --content "This is a child page"
110
+
111
+ # Create child page from file
112
+ confluence create-child "Technical Specifications" 123456789 --file ./content.md --format markdown
113
+
114
+ # Create child page with HTML content
115
+ confluence create-child "Report Summary" 123456789 --file ./content.html --format html
116
+ ```
117
+
118
+ ### Find Pages
119
+ ```bash
120
+ # Find page by title
121
+ confluence find "Project Documentation"
122
+
123
+ # Find page by title in specific space
124
+ confluence find "Project Documentation" --space MYTEAM
125
+ ```
126
+
127
+ ### Update an Existing Page
128
+ ```bash
129
+ # Update content only
130
+ confluence update 123456789 --content "Updated content"
131
+
132
+ # Update content from file
133
+ confluence update 123456789 --file ./updated-content.md --format markdown
134
+
135
+ # Update both title and content
136
+ confluence update 123456789 --title "New Title" --content "New content"
137
+
138
+ # Update from HTML file
139
+ confluence update 123456789 --file ./content.html --format html
140
+ ```
141
+
142
+ ### Edit Workflow
143
+ ```bash
144
+ # Export page content for editing
145
+ confluence edit 123456789 --output ./page-content.xml
146
+
147
+ # Edit the file with your preferred editor
148
+ vim ./page-content.xml
149
+
150
+ # Update the page with your changes
151
+ confluence update 123456789 --file ./page-content.xml --format storage
152
+ ```
153
+
68
154
  # Read with HTML format
69
155
  confluence read 123456789 --format html
70
156
 
@@ -91,6 +177,11 @@ confluence search "search term" --limit 5
91
177
  confluence spaces
92
178
  ```
93
179
 
180
+ ### View Usage Statistics
181
+ ```bash
182
+ confluence stats
183
+ ```
184
+
94
185
  ## Commands
95
186
 
96
187
  | Command | Description | Options |
@@ -100,6 +191,7 @@ confluence spaces
100
191
  | `info <pageId>` | Get page information | - |
101
192
  | `search <query>` | Search for pages | `--limit <number>` |
102
193
  | `spaces` | List all spaces | - |
194
+ | `stats` | View your usage statistics | - |
103
195
 
104
196
  ## Examples
105
197
 
@@ -121,6 +213,9 @@ confluence search "API documentation" --limit 3
121
213
 
122
214
  # List all spaces
123
215
  confluence spaces
216
+
217
+ # View usage statistics
218
+ confluence stats
124
219
  ```
125
220
 
126
221
  ## Development
@@ -157,7 +252,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
157
252
 
158
253
  ## Roadmap
159
254
 
160
- - [ ] Create and update pages
255
+ - [x] **Create and update pages** ✅
161
256
  - [ ] Page templates
162
257
  - [ ] Bulk operations
163
258
  - [ ] Export pages to different formats
@@ -165,13 +260,36 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
165
260
  - [ ] Page attachments management
166
261
  - [ ] Comments and reviews
167
262
 
168
- ## Support
263
+ ## Support & Feedback
264
+
265
+ ### 💬 We'd love to hear from you!
169
266
 
170
- If you encounter any issues or have questions:
267
+ Your feedback helps make confluence-cli better for everyone. Here's how you can share your thoughts:
171
268
 
269
+ #### 🐛 Found a bug?
172
270
  1. Check the [Issues](https://github.com/pchuri/confluence-cli/issues) page
173
- 2. Create a new issue if your problem isn't already reported
174
- 3. Provide detailed information about your environment and the issue
271
+ 2. Create a new [bug report](https://github.com/pchuri/confluence-cli/issues/new?template=bug_report.md)
272
+
273
+ #### 💡 Have a feature idea?
274
+ 1. Create a [feature request](https://github.com/pchuri/confluence-cli/issues/new?template=feature_request.md)
275
+ 2. Join our [Discussions](https://github.com/pchuri/confluence-cli/discussions) to chat with the community
276
+
277
+ #### 📝 General feedback?
278
+ - Share your experience with a [feedback issue](https://github.com/pchuri/confluence-cli/issues/new?template=feedback.md)
279
+ - Rate us on [NPM](https://www.npmjs.com/package/confluence-cli)
280
+ - Star the repo if you find it useful! ⭐
281
+
282
+ #### 🤝 Want to contribute?
283
+ Check out our [Contributing Guide](CONTRIBUTING.md) - all contributions are welcome!
284
+
285
+ ### 📈 Usage Analytics
286
+
287
+ To help us understand how confluence-cli is being used and improve it, we collect anonymous usage statistics. This includes:
288
+ - Command usage frequency (no personal data)
289
+ - Error patterns (to fix bugs faster)
290
+ - Feature adoption metrics
291
+
292
+ You can opt-out anytime by setting: `export CONFLUENCE_CLI_ANALYTICS=false`
175
293
 
176
294
  ---
177
295
 
package/bin/confluence.js CHANGED
@@ -4,6 +4,7 @@ const { program } = require('commander');
4
4
  const chalk = require('chalk');
5
5
  const ConfluenceClient = require('../lib/confluence-client');
6
6
  const { getConfig, initConfig } = require('../lib/config');
7
+ const Analytics = require('../lib/analytics');
7
8
 
8
9
  program
9
10
  .name('confluence')
@@ -24,12 +25,15 @@ program
24
25
  .description('Read a Confluence page by ID or URL')
25
26
  .option('-f, --format <format>', 'Output format (html, text)', 'text')
26
27
  .action(async (pageId, options) => {
28
+ const analytics = new Analytics();
27
29
  try {
28
30
  const config = getConfig();
29
31
  const client = new ConfluenceClient(config);
30
32
  const content = await client.readPage(pageId, options.format);
31
33
  console.log(content);
34
+ analytics.track('read', true);
32
35
  } catch (error) {
36
+ analytics.track('read', false);
33
37
  console.error(chalk.red('Error:'), error.message);
34
38
  process.exit(1);
35
39
  }
@@ -40,6 +44,7 @@ program
40
44
  .command('info <pageId>')
41
45
  .description('Get information about a Confluence page')
42
46
  .action(async (pageId) => {
47
+ const analytics = new Analytics();
43
48
  try {
44
49
  const config = getConfig();
45
50
  const client = new ConfluenceClient(config);
@@ -52,7 +57,9 @@ program
52
57
  if (info.space) {
53
58
  console.log(`Space: ${chalk.green(info.space.name)} (${info.space.key})`);
54
59
  }
60
+ analytics.track('info', true);
55
61
  } catch (error) {
62
+ analytics.track('info', false);
56
63
  console.error(chalk.red('Error:'), error.message);
57
64
  process.exit(1);
58
65
  }
@@ -64,6 +71,7 @@ program
64
71
  .description('Search for Confluence pages')
65
72
  .option('-l, --limit <limit>', 'Limit number of results', '10')
66
73
  .action(async (query, options) => {
74
+ const analytics = new Analytics();
67
75
  try {
68
76
  const config = getConfig();
69
77
  const client = new ConfluenceClient(config);
@@ -71,6 +79,7 @@ program
71
79
 
72
80
  if (results.length === 0) {
73
81
  console.log(chalk.yellow('No results found.'));
82
+ analytics.track('search', true);
74
83
  return;
75
84
  }
76
85
 
@@ -81,7 +90,9 @@ program
81
90
  console.log(` ${chalk.gray(result.excerpt)}`);
82
91
  }
83
92
  });
93
+ analytics.track('search', true);
84
94
  } catch (error) {
95
+ analytics.track('search', false);
85
96
  console.error(chalk.red('Error:'), error.message);
86
97
  process.exit(1);
87
98
  }
@@ -92,6 +103,7 @@ program
92
103
  .command('spaces')
93
104
  .description('List all Confluence spaces')
94
105
  .action(async () => {
106
+ const analytics = new Analytics();
95
107
  try {
96
108
  const config = getConfig();
97
109
  const client = new ConfluenceClient(config);
@@ -101,7 +113,221 @@ program
101
113
  spaces.forEach(space => {
102
114
  console.log(`${chalk.green(space.key)} - ${space.name}`);
103
115
  });
116
+ analytics.track('spaces', true);
104
117
  } catch (error) {
118
+ analytics.track('spaces', false);
119
+ console.error(chalk.red('Error:'), error.message);
120
+ process.exit(1);
121
+ }
122
+ });
123
+
124
+ // Stats command
125
+ program
126
+ .command('stats')
127
+ .description('Show usage statistics')
128
+ .action(async () => {
129
+ try {
130
+ const analytics = new Analytics();
131
+ analytics.showStats();
132
+ } catch (error) {
133
+ console.error(chalk.red('Error:'), error.message);
134
+ process.exit(1);
135
+ }
136
+ });
137
+
138
+ // Create command
139
+ program
140
+ .command('create <title> <spaceKey>')
141
+ .description('Create a new Confluence page')
142
+ .option('-f, --file <file>', 'Read content from file')
143
+ .option('-c, --content <content>', 'Page content as string')
144
+ .option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
145
+ .action(async (title, spaceKey, options) => {
146
+ const analytics = new Analytics();
147
+ try {
148
+ const config = getConfig();
149
+ const client = new ConfluenceClient(config);
150
+
151
+ let content = '';
152
+
153
+ if (options.file) {
154
+ const fs = require('fs');
155
+ if (!fs.existsSync(options.file)) {
156
+ throw new Error(`File not found: ${options.file}`);
157
+ }
158
+ content = fs.readFileSync(options.file, 'utf8');
159
+ } else if (options.content) {
160
+ content = options.content;
161
+ } else {
162
+ throw new Error('Either --file or --content option is required');
163
+ }
164
+
165
+ const result = await client.createPage(title, spaceKey, content, options.format);
166
+
167
+ console.log(chalk.green('✅ Page created successfully!'));
168
+ console.log(`Title: ${chalk.blue(result.title)}`);
169
+ console.log(`ID: ${chalk.blue(result.id)}`);
170
+ console.log(`Space: ${chalk.blue(result.space.name)} (${result.space.key})`);
171
+ console.log(`URL: ${chalk.gray(`https://${config.domain}/wiki${result._links.webui}`)}`);
172
+
173
+ analytics.track('create', true);
174
+ } catch (error) {
175
+ analytics.track('create', false);
176
+ console.error(chalk.red('Error:'), error.message);
177
+ process.exit(1);
178
+ }
179
+ });
180
+
181
+ // Create child page command
182
+ program
183
+ .command('create-child <title> <parentId>')
184
+ .description('Create a new Confluence page as a child of another page')
185
+ .option('-f, --file <file>', 'Read content from file')
186
+ .option('-c, --content <content>', 'Page content as string')
187
+ .option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
188
+ .action(async (title, parentId, options) => {
189
+ const analytics = new Analytics();
190
+ try {
191
+ const config = getConfig();
192
+ const client = new ConfluenceClient(config);
193
+
194
+ // Get parent page info to get space key
195
+ const parentInfo = await client.getPageInfo(parentId);
196
+ const spaceKey = parentInfo.space.key;
197
+
198
+ let content = '';
199
+
200
+ if (options.file) {
201
+ const fs = require('fs');
202
+ if (!fs.existsSync(options.file)) {
203
+ throw new Error(`File not found: ${options.file}`);
204
+ }
205
+ content = fs.readFileSync(options.file, 'utf8');
206
+ } else if (options.content) {
207
+ content = options.content;
208
+ } else {
209
+ throw new Error('Either --file or --content option is required');
210
+ }
211
+
212
+ const result = await client.createChildPage(title, spaceKey, parentId, content, options.format);
213
+
214
+ console.log(chalk.green('✅ Child page created successfully!'));
215
+ console.log(`Title: ${chalk.blue(result.title)}`);
216
+ console.log(`ID: ${chalk.blue(result.id)}`);
217
+ console.log(`Parent: ${chalk.blue(parentInfo.title)} (${parentId})`);
218
+ console.log(`Space: ${chalk.blue(result.space.name)} (${result.space.key})`);
219
+ console.log(`URL: ${chalk.gray(`https://${config.domain}/wiki${result._links.webui}`)}`);
220
+
221
+ analytics.track('create_child', true);
222
+ } catch (error) {
223
+ analytics.track('create_child', false);
224
+ console.error(chalk.red('Error:'), error.message);
225
+ process.exit(1);
226
+ }
227
+ });
228
+
229
+ // Update command
230
+ program
231
+ .command('update <pageId>')
232
+ .description('Update an existing Confluence page')
233
+ .option('-t, --title <title>', 'New page title (optional)')
234
+ .option('-f, --file <file>', 'Read content from file')
235
+ .option('-c, --content <content>', 'Page content as string')
236
+ .option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
237
+ .action(async (pageId, options) => {
238
+ const analytics = new Analytics();
239
+ try {
240
+ const config = getConfig();
241
+ const client = new ConfluenceClient(config);
242
+
243
+ let content = '';
244
+
245
+ if (options.file) {
246
+ const fs = require('fs');
247
+ if (!fs.existsSync(options.file)) {
248
+ throw new Error(`File not found: ${options.file}`);
249
+ }
250
+ content = fs.readFileSync(options.file, 'utf8');
251
+ } else if (options.content) {
252
+ content = options.content;
253
+ } else {
254
+ throw new Error('Either --file or --content option is required');
255
+ }
256
+
257
+ const result = await client.updatePage(pageId, options.title, content, options.format);
258
+
259
+ console.log(chalk.green('✅ Page updated successfully!'));
260
+ console.log(`Title: ${chalk.blue(result.title)}`);
261
+ console.log(`ID: ${chalk.blue(result.id)}`);
262
+ console.log(`Version: ${chalk.blue(result.version.number)}`);
263
+ console.log(`URL: ${chalk.gray(`https://${config.domain}/wiki${result._links.webui}`)}`);
264
+
265
+ analytics.track('update', true);
266
+ } catch (error) {
267
+ analytics.track('update', false);
268
+ console.error(chalk.red('Error:'), error.message);
269
+ process.exit(1);
270
+ }
271
+ });
272
+
273
+ // Edit command - opens page content for editing
274
+ program
275
+ .command('edit <pageId>')
276
+ .description('Get page content for editing')
277
+ .option('-o, --output <file>', 'Save content to file')
278
+ .action(async (pageId, options) => {
279
+ const analytics = new Analytics();
280
+ try {
281
+ const config = getConfig();
282
+ const client = new ConfluenceClient(config);
283
+ const pageData = await client.getPageForEdit(pageId);
284
+
285
+ console.log(chalk.blue('Page Information:'));
286
+ console.log(`Title: ${chalk.green(pageData.title)}`);
287
+ console.log(`ID: ${chalk.green(pageData.id)}`);
288
+ console.log(`Version: ${chalk.green(pageData.version)}`);
289
+ console.log(`Space: ${chalk.green(pageData.space.name)} (${pageData.space.key})`);
290
+ console.log('');
291
+
292
+ if (options.output) {
293
+ const fs = require('fs');
294
+ fs.writeFileSync(options.output, pageData.content);
295
+ console.log(chalk.green(`✅ Content saved to: ${options.output}`));
296
+ console.log(chalk.yellow('💡 Edit the file and use "confluence update" to save changes'));
297
+ } else {
298
+ console.log(chalk.blue('Page Content:'));
299
+ console.log(pageData.content);
300
+ }
301
+
302
+ analytics.track('edit', true);
303
+ } catch (error) {
304
+ analytics.track('edit', false);
305
+ console.error(chalk.red('Error:'), error.message);
306
+ process.exit(1);
307
+ }
308
+ });
309
+
310
+ // Find page by title command
311
+ program
312
+ .command('find <title>')
313
+ .description('Find a page by title')
314
+ .option('-s, --space <spaceKey>', 'Limit search to specific space')
315
+ .action(async (title, options) => {
316
+ const analytics = new Analytics();
317
+ try {
318
+ const config = getConfig();
319
+ const client = new ConfluenceClient(config);
320
+ const pageInfo = await client.findPageByTitle(title, options.space);
321
+
322
+ console.log(chalk.blue('Page found:'));
323
+ console.log(`Title: ${chalk.green(pageInfo.title)}`);
324
+ console.log(`ID: ${chalk.green(pageInfo.id)}`);
325
+ console.log(`Space: ${chalk.green(pageInfo.space.name)} (${pageInfo.space.key})`);
326
+ console.log(`URL: ${chalk.gray(`https://${config.domain}/wiki${pageInfo.url}`)}`);
327
+
328
+ analytics.track('find', true);
329
+ } catch (error) {
330
+ analytics.track('find', false);
105
331
  console.error(chalk.red('Error:'), error.message);
106
332
  process.exit(1);
107
333
  }
@@ -0,0 +1,63 @@
1
+ # GitHub Repository Topics
2
+
3
+ When setting up your GitHub repository, add these topics for better discoverability:
4
+
5
+ ## Suggested Topics:
6
+ - `confluence`
7
+ - `atlassian`
8
+ - `cli`
9
+ - `command-line`
10
+ - `wiki`
11
+ - `documentation`
12
+ - `nodejs`
13
+ - `javascript`
14
+ - `api-client`
15
+ - `automation`
16
+ - `developer-tools`
17
+ - `productivity`
18
+
19
+ ## How to add topics:
20
+ 1. Go to https://github.com/pchuri/confluence-cli
21
+ 2. Click the ⚙️ gear icon next to "About"
22
+ 3. Add the topics above in the "Topics" field
23
+ 4. Add a description: "A powerful command-line interface for Atlassian Confluence"
24
+ 5. Add website: https://www.npmjs.com/package/confluence-cli
25
+
26
+ ## Social Media Promotion Ideas:
27
+
28
+ ### Twitter/X Posts:
29
+ ```
30
+ 🚀 Just released confluence-cli v1.0!
31
+
32
+ A powerful CLI tool for @Atlassian Confluence:
33
+ ✅ Read pages from terminal
34
+ ✅ Search documentation
35
+ ✅ List spaces
36
+ ✅ Works with environment variables
37
+
38
+ Perfect for automation & DevOps workflows!
39
+
40
+ #CLI #Confluence #DevTools #OpenSource
41
+ https://github.com/pchuri/confluence-cli
42
+ ```
43
+
44
+ ### LinkedIn Post:
45
+ ```
46
+ Excited to share my latest open-source project: confluence-cli! 🎉
47
+
48
+ As someone who works with Confluence daily, I built this CLI tool to streamline documentation workflows. Now you can:
49
+
50
+ • Read Confluence pages from your terminal
51
+ • Search across your wiki content
52
+ • Integrate with CI/CD pipelines
53
+ • Automate documentation tasks
54
+
55
+ Available on NPM and fully open-source. Would love your feedback!
56
+
57
+ #OpenSource #CLI #Confluence #DevTools #Automation
58
+ ```
59
+
60
+ ### Dev.to Article Ideas:
61
+ 1. "Building a CLI Tool for Confluence: From Bash Functions to NPM Package"
62
+ 2. "How to Automate Your Documentation Workflow with Confluence CLI"
63
+ 3. "Open Source Journey: Lessons Learned Building confluence-cli"
@@ -0,0 +1,65 @@
1
+ #!/bin/bash
2
+
3
+ # 실제 Project Documentation 페이지 하위에 테스트 페이지 생성 예제
4
+ # 이 스크립트는 일반적인 Confluence 설정에서 작동합니다.
5
+
6
+ echo "🔍 Project Documentation 페이지 하위에 테스트 페이지 생성"
7
+ echo "=============================================================="
8
+
9
+ # 1단계: 부모 페이지 찾기
10
+ echo ""
11
+ echo "1️⃣ 부모 페이지 찾기..."
12
+ echo "실행: confluence find \"Project Documentation\" --space MYTEAM"
13
+ echo ""
14
+
15
+ # 실제 실행할 때는 아래 주석을 해제하세요
16
+ # confluence find "Project Documentation" --space MYTEAM
17
+
18
+ echo "📝 위 명령어 결과에서 페이지 ID를 확인하세요 (예: 123456789)"
19
+ echo ""
20
+
21
+ # 2단계: 페이지 정보 확인
22
+ echo "2️⃣ 페이지 정보 확인..."
23
+ echo "실행: confluence info [페이지ID]"
24
+ echo "예시: confluence info 123456789"
25
+ echo ""
26
+
27
+ # 3단계: 페이지 내용 읽기 (선택사항)
28
+ echo "3️⃣ 페이지 내용 확인 (선택사항)..."
29
+ echo "실행: confluence read [페이지ID] | head -20"
30
+ echo "예시: confluence read 123456789 | head -20"
31
+ echo ""
32
+
33
+ # 4단계: 테스트 페이지 생성
34
+ echo "4️⃣ 하위 테스트 페이지 생성..."
35
+ echo ""
36
+
37
+ # 간단한 텍스트 콘텐츠로 테스트 페이지 생성
38
+ echo "📄 방법 1: 간단한 텍스트 콘텐츠로 생성"
39
+ echo 'confluence create-child "Test Page - $(date +%Y%m%d)" [부모페이지ID] --content "이것은 CLI로 생성된 테스트 페이지입니다. 생성 시간: $(date)"'
40
+ echo ""
41
+
42
+ # 마크다운 파일에서 테스트 페이지 생성
43
+ echo "📄 방법 2: 마크다운 파일에서 생성"
44
+ echo "confluence create-child \"Test Documentation - $(date +%Y%m%d)\" [부모페이지ID] --file ./sample-page.md --format markdown"
45
+ echo ""
46
+
47
+ # HTML 콘텐츠로 생성
48
+ echo "📄 방법 3: HTML 콘텐츠로 생성"
49
+ echo 'confluence create-child "Test HTML Page" [부모페이지ID] --content "<h1>테스트 페이지</h1><p>이것은 <strong>HTML</strong>로 작성된 테스트 페이지입니다.</p>" --format html'
50
+ echo ""
51
+
52
+ echo "💡 실제 사용 예제:"
53
+ echo "=============================="
54
+ echo "# 1. 부모 페이지 ID 찾기"
55
+ echo 'PARENT_ID=$(confluence find "Project Documentation" --space MYTEAM | grep "ID:" | cut -d" " -f2)'
56
+ echo ""
57
+ echo "# 2. 테스트 페이지 생성"
58
+ echo 'confluence create-child "테스트 페이지 - $(date +%Y%m%d_%H%M)" $PARENT_ID --content "CLI 테스트용 페이지입니다."'
59
+ echo ""
60
+
61
+ echo "⚠️ 주의사항:"
62
+ echo "- confluence CLI가 올바르게 설정되어 있어야 합니다 (confluence init)"
63
+ echo "- 해당 Confluence 인스턴스에 대한 적절한 권한이 있어야 합니다"
64
+ echo "- 페이지 생성 권한이 있는지 확인하세요"
65
+ echo "- 테스트 후에는 불필요한 페이지를 정리하세요"
@@ -0,0 +1,68 @@
1
+ #!/bin/bash
2
+
3
+ # Confluence CLI Demo Script
4
+ # This script demonstrates the new page creation and update features
5
+
6
+ echo "🚀 Confluence CLI - Page Creation Demo"
7
+ echo "======================================"
8
+
9
+ # Note: Replace with your actual space key and ensure you have confluence-cli configured
10
+ SPACE_KEY="MYTEAM" # Change this to your space key
11
+
12
+ echo ""
13
+ echo "1. Creating a page from Markdown file..."
14
+ echo "confluence create \"Sample Page from CLI\" $SPACE_KEY --file ./sample-page.md --format markdown"
15
+
16
+ echo ""
17
+ echo "2. Creating a page with inline content..."
18
+ echo "confluence create \"Quick Note\" $SPACE_KEY --content \"This is a quick note created from the CLI\" --format storage"
19
+
20
+ echo ""
21
+ echo "3. Updating a page (replace 123456789 with actual page ID)..."
22
+ echo "confluence update 123456789 --content \"This page has been updated via CLI\""
23
+
24
+ echo ""
25
+ echo "4. Getting page content for editing..."
26
+ echo "confluence edit 123456789 --output ./page-backup.xml"
27
+
28
+ echo ""
29
+ echo "5. Searching for pages..."
30
+ echo "confluence search \"CLI\""
31
+
32
+ echo ""
33
+ echo "6. Listing all spaces..."
34
+ echo "confluence spaces"
35
+
36
+ echo ""
37
+ echo "7. Finding a page by title..."
38
+ echo "confluence find \"Project Documentation\" --space MYTEAM"
39
+
40
+ echo ""
41
+ echo "8. Creating a child page under a parent page..."
42
+ echo "confluence create-child \"Test Page\" 123456789 --content \"This is a test page created as a child\""
43
+
44
+ echo ""
45
+ echo "9. Creating a child page from Markdown file..."
46
+ echo "confluence create-child \"Test Documentation\" 123456789 --file ./sample-page.md --format markdown"
47
+
48
+ echo ""
49
+ echo "💡 Tips:"
50
+ echo "- Use --format markdown to create pages from Markdown files"
51
+ echo "- Use --format html for HTML content"
52
+ echo "- Use --format storage for Confluence native format"
53
+ echo "- Always backup important pages before updating"
54
+ echo "- Use 'confluence find' to get page IDs by title"
55
+ echo "- Child pages inherit permissions from their parent"
56
+
57
+ echo ""
58
+ echo "📝 Edit workflow:"
59
+ echo "1. confluence edit [pageId] --output page.xml"
60
+ echo "2. Edit the file with your preferred editor"
61
+ echo "3. confluence update [pageId] --file page.xml --format storage"
62
+
63
+ echo ""
64
+ echo "🔍 Finding and creating child pages workflow:"
65
+ echo "1. confluence find \"Project Documentation\" --space MYTEAM"
66
+ echo "2. Note the page ID from the result"
67
+ echo "3. confluence create-child \"Meeting Notes\" [parentId] --content \"Child content\""
68
+ echo "4. Or use: confluence create-child \"Technical Docs\" [parentId] --file ./content.md --format markdown"
@@ -0,0 +1,30 @@
1
+ # Welcome to Confluence CLI
2
+
3
+ This is a sample page created using the **Confluence CLI** tool.
4
+
5
+ ## Features
6
+
7
+ - Easy page creation from Markdown files
8
+ - Support for multiple content formats
9
+ - Command-line interface for automation
10
+
11
+ ## Code Example
12
+
13
+ ```javascript
14
+ const client = new ConfluenceClient(config);
15
+ await client.createPage('My Page', 'SPACE', content, 'markdown');
16
+ ```
17
+
18
+ ## Lists
19
+
20
+ 1. First item
21
+ 2. Second item
22
+ 3. Third item
23
+
24
+ - Bullet point one
25
+ - Bullet point two
26
+ - Bullet point three
27
+
28
+ ---
29
+
30
+ *Created with ❤️ using confluence-cli*
@@ -0,0 +1,87 @@
1
+ const os = require('os');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+
5
+ /**
6
+ * Simple anonymous usage analytics
7
+ * Helps understand which features are most used
8
+ */
9
+ class Analytics {
10
+ constructor() {
11
+ this.enabled = process.env.CONFLUENCE_CLI_ANALYTICS !== 'false';
12
+ this.configDir = path.join(os.homedir(), '.confluence-cli');
13
+ this.statsFile = path.join(this.configDir, 'stats.json');
14
+ }
15
+
16
+ /**
17
+ * Track command usage (anonymous)
18
+ */
19
+ track(command, success = true) {
20
+ if (!this.enabled) return;
21
+
22
+ try {
23
+ let stats = {};
24
+
25
+ // Read existing stats
26
+ if (fs.existsSync(this.statsFile)) {
27
+ stats = JSON.parse(fs.readFileSync(this.statsFile, 'utf8'));
28
+ }
29
+
30
+ // Initialize stats structure
31
+ if (!stats.commands) stats.commands = {};
32
+ if (!stats.firstUsed) stats.firstUsed = new Date().toISOString();
33
+ stats.lastUsed = new Date().toISOString();
34
+
35
+ // Track command usage
36
+ const commandKey = `${command}_${success ? 'success' : 'error'}`;
37
+ stats.commands[commandKey] = (stats.commands[commandKey] || 0) + 1;
38
+
39
+ // Create directory if it doesn't exist
40
+ if (!fs.existsSync(this.configDir)) {
41
+ fs.mkdirSync(this.configDir, { recursive: true });
42
+ }
43
+
44
+ // Save stats
45
+ fs.writeFileSync(this.statsFile, JSON.stringify(stats, null, 2));
46
+ } catch (error) {
47
+ // Silently fail - analytics should never break the main functionality
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Get usage statistics
53
+ */
54
+ getStats() {
55
+ if (!fs.existsSync(this.statsFile)) {
56
+ return null;
57
+ }
58
+
59
+ try {
60
+ return JSON.parse(fs.readFileSync(this.statsFile, 'utf8'));
61
+ } catch (error) {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Show usage stats to user
68
+ */
69
+ showStats() {
70
+ const stats = this.getStats();
71
+ if (!stats) {
72
+ console.log('No usage statistics available.');
73
+ return;
74
+ }
75
+
76
+ console.log('📊 Usage Statistics:');
77
+ console.log(`First used: ${new Date(stats.firstUsed).toLocaleDateString()}`);
78
+ console.log(`Last used: ${new Date(stats.lastUsed).toLocaleDateString()}`);
79
+ console.log('\nCommand usage:');
80
+
81
+ Object.entries(stats.commands).forEach(([command, count]) => {
82
+ console.log(` ${command}: ${count} times`);
83
+ });
84
+ }
85
+ }
86
+
87
+ module.exports = Analytics;
@@ -1,11 +1,13 @@
1
1
  const axios = require('axios');
2
2
  const { convert } = require('html-to-text');
3
+ const MarkdownIt = require('markdown-it');
3
4
 
4
5
  class ConfluenceClient {
5
6
  constructor(config) {
6
7
  this.baseURL = `https://${config.domain}/rest/api`;
7
8
  this.token = config.token;
8
9
  this.domain = config.domain;
10
+ this.markdown = new MarkdownIt();
9
11
 
10
12
  this.client = axios.create({
11
13
  baseURL: this.baseURL,
@@ -132,6 +134,187 @@ class ConfluenceClient {
132
134
  type: space.type
133
135
  }));
134
136
  }
137
+
138
+ /**
139
+ * Convert markdown to Confluence storage format
140
+ */
141
+ markdownToStorage(markdown) {
142
+ // Convert markdown to HTML first
143
+ const html = this.markdown.render(markdown);
144
+
145
+ // Basic conversion to Confluence storage format
146
+ // This is a simplified version - Confluence has a specific storage format
147
+ return `<ac:structured-macro ac:name="html">
148
+ <ac:parameter ac:name="atlassian-macro-output-type">BLOCK</ac:parameter>
149
+ <ac:plain-text-body><![CDATA[${html}]]></ac:plain-text-body>
150
+ </ac:structured-macro>`;
151
+ }
152
+
153
+ /**
154
+ * Create a new Confluence page
155
+ */
156
+ async createPage(title, spaceKey, content, format = 'storage') {
157
+ let storageContent = content;
158
+
159
+ if (format === 'markdown') {
160
+ storageContent = this.markdownToStorage(content);
161
+ } else if (format === 'html') {
162
+ // Wrap HTML in storage format
163
+ storageContent = `<ac:structured-macro ac:name="html">
164
+ <ac:parameter ac:name="atlassian-macro-output-type">BLOCK</ac:parameter>
165
+ <ac:plain-text-body><![CDATA[${content}]]></ac:plain-text-body>
166
+ </ac:structured-macro>`;
167
+ }
168
+
169
+ const pageData = {
170
+ type: 'page',
171
+ title: title,
172
+ space: {
173
+ key: spaceKey
174
+ },
175
+ body: {
176
+ storage: {
177
+ value: storageContent,
178
+ representation: 'storage'
179
+ }
180
+ }
181
+ };
182
+
183
+ const response = await this.client.post('/content', pageData);
184
+ return response.data;
185
+ }
186
+
187
+ /**
188
+ * Create a new Confluence page as a child of another page
189
+ */
190
+ async createChildPage(title, spaceKey, parentId, content, format = 'storage') {
191
+ let storageContent = content;
192
+
193
+ if (format === 'markdown') {
194
+ storageContent = this.markdownToStorage(content);
195
+ } else if (format === 'html') {
196
+ // Wrap HTML in storage format
197
+ storageContent = `<ac:structured-macro ac:name="html">
198
+ <ac:parameter ac:name="atlassian-macro-output-type">BLOCK</ac:parameter>
199
+ <ac:plain-text-body><![CDATA[${content}]]></ac:plain-text-body>
200
+ </ac:structured-macro>`;
201
+ }
202
+
203
+ const pageData = {
204
+ type: 'page',
205
+ title: title,
206
+ space: {
207
+ key: spaceKey
208
+ },
209
+ ancestors: [
210
+ {
211
+ id: parentId
212
+ }
213
+ ],
214
+ body: {
215
+ storage: {
216
+ value: storageContent,
217
+ representation: 'storage'
218
+ }
219
+ }
220
+ };
221
+
222
+ const response = await this.client.post('/content', pageData);
223
+ return response.data;
224
+ }
225
+
226
+ /**
227
+ * Update an existing Confluence page
228
+ */
229
+ async updatePage(pageId, title, content, format = 'storage') {
230
+ // First, get the current page to get the version number
231
+ const currentPage = await this.client.get(`/content/${pageId}`);
232
+ const currentVersion = currentPage.data.version.number;
233
+
234
+ let storageContent = content;
235
+
236
+ if (format === 'markdown') {
237
+ storageContent = this.markdownToStorage(content);
238
+ } else if (format === 'html') {
239
+ // Wrap HTML in storage format
240
+ storageContent = `<ac:structured-macro ac:name="html">
241
+ <ac:parameter ac:name="atlassian-macro-output-type">BLOCK</ac:parameter>
242
+ <ac:plain-text-body><![CDATA[${content}]]></ac:plain-text-body>
243
+ </ac:structured-macro>`;
244
+ }
245
+
246
+ const pageData = {
247
+ id: pageId,
248
+ type: 'page',
249
+ title: title || currentPage.data.title,
250
+ space: currentPage.data.space,
251
+ body: {
252
+ storage: {
253
+ value: storageContent,
254
+ representation: 'storage'
255
+ }
256
+ },
257
+ version: {
258
+ number: currentVersion + 1
259
+ }
260
+ };
261
+
262
+ const response = await this.client.put(`/content/${pageId}`, pageData);
263
+ return response.data;
264
+ }
265
+
266
+ /**
267
+ * Get page content for editing
268
+ */
269
+ async getPageForEdit(pageIdOrUrl) {
270
+ const pageId = this.extractPageId(pageIdOrUrl);
271
+
272
+ const response = await this.client.get(`/content/${pageId}`, {
273
+ params: {
274
+ expand: 'body.storage,version,space'
275
+ }
276
+ });
277
+
278
+ return {
279
+ id: response.data.id,
280
+ title: response.data.title,
281
+ content: response.data.body.storage.value,
282
+ version: response.data.version.number,
283
+ space: response.data.space
284
+ };
285
+ }
286
+
287
+ /**
288
+ * Search for a page by title and space
289
+ */
290
+ async findPageByTitle(title, spaceKey = null) {
291
+ let cql = `title = "${title}"`;
292
+ if (spaceKey) {
293
+ cql += ` AND space = "${spaceKey}"`;
294
+ }
295
+
296
+ const response = await this.client.get('/search', {
297
+ params: {
298
+ cql: cql,
299
+ limit: 1,
300
+ expand: 'content.space'
301
+ }
302
+ });
303
+
304
+ if (response.data.results.length === 0) {
305
+ throw new Error(`Page not found: "${title}"`);
306
+ }
307
+
308
+ const result = response.data.results[0];
309
+ const content = result.content || result;
310
+
311
+ return {
312
+ id: content.id,
313
+ title: content.title,
314
+ space: content.space || { key: spaceKey || 'Unknown', name: 'Unknown' },
315
+ url: content._links?.webui || ''
316
+ };
317
+ }
135
318
  }
136
319
 
137
320
  module.exports = ConfluenceClient;
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "confluence-cli",
3
- "version": "1.0.0",
4
- "description": "A command-line interface for Atlassian Confluence",
3
+ "version": "1.2.0",
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
8
  },
9
9
  "scripts": {
10
10
  "start": "node bin/confluence.js",
@@ -22,24 +22,25 @@
22
22
  "author": "Your Name",
23
23
  "license": "MIT",
24
24
  "dependencies": {
25
- "commander": "^11.1.0",
26
25
  "axios": "^1.6.2",
27
26
  "chalk": "^4.1.2",
27
+ "commander": "^11.1.0",
28
+ "html-to-text": "^9.0.5",
28
29
  "inquirer": "^8.2.6",
29
- "ora": "^5.4.1",
30
- "html-to-text": "^9.0.5"
30
+ "markdown-it": "^14.1.0",
31
+ "ora": "^5.4.1"
31
32
  },
32
33
  "devDependencies": {
34
+ "@types/node": "^20.10.0",
33
35
  "eslint": "^8.55.0",
34
- "jest": "^29.7.0",
35
- "@types/node": "^20.10.0"
36
+ "jest": "^29.7.0"
36
37
  },
37
38
  "engines": {
38
39
  "node": ">=14.0.0"
39
40
  },
40
41
  "repository": {
41
42
  "type": "git",
42
- "url": "https://github.com/pchuri/confluence-cli.git"
43
+ "url": "git+https://github.com/pchuri/confluence-cli.git"
43
44
  },
44
45
  "bugs": {
45
46
  "url": "https://github.com/pchuri/confluence-cli/issues"
@@ -26,4 +26,25 @@ describe('ConfluenceClient', () => {
26
26
  expect(() => client.extractPageId(displayUrl)).toThrow('Display URLs not yet supported');
27
27
  });
28
28
  });
29
+
30
+ describe('markdownToStorage', () => {
31
+ test('should convert markdown to Confluence storage format', () => {
32
+ const markdown = '# Hello World\n\nThis is a **test** page.';
33
+ const result = client.markdownToStorage(markdown);
34
+
35
+ expect(result).toContain('<ac:structured-macro ac:name="html">');
36
+ expect(result).toContain('<h1>Hello World</h1>');
37
+ expect(result).toContain('<strong>test</strong>');
38
+ });
39
+ });
40
+
41
+ describe('page creation and updates', () => {
42
+ test('should have required methods for page management', () => {
43
+ expect(typeof client.createPage).toBe('function');
44
+ expect(typeof client.updatePage).toBe('function');
45
+ expect(typeof client.getPageForEdit).toBe('function');
46
+ expect(typeof client.createChildPage).toBe('function');
47
+ expect(typeof client.findPageByTitle).toBe('function');
48
+ });
49
+ });
29
50
  });