@bestend/confluence-cli 1.16.0 → 2.0.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.
@@ -1,16 +1,42 @@
1
1
  const axios = require('axios');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const FormData = require('form-data');
2
5
  const { convert } = require('html-to-text');
3
6
  const MarkdownIt = require('markdown-it');
4
7
  const { XMLValidator } = require('fast-xml-parser');
5
8
 
9
+ const NAMED_ENTITIES = {
10
+ // uppercase variants
11
+ aring: 'å', auml: 'ä', ouml: 'ö',
12
+ eacute: 'é', egrave: 'è', ecirc: 'ê', euml: 'ë',
13
+ aacute: 'á', agrave: 'à', acirc: 'â', atilde: 'ã',
14
+ oacute: 'ó', ograve: 'ò', ocirc: 'ô', otilde: 'õ',
15
+ uacute: 'ú', ugrave: 'ù', ucirc: 'û', uuml: 'ü',
16
+ iacute: 'í', igrave: 'ì', icirc: 'î', iuml: 'ï',
17
+ ntilde: 'ñ', ccedil: 'ç', szlig: 'ß', yuml: 'ÿ',
18
+ eth: 'ð', thorn: 'þ',
19
+ // uppercase variants
20
+ Aring: 'Å', Auml: 'Ä', Ouml: 'Ö',
21
+ Eacute: 'É', Egrave: 'È', Ecirc: 'Ê', Euml: 'Ë',
22
+ Aacute: 'Á', Agrave: 'À', Acirc: 'Â', Atilde: 'Ã',
23
+ Oacute: 'Ó', Ograve: 'Ò', Ocirc: 'Ô', Otilde: 'Õ',
24
+ Uacute: 'Ú', Ugrave: 'Ù', Ucirc: 'Û', Uuml: 'Ü',
25
+ Iacute: 'Í', Igrave: 'Ì', Icirc: 'Î', Iuml: 'Ï',
26
+ Ntilde: 'Ñ', Ccedil: 'Ç', Szlig: 'SS', Yuml: 'Ÿ',
27
+ Eth: 'Ð', Thorn: 'Þ'
28
+ };
29
+
6
30
  class ConfluenceClient {
7
31
  constructor(config) {
8
32
  this.domain = config.domain;
33
+ const rawProtocol = (config.protocol || 'https').trim().toLowerCase();
34
+ this.protocol = (rawProtocol === 'http' || rawProtocol === 'https') ? rawProtocol : 'https';
9
35
  this.token = config.token;
10
36
  this.email = config.email;
11
37
  this.authType = (config.authType || (this.email ? 'basic' : 'bearer')).toLowerCase();
12
38
  this.apiPath = this.sanitizeApiPath(config.apiPath);
13
- this.baseURL = `https://${this.domain}${this.apiPath}`;
39
+ this.baseURL = `${this.protocol}://${this.domain}${this.apiPath}`;
14
40
  this.markdown = new MarkdownIt();
15
41
  this.setupConfluenceMarkdownExtensions();
16
42
 
@@ -23,6 +49,28 @@ class ConfluenceClient {
23
49
  baseURL: this.baseURL,
24
50
  headers
25
51
  });
52
+
53
+ // Auto-detect context path (e.g., "/wiki" or "") from first API response
54
+ this._contextPath = null;
55
+ this.client.interceptors.response.use((response) => {
56
+ if (this._contextPath === null && response.data?._links?.context !== undefined) {
57
+ this._contextPath = response.data._links.context;
58
+ }
59
+ return response;
60
+ });
61
+ }
62
+
63
+ sanitizeApiPath(rawPath) {
64
+ const fallback = '/rest/api';
65
+ const value = (rawPath || '').trim();
66
+
67
+ if (!value) {
68
+ return fallback;
69
+ }
70
+
71
+ const withoutLeading = value.replace(/^\/+/, '');
72
+ const normalized = `/${withoutLeading}`.replace(/\/+$/, '');
73
+ return normalized || fallback;
26
74
  }
27
75
 
28
76
  sanitizeStorageContent(content, options = {}) {
@@ -53,22 +101,9 @@ class ConfluenceClient {
53
101
  return result;
54
102
  }
55
103
 
56
- sanitizeApiPath(rawPath) {
57
- const fallback = '/rest/api';
58
- const value = (rawPath || '').trim();
59
-
60
- if (!value) {
61
- return fallback;
62
- }
63
-
64
- const withoutLeading = value.replace(/^\/+/, '');
65
- const normalized = `/${withoutLeading}`.replace(/\/+$/, '');
66
- return normalized || fallback;
67
- }
68
-
69
104
  buildBasicAuthHeader() {
70
105
  if (!this.email) {
71
- throw new Error('Basic authentication requires an email address.');
106
+ throw new Error('Basic authentication requires an email address or username.');
72
107
  }
73
108
 
74
109
  const encodedCredentials = Buffer.from(`${this.email}:${this.token}`).toString('base64');
@@ -91,12 +126,11 @@ class ConfluenceClient {
91
126
  return pageIdMatch[1];
92
127
  }
93
128
 
94
- // Handle /spaces/SPACEKEY/pages/PAGEID/Title format
95
- const spacesMatch = pageIdOrUrl.match(/\/spaces\/[^/]+\/pages\/(\d+)/);
96
- if (spacesMatch) {
97
- return spacesMatch[1];
129
+ const prettyMatch = pageIdOrUrl.match(/\/pages\/(\d+)(?:[/?#]|$)/);
130
+ if (prettyMatch) {
131
+ return prettyMatch[1];
98
132
  }
99
-
133
+
100
134
  // Handle display URLs - search by space and title
101
135
  const displayMatch = pageIdOrUrl.match(/\/display\/([^/]+)\/(.+)/);
102
136
  if (displayMatch) {
@@ -248,10 +282,11 @@ class ConfluenceClient {
248
282
  /**
249
283
  * Search for pages
250
284
  */
251
- async search(query, limit = 10) {
285
+ async search(query, limit = 10, rawCql = false) {
286
+ const cql = rawCql ? query : `text ~ "${String(query).replace(/"/g, '\\"')}"`;
252
287
  const response = await this.client.get('/search', {
253
288
  params: {
254
- cql: `text ~ "${query}"`,
289
+ cql,
255
290
  limit: limit
256
291
  }
257
292
  });
@@ -373,7 +408,7 @@ class ConfluenceClient {
373
408
  const webui = page._links?.webui || '';
374
409
  return {
375
410
  title: page.title,
376
- url: webui ? `https://${this.domain}/wiki${webui}` : ''
411
+ url: webui ? this.buildUrl(webui) : ''
377
412
  };
378
413
  }
379
414
  return null;
@@ -467,7 +502,7 @@ class ConfluenceClient {
467
502
  // Format: - [Page Title](URL)
468
503
  const childPagesList = childPages.map(page => {
469
504
  const webui = page._links?.webui || '';
470
- const url = webui ? `https://${this.domain}/wiki${webui}` : '';
505
+ const url = webui ? this.buildUrl(webui) : '';
471
506
  if (url) {
472
507
  return `- [${page.title}](${url})`;
473
508
  } else {
@@ -838,6 +873,158 @@ class ConfluenceClient {
838
873
  return downloadResponse.data;
839
874
  }
840
875
 
876
+ /**
877
+ * Upload an attachment to a page
878
+ */
879
+ async uploadAttachment(pageIdOrUrl, filePath, options = {}) {
880
+ if (!filePath || typeof filePath !== 'string') {
881
+ throw new Error('File path is required for attachment upload.');
882
+ }
883
+
884
+ const resolvedPath = path.resolve(filePath);
885
+ if (!fs.existsSync(resolvedPath)) {
886
+ throw new Error(`File not found: ${filePath}`);
887
+ }
888
+
889
+ const pageId = await this.extractPageId(pageIdOrUrl);
890
+ const form = new FormData();
891
+ form.append('file', fs.createReadStream(resolvedPath), { filename: path.basename(resolvedPath) });
892
+
893
+ if (options.comment !== undefined && options.comment !== null) {
894
+ form.append('comment', options.comment, { contentType: 'text/plain; charset=utf-8' });
895
+ }
896
+
897
+ if (typeof options.minorEdit === 'boolean') {
898
+ form.append('minorEdit', options.minorEdit ? 'true' : 'false');
899
+ }
900
+
901
+ const method = options.replace ? 'put' : 'post';
902
+ const response = await this.client.request({
903
+ url: `/content/${pageId}/child/attachment`,
904
+ method,
905
+ headers: {
906
+ ...form.getHeaders(),
907
+ 'X-Atlassian-Token': 'nocheck'
908
+ },
909
+ data: form,
910
+ maxBodyLength: Infinity,
911
+ maxContentLength: Infinity
912
+ });
913
+
914
+ const results = Array.isArray(response.data?.results)
915
+ ? response.data.results.map((item) => this.normalizeAttachment(item))
916
+ : [];
917
+
918
+ return {
919
+ results,
920
+ raw: response.data
921
+ };
922
+ }
923
+
924
+ /**
925
+ * Delete an attachment by ID
926
+ */
927
+ async deleteAttachment(pageIdOrUrl, attachmentId) {
928
+ if (!attachmentId) {
929
+ throw new Error('Attachment ID is required.');
930
+ }
931
+
932
+ const pageId = await this.extractPageId(pageIdOrUrl);
933
+ await this.client.delete(`/content/${pageId}/child/attachment/${attachmentId}`);
934
+ return { id: String(attachmentId), pageId: String(pageId) };
935
+ }
936
+
937
+ /**
938
+ * List content properties for a page with pagination support
939
+ */
940
+ async listProperties(pageIdOrUrl, options = {}) {
941
+ const pageId = await this.extractPageId(pageIdOrUrl);
942
+ const limit = this.parsePositiveInt(options.limit, 25);
943
+ const start = this.parsePositiveInt(options.start, 0);
944
+ const params = { limit, start };
945
+
946
+ const response = await this.client.get(`/content/${pageId}/property`, { params });
947
+ const results = Array.isArray(response.data.results) ? response.data.results : [];
948
+
949
+ return {
950
+ results,
951
+ nextStart: this.parseNextStart(response.data?._links?.next)
952
+ };
953
+ }
954
+
955
+ /**
956
+ * Fetch all content properties for a page, honoring an optional maxResults cap
957
+ */
958
+ async getAllProperties(pageIdOrUrl, options = {}) {
959
+ const pageSize = this.parsePositiveInt(options.pageSize || options.limit, 25);
960
+ const maxResults = this.parsePositiveInt(options.maxResults, null);
961
+ let start = this.parsePositiveInt(options.start, 0);
962
+ const properties = [];
963
+
964
+ let hasNext = true;
965
+ while (hasNext) {
966
+ const page = await this.listProperties(pageIdOrUrl, {
967
+ limit: pageSize,
968
+ start
969
+ });
970
+ properties.push(...page.results);
971
+
972
+ if (maxResults && properties.length >= maxResults) {
973
+ return properties.slice(0, maxResults);
974
+ }
975
+
976
+ hasNext = page.nextStart !== null && page.nextStart !== undefined;
977
+ if (hasNext) {
978
+ start = page.nextStart;
979
+ }
980
+ }
981
+
982
+ return properties;
983
+ }
984
+
985
+ /**
986
+ * Get a single content property by key
987
+ */
988
+ async getProperty(pageIdOrUrl, key) {
989
+ const pageId = await this.extractPageId(pageIdOrUrl);
990
+ const response = await this.client.get(`/content/${pageId}/property/${encodeURIComponent(key)}`);
991
+ return response.data;
992
+ }
993
+
994
+ /**
995
+ * Set (create or update) a content property
996
+ */
997
+ async setProperty(pageIdOrUrl, key, value) {
998
+ const pageId = await this.extractPageId(pageIdOrUrl);
999
+ const encodedKey = encodeURIComponent(key);
1000
+
1001
+ let version = 1;
1002
+ try {
1003
+ const existing = await this.client.get(`/content/${pageId}/property/${encodedKey}`);
1004
+ version = existing.data.version.number + 1;
1005
+ } catch (err) {
1006
+ if (!err.response || err.response.status !== 404) {
1007
+ throw err;
1008
+ }
1009
+ }
1010
+
1011
+ const response = await this.client.put(`/content/${pageId}/property/${encodedKey}`, {
1012
+ key,
1013
+ value,
1014
+ version: { number: version }
1015
+ });
1016
+ return response.data;
1017
+ }
1018
+
1019
+ /**
1020
+ * Delete a content property by key
1021
+ */
1022
+ async deleteProperty(pageIdOrUrl, key) {
1023
+ const pageId = await this.extractPageId(pageIdOrUrl);
1024
+ await this.client.delete(`/content/${pageId}/property/${encodeURIComponent(key)}`);
1025
+ return { pageId: String(pageId), key };
1026
+ }
1027
+
841
1028
  /**
842
1029
  * Convert markdown to Confluence storage format
843
1030
  */
@@ -853,36 +1040,39 @@ class ConfluenceClient {
853
1040
  * Convert HTML to native Confluence storage format
854
1041
  */
855
1042
  htmlToConfluenceStorage(html) {
856
- // MarkdownIt already produces valid HTML tags (h1-h6, p, strong, em, ul, ol, table, etc.)
857
- // This function only transforms elements that need Confluence-specific handling:
858
- // - li content wrapped in p (Confluence requirement)
859
- // - pre/code → ac:structured-macro code blocks with CDATA
860
- // - blockquote → info/warning/note macros
861
- // - th/td content wrapped in p
862
- // - hr → self-closing hr /
863
1043
  let storage = html;
864
1044
 
865
- // Wrap li content in p tags (Confluence requirement)
1045
+ // Convert headings to native Confluence format
1046
+ storage = storage.replace(/<h([1-6])>(.*?)<\/h[1-6]>/g, '<h$1>$2</h$1>');
1047
+
1048
+ // Convert paragraphs
1049
+ storage = storage.replace(/<p>(.*?)<\/p>/g, '<p>$1</p>');
1050
+
1051
+ // Convert strong/bold text
1052
+ storage = storage.replace(/<strong>(.*?)<\/strong>/g, '<strong>$1</strong>');
1053
+
1054
+ // Convert emphasis/italic text
1055
+ storage = storage.replace(/<em>(.*?)<\/em>/g, '<em>$1</em>');
1056
+
1057
+ // Convert unordered lists
1058
+ storage = storage.replace(/<ul>(.*?)<\/ul>/gs, '<ul>$1</ul>');
866
1059
  storage = storage.replace(/<li>(.*?)<\/li>/g, '<li><p>$1</p></li>');
867
1060
 
1061
+ // Convert ordered lists
1062
+ storage = storage.replace(/<ol>(.*?)<\/ol>/gs, '<ol>$1</ol>');
1063
+
868
1064
  // Convert code blocks to Confluence code macro
869
1065
  storage = storage.replace(/<pre><code(?:\s+class="language-(\w+)")?>(.*?)<\/code><\/pre>/gs, (_, lang, code) => {
870
1066
  const language = lang || 'text';
871
- // Decode HTML entities inside code blocks before wrapping in CDATA
872
- let decoded = code
873
- .replace(/&amp;/g, '&')
874
- .replace(/&lt;/g, '<')
875
- .replace(/&gt;/g, '>')
876
- .replace(/&quot;/g, '"')
877
- .replace(/&#39;/g, '\'');
878
- decoded = decoded.replace(/\]\]>/g, ']]]]><![CDATA[>');
879
-
880
1067
  return `<ac:structured-macro ac:name="code">
881
1068
  <ac:parameter ac:name="language">${language}</ac:parameter>
882
- <ac:plain-text-body><![CDATA[${decoded}]]></ac:plain-text-body>
1069
+ <ac:plain-text-body><![CDATA[${code}]]></ac:plain-text-body>
883
1070
  </ac:structured-macro>`;
884
1071
  });
885
1072
 
1073
+ // Convert inline code
1074
+ storage = storage.replace(/<code>(.*?)<\/code>/g, '<code>$1</code>');
1075
+
886
1076
  // Convert blockquotes to appropriate macros based on content
887
1077
  storage = storage.replace(/<blockquote>(.*?)<\/blockquote>/gs, (_, content) => {
888
1078
  // Check for admonition patterns
@@ -909,14 +1099,22 @@ class ConfluenceClient {
909
1099
  }
910
1100
  });
911
1101
 
912
- // Wrap th/td content in p tags (Confluence requirement)
1102
+ // Convert tables
1103
+ storage = storage.replace(/<table>(.*?)<\/table>/gs, '<table>$1</table>');
1104
+ storage = storage.replace(/<thead>(.*?)<\/thead>/gs, '<thead>$1</thead>');
1105
+ storage = storage.replace(/<tbody>(.*?)<\/tbody>/gs, '<tbody>$1</tbody>');
1106
+ storage = storage.replace(/<tr>(.*?)<\/tr>/gs, '<tr>$1</tr>');
913
1107
  storage = storage.replace(/<th>(.*?)<\/th>/g, '<th><p>$1</p></th>');
914
1108
  storage = storage.replace(/<td>(.*?)<\/td>/g, '<td><p>$1</p></td>');
915
1109
 
916
- // Links: keep <a href> as-is (compatible with both Cloud and Server/Data Center)
1110
+ // Convert links
1111
+ storage = storage.replace(/<a href="(.*?)">(.*?)<\/a>/g, '<ac:link><ri:url ri:value="$1" /><ac:plain-text-link-body><![CDATA[$2]]></ac:plain-text-link-body></ac:link>');
917
1112
 
918
- // Convert horizontal rules
919
- storage = storage.replace(/<hr\s*\/?>/g, '<hr />');
1113
+ // Remove horizontal rules (not needed in Confluence storage format)
1114
+ storage = storage.replace(/<hr\s*\/?>/g, '');
1115
+
1116
+ // Clean up any remaining HTML entities and normalize whitespace
1117
+ storage = storage.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
920
1118
 
921
1119
  return storage;
922
1120
  }
@@ -1136,11 +1334,11 @@ class ConfluenceClient {
1136
1334
  // Try to build a proper URL - if spaceKey starts with ~, it's a user space
1137
1335
  if (spaceKey.startsWith('~')) {
1138
1336
  const spacePath = `display/${spaceKey}/${encodeURIComponent(title)}`;
1139
- return `\n> 📄 **${labels.includePage}**: [${title}](https://${this.domain}/wiki/${spacePath})\n`;
1337
+ return `\n> 📄 **${labels.includePage}**: [${title}](${this.buildUrl(`/display/${spacePath}`)})\n`;
1140
1338
  } else {
1141
1339
  // For non-user spaces, we cannot construct a valid link without the page ID.
1142
1340
  // Document that manual correction is required.
1143
- return `\n> 📄 **${labels.includePage}**: [${title}](https://${this.domain}/wiki/spaces/${spaceKey}/pages/[PAGE_ID_HERE]) _(manual link correction required)_\n`;
1341
+ return `\n> 📄 **${labels.includePage}**: [${title}](${this.buildUrl(`/spaces/${spaceKey}/pages/[PAGE_ID_HERE]`)}) _(manual link correction required)_\n`;
1144
1342
  }
1145
1343
  });
1146
1344
 
@@ -1335,6 +1533,9 @@ class ConfluenceClient {
1335
1533
  // Numeric HTML entities
1336
1534
  markdown = markdown.replace(/&#(\d+);/g, (_, code) => String.fromCharCode(parseInt(code, 10)));
1337
1535
  markdown = markdown.replace(/&#x([0-9a-fA-F]+);/g, (_, code) => String.fromCharCode(parseInt(code, 16)));
1536
+
1537
+ // Clean up nordic alphabets and other named entities
1538
+ markdown = markdown.replace(/&([a-zA-Z]+);/g, (match, name) => NAMED_ENTITIES[name] || match);
1338
1539
 
1339
1540
  // Clean up extra whitespace for standard Markdown format
1340
1541
  // Remove trailing spaces from each line
@@ -1358,7 +1559,7 @@ class ConfluenceClient {
1358
1559
  */
1359
1560
  async createPage(title, spaceKey, content, format = 'storage', options = {}) {
1360
1561
  let storageContent = content;
1361
-
1562
+
1362
1563
  if (format === 'markdown') {
1363
1564
  storageContent = this.markdownToStorage(content);
1364
1565
  } else if (format === 'html') {
@@ -1393,7 +1594,7 @@ class ConfluenceClient {
1393
1594
  */
1394
1595
  async createChildPage(title, spaceKey, parentId, content, format = 'storage', options = {}) {
1395
1596
  let storageContent = content;
1396
-
1597
+
1397
1598
  if (format === 'markdown') {
1398
1599
  storageContent = this.markdownToStorage(content);
1399
1600
  } else if (format === 'html') {
@@ -1480,6 +1681,55 @@ class ConfluenceClient {
1480
1681
  return response.data;
1481
1682
  }
1482
1683
 
1684
+ /**
1685
+ * Move a page to a new parent location
1686
+ */
1687
+ async movePage(pageIdOrUrl, newParentIdOrUrl, newTitle = null) {
1688
+ // Resolve both IDs from URLs if needed
1689
+ const pageId = await this.extractPageId(pageIdOrUrl);
1690
+ const newParentId = await this.extractPageId(newParentIdOrUrl);
1691
+
1692
+ // Fetch current page
1693
+ const response = await this.client.get(`/content/${pageId}`, {
1694
+ params: { expand: 'body.storage,version,space' }
1695
+ });
1696
+ const { version, title, body, space } = response.data;
1697
+
1698
+ // Fetch new parent to get its space (for validation)
1699
+ const parentResponse = await this.client.get(`/content/${newParentId}`, {
1700
+ params: { expand: 'space' }
1701
+ });
1702
+ const parentSpace = parentResponse.data.space;
1703
+
1704
+ // Validate same space
1705
+ if (parentSpace.key !== space.key) {
1706
+ throw new Error(
1707
+ `Cannot move page across spaces. Page is in space "${space.key}" ` +
1708
+ `but new parent is in space "${parentSpace.key}". ` +
1709
+ 'Pages can only be moved within the same space.'
1710
+ );
1711
+ }
1712
+
1713
+ // Proceed with move
1714
+ const pageData = {
1715
+ id: pageId,
1716
+ type: 'page',
1717
+ title: newTitle || title,
1718
+ space: { key: space.key },
1719
+ body: {
1720
+ storage: {
1721
+ value: body.storage.value,
1722
+ representation: 'storage'
1723
+ }
1724
+ },
1725
+ version: { number: version.number + 1 },
1726
+ ancestors: [{ id: newParentId }]
1727
+ };
1728
+
1729
+ const updateResponse = await this.client.put(`/content/${pageId}`, pageData);
1730
+ return updateResponse.data;
1731
+ }
1732
+
1483
1733
  /**
1484
1734
  * Get page content for editing
1485
1735
  */
@@ -1807,6 +2057,13 @@ class ConfluenceClient {
1807
2057
  };
1808
2058
  }
1809
2059
 
2060
+ buildUrl(path) {
2061
+ if (!path) return '';
2062
+ const context = this._contextPath || '';
2063
+ const normalized = path.startsWith('/') ? path : `/${path}`;
2064
+ return `${this.protocol}://${this.domain}${context}${normalized}`;
2065
+ }
2066
+
1810
2067
  toAbsoluteUrl(pathOrUrl) {
1811
2068
  if (!pathOrUrl) {
1812
2069
  return null;
@@ -1816,8 +2073,7 @@ class ConfluenceClient {
1816
2073
  return pathOrUrl;
1817
2074
  }
1818
2075
 
1819
- const normalized = pathOrUrl.startsWith('/') ? pathOrUrl : `/${pathOrUrl}`;
1820
- return `https://${this.domain}${normalized}`;
2076
+ return this.buildUrl(pathOrUrl);
1821
2077
  }
1822
2078
 
1823
2079
  parseNextStart(nextLink) {
@@ -1844,3 +2100,4 @@ class ConfluenceClient {
1844
2100
  }
1845
2101
 
1846
2102
  module.exports = ConfluenceClient;
2103
+ module.exports.NAMED_ENTITIES = NAMED_ENTITIES;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bestend/confluence-cli",
3
- "version": "1.16.0",
3
+ "version": "2.0.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": {
@@ -20,13 +20,14 @@
20
20
  "wiki",
21
21
  "documentation"
22
22
  ],
23
- "author": "pchuri",
23
+ "author": "bestend",
24
24
  "license": "MIT",
25
25
  "dependencies": {
26
26
  "axios": "^1.12.0",
27
27
  "chalk": "^4.1.2",
28
28
  "commander": "^11.1.0",
29
- "fast-xml-parser": "^4.5.3",
29
+ "fast-xml-parser": "^5.5.4",
30
+ "form-data": "^4.0.5",
30
31
  "html-to-text": "^9.0.5",
31
32
  "inquirer": "^8.2.6",
32
33
  "markdown-it": "^14.1.0",
@@ -54,5 +55,10 @@
54
55
  "bugs": {
55
56
  "url": "https://github.com/bestend/confluence-cli/issues"
56
57
  },
57
- "homepage": "https://github.com/bestend/confluence-cli#readme"
58
+ "homepage": "https://github.com/bestend/confluence-cli#readme",
59
+ "files": [
60
+ "bin/",
61
+ "lib/",
62
+ ".claude/skills/"
63
+ ]
58
64
  }
package/.eslintrc.js DELETED
@@ -1,23 +0,0 @@
1
- module.exports = {
2
- env: {
3
- es2021: true,
4
- node: true,
5
- jest: true
6
- },
7
- extends: [
8
- 'eslint:recommended'
9
- ],
10
- parserOptions: {
11
- ecmaVersion: 12,
12
- sourceType: 'module'
13
- },
14
- rules: {
15
- 'indent': ['error', 2],
16
- 'linebreak-style': ['error', 'unix'],
17
- 'quotes': ['error', 'single'],
18
- 'semi': ['error', 'always'],
19
- 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
20
- 'no-console': 'off',
21
- 'no-process-exit': 'off'
22
- }
23
- };
@@ -1,34 +0,0 @@
1
- ---
2
- name: Bug report
3
- about: Create a report to help us improve
4
- title: '[BUG] '
5
- labels: bug
6
- assignees: ''
7
-
8
- ---
9
-
10
- **Describe the bug**
11
- A clear and concise description of what the bug is.
12
-
13
- **To Reproduce**
14
- Steps to reproduce the behavior:
15
- 1. Run command '...'
16
- 2. See error
17
-
18
- **Expected behavior**
19
- A clear and concise description of what you expected to happen.
20
-
21
- **Screenshots**
22
- If applicable, add screenshots to help explain your problem.
23
-
24
- **Environment (please complete the following information):**
25
- - OS: [e.g. macOS, Windows, Linux]
26
- - Node.js version: [e.g. 18.17.0]
27
- - confluence-cli version: [e.g. 1.0.0]
28
- - Confluence version: [e.g. Cloud, Server 8.5]
29
-
30
- **Additional context**
31
- Add any other context about the problem here.
32
-
33
- **Error logs**
34
- If applicable, add error logs or stack traces.
@@ -1,26 +0,0 @@
1
- ---
2
- name: Feature request
3
- about: Suggest an idea for this project
4
- title: '[FEATURE] '
5
- labels: enhancement
6
- assignees: ''
7
-
8
- ---
9
-
10
- **Is your feature request related to a problem? Please describe.**
11
- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12
-
13
- **Describe the solution you'd like**
14
- A clear and concise description of what you want to happen.
15
-
16
- **Describe alternatives you've considered**
17
- A clear and concise description of any alternative solutions or features you've considered.
18
-
19
- **Use case**
20
- Describe how this feature would be used and who would benefit from it.
21
-
22
- **Additional context**
23
- Add any other context or screenshots about the feature request here.
24
-
25
- **Implementation suggestions**
26
- If you have ideas about how this could be implemented, please share them here.
@@ -1,37 +0,0 @@
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!