confluence-cli 1.26.0 → 1.27.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/.claude/skills/confluence/SKILL.md +98 -2
- package/README.md +78 -1
- package/bin/confluence.js +30 -5
- package/lib/config.js +21 -6
- package/package.json +1 -1
|
@@ -25,6 +25,18 @@ confluence --version # verify install
|
|
|
25
25
|
| `CONFLUENCE_AUTH_TYPE` | `basic` or `bearer` | `basic` |
|
|
26
26
|
| `CONFLUENCE_EMAIL` | Email address (basic auth only) | `user@company.com` |
|
|
27
27
|
| `CONFLUENCE_API_TOKEN` | API token or personal access token | `ATATT3x...` |
|
|
28
|
+
| `CONFLUENCE_PROFILE` | Named profile to use (optional) | `staging` |
|
|
29
|
+
| `CONFLUENCE_READ_ONLY` | Block all write operations when `true` | `true` |
|
|
30
|
+
|
|
31
|
+
**Global `--profile` flag (use a named profile for any command):**
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
confluence --profile <name> <command>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Config resolution works in two stages:
|
|
38
|
+
- **Direct env config:** If both `CONFLUENCE_DOMAIN` and `CONFLUENCE_API_TOKEN` are set, they are used directly and the config file / profiles are not consulted.
|
|
39
|
+
- **Profile-based config:** Otherwise, a profile is selected in this order: `--profile` flag > `CONFLUENCE_PROFILE` env > `activeProfile` in config > `default`.
|
|
28
40
|
|
|
29
41
|
**Non-interactive init (good for CI/CD scripts):**
|
|
30
42
|
|
|
@@ -52,6 +64,27 @@ export CONFLUENCE_EMAIL="user@company.com"
|
|
|
52
64
|
export CONFLUENCE_API_TOKEN="your-scoped-token"
|
|
53
65
|
```
|
|
54
66
|
|
|
67
|
+
**Read-only mode (recommended for AI agents):**
|
|
68
|
+
|
|
69
|
+
Prevents all write operations (create, update, delete, move, etc.) at the profile level. Useful when giving an AI agent access to Confluence for reading only.
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
# Via profile flag
|
|
73
|
+
confluence profile add agent --domain "company.atlassian.net" --token "xxx" --read-only
|
|
74
|
+
|
|
75
|
+
# Via environment variable (overrides config file)
|
|
76
|
+
export CONFLUENCE_READ_ONLY=true
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
When read-only mode is active, any write command exits with an error:
|
|
80
|
+
```
|
|
81
|
+
Error: This profile is in read-only mode. Write operations are not allowed.
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`profile list` shows read-only profiles with a `[read-only]` badge.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
55
88
|
## Page ID Resolution
|
|
56
89
|
|
|
57
90
|
Most commands accept `<pageId>` — a numeric ID or any of the supported URL formats below.
|
|
@@ -91,10 +124,14 @@ confluence read "https://company.atlassian.net/wiki/spaces/MYSPACE/pages/1234567
|
|
|
91
124
|
Initialize configuration. Saves credentials to `~/.confluence-cli/config.json`.
|
|
92
125
|
|
|
93
126
|
```sh
|
|
94
|
-
confluence init [--domain <domain>] [--api-path <path>] [--auth-type basic|bearer] [--email <email>] [--token <token>]
|
|
127
|
+
confluence init [--domain <domain>] [--api-path <path>] [--auth-type basic|bearer] [--email <email>] [--token <token>] [--read-only]
|
|
95
128
|
```
|
|
96
129
|
|
|
97
|
-
All flags are optional; omitting any flag triggers an interactive prompt for that field. Provide all flags to run fully non-interactive.
|
|
130
|
+
All flags are optional; omitting any flag triggers an interactive prompt for that field. Provide all flags to run fully non-interactive. Use the global `--profile` flag to save to a named profile:
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
confluence --profile staging init --domain "staging.example.com" --auth-type bearer --token "your-token"
|
|
134
|
+
```
|
|
98
135
|
|
|
99
136
|
---
|
|
100
137
|
|
|
@@ -520,6 +557,60 @@ confluence copy-tree 123456789 987654321 --exclude "Draft*,Archive*"
|
|
|
520
557
|
|
|
521
558
|
---
|
|
522
559
|
|
|
560
|
+
### `profile list`
|
|
561
|
+
|
|
562
|
+
List all configuration profiles with the active profile marked.
|
|
563
|
+
|
|
564
|
+
```sh
|
|
565
|
+
confluence profile list
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
### `profile use <name>`
|
|
571
|
+
|
|
572
|
+
Switch the active configuration profile.
|
|
573
|
+
|
|
574
|
+
```sh
|
|
575
|
+
confluence profile use <name>
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
```sh
|
|
579
|
+
confluence profile use staging
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
### `profile add <name>`
|
|
585
|
+
|
|
586
|
+
Add a new configuration profile. Supports the same options as `init` (interactive, non-interactive, or hybrid).
|
|
587
|
+
|
|
588
|
+
```sh
|
|
589
|
+
confluence profile add <name> [--domain <domain>] [--api-path <path>] [--auth-type basic|bearer] [--email <email>] [--token <token>] [--protocol http|https] [--read-only]
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
Profile names may contain letters, numbers, hyphens, and underscores only.
|
|
593
|
+
|
|
594
|
+
```sh
|
|
595
|
+
confluence profile add staging --domain "staging.example.com" --auth-type bearer --token "xyz"
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
---
|
|
599
|
+
|
|
600
|
+
### `profile remove <name>`
|
|
601
|
+
|
|
602
|
+
Remove a configuration profile (prompts for confirmation). Cannot remove the only remaining profile.
|
|
603
|
+
|
|
604
|
+
```sh
|
|
605
|
+
confluence profile remove <name>
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
```sh
|
|
609
|
+
confluence profile remove staging
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
---
|
|
613
|
+
|
|
523
614
|
### `stats`
|
|
524
615
|
|
|
525
616
|
Show local usage statistics.
|
|
@@ -614,6 +705,8 @@ confluence search "release notes" --limit 20
|
|
|
614
705
|
- **ANSI color codes**: stdout may contain ANSI escape sequences. Pipe through `| cat` or use `NO_COLOR=1` if your downstream tool doesn't handle them.
|
|
615
706
|
- **Page ID vs URL**: when you have a Confluence URL, extract `?pageId=<number>` and pass the number. Do not pass pretty/display URLs — they are not supported.
|
|
616
707
|
- **Cross-space moves**: `confluence move` only works within the same space. Moving across spaces is not supported.
|
|
708
|
+
- **Multiple instances**: Use `--profile <name>` or `CONFLUENCE_PROFILE` env var to target different Confluence instances without reconfiguring.
|
|
709
|
+
- **Read-only mode**: Set `CONFLUENCE_READ_ONLY=true` or use `--read-only` when creating profiles to prevent accidental writes. This is enforced at the CLI level — all write commands will be blocked.
|
|
617
710
|
|
|
618
711
|
## Error Patterns
|
|
619
712
|
|
|
@@ -624,3 +717,6 @@ confluence search "release notes" --limit 20
|
|
|
624
717
|
| 400 on inline comment creation | Editor metadata required | Use `--location footer` or reply to existing inline comment with `--parent` |
|
|
625
718
|
| `File not found: <path>` | `--file` path doesn't exist | Check the path before calling the command |
|
|
626
719
|
| `At least one of --title, --file, or --content must be provided` | `update` called with no content options | Provide at least one of the required options |
|
|
720
|
+
| `Profile "<name>" not found!` | Specified profile doesn't exist | Run `confluence profile list` to see available profiles |
|
|
721
|
+
| `Cannot delete the only remaining profile.` | Tried to remove the last profile | Add another profile before removing |
|
|
722
|
+
| `This profile is in read-only mode` | Write command used with a read-only profile | Use a writable profile or remove `readOnly` from config |
|
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ A powerful command-line interface for Atlassian Confluence that allows you to re
|
|
|
16
16
|
- 💬 **Comments** - List, create, and delete page comments (footer or inline)
|
|
17
17
|
- 📦 **Export** - Save a page and its attachments to a local folder
|
|
18
18
|
- 🛠️ **Edit workflow** - Export page content for editing and re-import
|
|
19
|
+
- 🔀 **Profiles** - Manage multiple Confluence instances with named configuration profiles
|
|
20
|
+
- 🔒 **Read-only mode** - Profile-level write protection for safe AI agent usage
|
|
19
21
|
- 🔧 **Easy setup** - Simple configuration with environment variables or interactive setup
|
|
20
22
|
|
|
21
23
|
## Installation
|
|
@@ -115,6 +117,15 @@ confluence init \
|
|
|
115
117
|
--token "your-scoped-token"
|
|
116
118
|
```
|
|
117
119
|
|
|
120
|
+
**Named profile** (save to a specific profile):
|
|
121
|
+
```bash
|
|
122
|
+
confluence --profile staging init \
|
|
123
|
+
--domain "staging.example.com" \
|
|
124
|
+
--api-path "/rest/api" \
|
|
125
|
+
--auth-type "bearer" \
|
|
126
|
+
--token "your-personal-access-token"
|
|
127
|
+
```
|
|
128
|
+
|
|
118
129
|
**Hybrid mode** (some fields provided, rest via prompts):
|
|
119
130
|
```bash
|
|
120
131
|
# Domain and token provided, will prompt for auth method and email
|
|
@@ -130,6 +141,7 @@ confluence init --email "user@example.com" --token "your-api-token"
|
|
|
130
141
|
- `-a, --auth-type <type>` - Authentication type: `basic` or `bearer`
|
|
131
142
|
- `-e, --email <email>` - Email or username for basic authentication
|
|
132
143
|
- `-t, --token <token>` - API token or password
|
|
144
|
+
- `--read-only` - Enable read-only mode (blocks all write operations)
|
|
133
145
|
|
|
134
146
|
⚠️ **Security note:** While flags work, storing tokens in shell history is risky. Prefer environment variables (Option 3) for production environments.
|
|
135
147
|
|
|
@@ -141,6 +153,8 @@ export CONFLUENCE_EMAIL="your.email@example.com" # required for basic auth (ali
|
|
|
141
153
|
export CONFLUENCE_API_PATH="/wiki/rest/api" # Cloud default; use /rest/api for Server/DC
|
|
142
154
|
# Optional: set to 'bearer' for self-hosted/Data Center instances
|
|
143
155
|
export CONFLUENCE_AUTH_TYPE="basic"
|
|
156
|
+
# Optional: select a named profile (overridden by --profile flag)
|
|
157
|
+
export CONFLUENCE_PROFILE="default"
|
|
144
158
|
```
|
|
145
159
|
|
|
146
160
|
**Scoped API token** (recommended for agents):
|
|
@@ -154,6 +168,12 @@ export CONFLUENCE_API_TOKEN="your-scoped-token"
|
|
|
154
168
|
|
|
155
169
|
`CONFLUENCE_API_PATH` defaults to `/wiki/rest/api` for Atlassian Cloud domains and `/rest/api` otherwise. Override it when your site lives under a custom reverse proxy or on-premises path. `CONFLUENCE_AUTH_TYPE` defaults to `basic` when an email is present and falls back to `bearer` otherwise.
|
|
156
170
|
|
|
171
|
+
**Read-only mode** (recommended for AI agents):
|
|
172
|
+
```bash
|
|
173
|
+
export CONFLUENCE_READ_ONLY=true
|
|
174
|
+
```
|
|
175
|
+
When set, all write operations (`create`, `update`, `delete`, etc.) are blocked at the CLI level. The environment variable overrides the profile's `readOnly` setting.
|
|
176
|
+
|
|
157
177
|
### Getting Your API Token
|
|
158
178
|
|
|
159
179
|
**Atlassian Cloud:**
|
|
@@ -434,6 +454,52 @@ vim ./page-to-edit.xml
|
|
|
434
454
|
confluence update 123456789 --file ./page-to-edit.xml --format storage
|
|
435
455
|
```
|
|
436
456
|
|
|
457
|
+
### Profile Management
|
|
458
|
+
```bash
|
|
459
|
+
# List all profiles and see which is active
|
|
460
|
+
confluence profile list
|
|
461
|
+
|
|
462
|
+
# Switch the active profile
|
|
463
|
+
confluence profile use staging
|
|
464
|
+
|
|
465
|
+
# Add a new profile interactively
|
|
466
|
+
confluence profile add staging
|
|
467
|
+
|
|
468
|
+
# Add a new profile non-interactively
|
|
469
|
+
confluence profile add staging --domain "staging.example.com" --auth-type bearer --token "xyz"
|
|
470
|
+
|
|
471
|
+
# Add a read-only profile (blocks all write operations)
|
|
472
|
+
confluence profile add agent --domain "company.atlassian.net" --auth-type basic --email "bot@example.com" --token "xyz" --read-only
|
|
473
|
+
|
|
474
|
+
# Remove a profile
|
|
475
|
+
confluence profile remove staging
|
|
476
|
+
|
|
477
|
+
# Use a specific profile for a single command
|
|
478
|
+
confluence --profile staging spaces
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Read-Only Mode
|
|
482
|
+
|
|
483
|
+
Read-only mode blocks all write operations at the CLI level, making it safe to hand the tool to AI agents (Claude Code, Copilot, etc.) without risking accidental edits.
|
|
484
|
+
|
|
485
|
+
**Enable via profile:**
|
|
486
|
+
```bash
|
|
487
|
+
# During init
|
|
488
|
+
confluence init --read-only
|
|
489
|
+
|
|
490
|
+
# When adding a profile
|
|
491
|
+
confluence profile add agent --domain "company.atlassian.net" --token "xyz" --read-only
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
**Enable via environment variable:**
|
|
495
|
+
```bash
|
|
496
|
+
export CONFLUENCE_READ_ONLY=true # overrides profile setting
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
When read-only mode is active, any write command (`create`, `create-child`, `update`, `delete`, `move`, `edit`, `comment`, `attachment-upload`, `attachment-delete`, `property-set`, `property-delete`, `comment-delete`, `copy-tree`) exits with code 1 and prints an error message.
|
|
500
|
+
|
|
501
|
+
`confluence profile list` shows a `[read-only]` badge next to protected profiles.
|
|
502
|
+
|
|
437
503
|
### View Usage Statistics
|
|
438
504
|
```bash
|
|
439
505
|
confluence stats
|
|
@@ -443,7 +509,7 @@ confluence stats
|
|
|
443
509
|
|
|
444
510
|
| Command | Description | Options |
|
|
445
511
|
|---|---|---|
|
|
446
|
-
| `init` | Initialize CLI configuration | |
|
|
512
|
+
| `init` | Initialize CLI configuration | `--read-only` |
|
|
447
513
|
| `read <pageId_or_url>` | Read page content | `--format <html\|text\|markdown>` |
|
|
448
514
|
| `info <pageId_or_url>` | Get page information | |
|
|
449
515
|
| `search <query>` | Search for pages | `--limit <number>` |
|
|
@@ -468,8 +534,14 @@ confluence stats
|
|
|
468
534
|
| `property-set <pageId_or_url> <key>` | Set a content property (create or update) | `--value <json>`, `--file <path>`, `--format <text\|json>` |
|
|
469
535
|
| `property-delete <pageId_or_url> <key>` | Delete a content property by key | `--yes` |
|
|
470
536
|
| `export <pageId_or_url>` | Export a page to a directory with its attachments | `--format <html\|text\|markdown>`, `--dest <directory>`, `--file <filename>`, `--attachments-dir <name>`, `--pattern <glob>`, `--referenced-only`, `--skip-attachments` |
|
|
537
|
+
| `profile list` | List all configuration profiles | |
|
|
538
|
+
| `profile use <name>` | Set the active configuration profile | |
|
|
539
|
+
| `profile add <name>` | Add a new configuration profile | `-d, --domain`, `-p, --api-path`, `-a, --auth-type`, `-e, --email`, `-t, --token`, `--protocol`, `--read-only` |
|
|
540
|
+
| `profile remove <name>` | Remove a configuration profile | |
|
|
471
541
|
| `stats` | View your usage statistics | |
|
|
472
542
|
|
|
543
|
+
**Global option:** `--profile <name>` — Use a specific profile for any command (overrides `CONFLUENCE_PROFILE` env var and active profile).
|
|
544
|
+
|
|
473
545
|
## Examples
|
|
474
546
|
|
|
475
547
|
```bash
|
|
@@ -503,6 +575,11 @@ confluence attachment-delete 123456789 998877 --yes
|
|
|
503
575
|
|
|
504
576
|
# View usage statistics
|
|
505
577
|
confluence stats
|
|
578
|
+
|
|
579
|
+
# Profile management
|
|
580
|
+
confluence profile list
|
|
581
|
+
confluence profile use staging
|
|
582
|
+
confluence --profile staging spaces
|
|
506
583
|
```
|
|
507
584
|
|
|
508
585
|
## Development
|
package/bin/confluence.js
CHANGED
|
@@ -13,6 +13,14 @@ function buildPageUrl(config, path) {
|
|
|
13
13
|
return `${protocol}://${config.domain}${path}`;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function assertWritable(config) {
|
|
17
|
+
if (config.readOnly) {
|
|
18
|
+
console.error(chalk.red('Error: This profile is in read-only mode. Write operations are not allowed.'));
|
|
19
|
+
console.error(chalk.yellow('Tip: Use "confluence profile add <name>" without --read-only, or set readOnly to false in config.'));
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
16
24
|
program
|
|
17
25
|
.name('confluence')
|
|
18
26
|
.description('CLI tool for Atlassian Confluence')
|
|
@@ -34,6 +42,7 @@ program
|
|
|
34
42
|
.option('-a, --auth-type <type>', 'Authentication type (basic or bearer)')
|
|
35
43
|
.option('-e, --email <email>', 'Email or username for basic auth')
|
|
36
44
|
.option('-t, --token <token>', 'API token')
|
|
45
|
+
.option('--read-only', 'Set profile to read-only mode (blocks write operations)')
|
|
37
46
|
.action(async (options) => {
|
|
38
47
|
const profile = getProfileName();
|
|
39
48
|
await initConfig({ ...options, profile });
|
|
@@ -208,8 +217,9 @@ program
|
|
|
208
217
|
const analytics = new Analytics();
|
|
209
218
|
try {
|
|
210
219
|
const config = getConfig(getProfileName());
|
|
220
|
+
assertWritable(config);
|
|
211
221
|
const client = new ConfluenceClient(config);
|
|
212
|
-
|
|
222
|
+
|
|
213
223
|
let content = '';
|
|
214
224
|
|
|
215
225
|
if (options.file) {
|
|
@@ -251,8 +261,9 @@ program
|
|
|
251
261
|
const analytics = new Analytics();
|
|
252
262
|
try {
|
|
253
263
|
const config = getConfig(getProfileName());
|
|
264
|
+
assertWritable(config);
|
|
254
265
|
const client = new ConfluenceClient(config);
|
|
255
|
-
|
|
266
|
+
|
|
256
267
|
// Get parent page info to get space key
|
|
257
268
|
const parentInfo = await client.getPageInfo(parentId);
|
|
258
269
|
const spaceKey = parentInfo.space.key;
|
|
@@ -305,8 +316,9 @@ program
|
|
|
305
316
|
}
|
|
306
317
|
|
|
307
318
|
const config = getConfig(getProfileName());
|
|
319
|
+
assertWritable(config);
|
|
308
320
|
const client = new ConfluenceClient(config);
|
|
309
|
-
|
|
321
|
+
|
|
310
322
|
let content = null; // Use null to indicate no content change
|
|
311
323
|
|
|
312
324
|
if (options.file) {
|
|
@@ -344,6 +356,7 @@ program
|
|
|
344
356
|
const analytics = new Analytics();
|
|
345
357
|
try {
|
|
346
358
|
const config = getConfig(getProfileName());
|
|
359
|
+
assertWritable(config);
|
|
347
360
|
const client = new ConfluenceClient(config);
|
|
348
361
|
const result = await client.movePage(pageId, newParentId, options.title);
|
|
349
362
|
|
|
@@ -371,6 +384,7 @@ program
|
|
|
371
384
|
const analytics = new Analytics();
|
|
372
385
|
try {
|
|
373
386
|
const config = getConfig(getProfileName());
|
|
387
|
+
assertWritable(config);
|
|
374
388
|
const client = new ConfluenceClient(config);
|
|
375
389
|
const pageInfo = await client.getPageInfo(pageIdOrUrl);
|
|
376
390
|
|
|
@@ -414,6 +428,7 @@ program
|
|
|
414
428
|
const analytics = new Analytics();
|
|
415
429
|
try {
|
|
416
430
|
const config = getConfig(getProfileName());
|
|
431
|
+
assertWritable(config);
|
|
417
432
|
const client = new ConfluenceClient(config);
|
|
418
433
|
const pageData = await client.getPageForEdit(pageId);
|
|
419
434
|
|
|
@@ -613,6 +628,7 @@ program
|
|
|
613
628
|
const fs = require('fs');
|
|
614
629
|
const path = require('path');
|
|
615
630
|
const config = getConfig(getProfileName());
|
|
631
|
+
assertWritable(config);
|
|
616
632
|
const client = new ConfluenceClient(config);
|
|
617
633
|
|
|
618
634
|
const resolvedFiles = files.map((filePath) => ({
|
|
@@ -660,6 +676,7 @@ program
|
|
|
660
676
|
const analytics = new Analytics();
|
|
661
677
|
try {
|
|
662
678
|
const config = getConfig(getProfileName());
|
|
679
|
+
assertWritable(config);
|
|
663
680
|
const client = new ConfluenceClient(config);
|
|
664
681
|
|
|
665
682
|
if (!options.yes) {
|
|
@@ -810,6 +827,7 @@ program
|
|
|
810
827
|
const analytics = new Analytics();
|
|
811
828
|
try {
|
|
812
829
|
const config = getConfig(getProfileName());
|
|
830
|
+
assertWritable(config);
|
|
813
831
|
const client = new ConfluenceClient(config);
|
|
814
832
|
|
|
815
833
|
if (!options.value && !options.file) {
|
|
@@ -866,6 +884,7 @@ program
|
|
|
866
884
|
const analytics = new Analytics();
|
|
867
885
|
try {
|
|
868
886
|
const config = getConfig(getProfileName());
|
|
887
|
+
assertWritable(config);
|
|
869
888
|
const client = new ConfluenceClient(config);
|
|
870
889
|
|
|
871
890
|
if (!options.yes) {
|
|
@@ -1067,6 +1086,7 @@ program
|
|
|
1067
1086
|
let location = null;
|
|
1068
1087
|
try {
|
|
1069
1088
|
const config = getConfig(getProfileName());
|
|
1089
|
+
assertWritable(config);
|
|
1070
1090
|
const client = new ConfluenceClient(config);
|
|
1071
1091
|
|
|
1072
1092
|
let content = '';
|
|
@@ -1181,6 +1201,7 @@ program
|
|
|
1181
1201
|
const analytics = new Analytics();
|
|
1182
1202
|
try {
|
|
1183
1203
|
const config = getConfig(getProfileName());
|
|
1204
|
+
assertWritable(config);
|
|
1184
1205
|
const client = new ConfluenceClient(config);
|
|
1185
1206
|
|
|
1186
1207
|
if (!options.yes) {
|
|
@@ -1599,8 +1620,9 @@ program
|
|
|
1599
1620
|
const analytics = new Analytics();
|
|
1600
1621
|
try {
|
|
1601
1622
|
const config = getConfig(getProfileName());
|
|
1623
|
+
assertWritable(config);
|
|
1602
1624
|
const client = new ConfluenceClient(config);
|
|
1603
|
-
|
|
1625
|
+
|
|
1604
1626
|
// Parse numeric flags with safe fallbacks
|
|
1605
1627
|
const parsedDepth = parseInt(options.maxDepth, 10);
|
|
1606
1628
|
const maxDepth = Number.isNaN(parsedDepth) ? 10 : parsedDepth;
|
|
@@ -1876,7 +1898,8 @@ profileCmd
|
|
|
1876
1898
|
console.log(chalk.blue('Configuration profiles:\n'));
|
|
1877
1899
|
profiles.forEach(p => {
|
|
1878
1900
|
const marker = p.active ? chalk.green(' (active)') : '';
|
|
1879
|
-
|
|
1901
|
+
const readOnlyBadge = p.readOnly ? chalk.red(' [read-only]') : '';
|
|
1902
|
+
console.log(` ${p.active ? chalk.green('*') : ' '} ${chalk.cyan(p.name)}${marker}${readOnlyBadge} - ${chalk.gray(p.domain)}`);
|
|
1880
1903
|
});
|
|
1881
1904
|
});
|
|
1882
1905
|
|
|
@@ -1902,6 +1925,7 @@ profileCmd
|
|
|
1902
1925
|
.option('-a, --auth-type <type>', 'Authentication type (basic or bearer)')
|
|
1903
1926
|
.option('-e, --email <email>', 'Email or username for basic auth')
|
|
1904
1927
|
.option('-t, --token <token>', 'API token')
|
|
1928
|
+
.option('--read-only', 'Set profile to read-only mode (blocks write operations)')
|
|
1905
1929
|
.action(async (name, options) => {
|
|
1906
1930
|
if (!isValidProfileName(name)) {
|
|
1907
1931
|
console.error(chalk.red('Invalid profile name. Use only letters, numbers, hyphens, and underscores.'));
|
|
@@ -1943,6 +1967,7 @@ module.exports = {
|
|
|
1943
1967
|
uniquePathFor,
|
|
1944
1968
|
exportRecursive,
|
|
1945
1969
|
sanitizeTitle,
|
|
1970
|
+
assertWritable,
|
|
1946
1971
|
},
|
|
1947
1972
|
};
|
|
1948
1973
|
|
package/lib/config.js
CHANGED
|
@@ -172,6 +172,10 @@ const saveConfig = (configData, profileName) => {
|
|
|
172
172
|
config.email = configData.email.trim();
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
if (configData.readOnly) {
|
|
176
|
+
config.readOnly = true;
|
|
177
|
+
}
|
|
178
|
+
|
|
175
179
|
// Read existing config file (or create new structure)
|
|
176
180
|
const fileData = readConfigFile() || { activeProfile: DEFAULT_PROFILE, profiles: {} };
|
|
177
181
|
|
|
@@ -297,6 +301,8 @@ async function initConfig(cliOptions = {}) {
|
|
|
297
301
|
process.exit(1);
|
|
298
302
|
}
|
|
299
303
|
|
|
304
|
+
const readOnly = cliOptions.readOnly || false;
|
|
305
|
+
|
|
300
306
|
// Extract provided values from CLI options
|
|
301
307
|
const providedValues = {
|
|
302
308
|
protocol: cliOptions.protocol,
|
|
@@ -375,7 +381,7 @@ async function initConfig(cliOptions = {}) {
|
|
|
375
381
|
}
|
|
376
382
|
]);
|
|
377
383
|
|
|
378
|
-
saveConfig(answers, profileName);
|
|
384
|
+
saveConfig({ ...answers, readOnly }, profileName);
|
|
379
385
|
return;
|
|
380
386
|
}
|
|
381
387
|
|
|
@@ -427,7 +433,8 @@ async function initConfig(cliOptions = {}) {
|
|
|
427
433
|
apiPath: providedValues.apiPath || inferApiPath(normalizedDomain),
|
|
428
434
|
token: providedValues.token,
|
|
429
435
|
authType: normalizedAuthType,
|
|
430
|
-
email: providedValues.email
|
|
436
|
+
email: providedValues.email,
|
|
437
|
+
readOnly
|
|
431
438
|
};
|
|
432
439
|
|
|
433
440
|
saveConfig(configData, profileName);
|
|
@@ -451,7 +458,7 @@ async function initConfig(cliOptions = {}) {
|
|
|
451
458
|
// Normalize auth type
|
|
452
459
|
mergedValues.authType = normalizeAuthType(mergedValues.authType, Boolean(mergedValues.email));
|
|
453
460
|
|
|
454
|
-
saveConfig(mergedValues, profileName);
|
|
461
|
+
saveConfig({ ...mergedValues, readOnly }, profileName);
|
|
455
462
|
} catch (error) {
|
|
456
463
|
console.error(chalk.red(`❌ ${error.message}`));
|
|
457
464
|
process.exit(1);
|
|
@@ -465,6 +472,7 @@ function getConfig(profileName) {
|
|
|
465
472
|
const envAuthType = process.env.CONFLUENCE_AUTH_TYPE;
|
|
466
473
|
const envApiPath = process.env.CONFLUENCE_API_PATH;
|
|
467
474
|
const envProtocol = process.env.CONFLUENCE_PROTOCOL;
|
|
475
|
+
const envReadOnly = process.env.CONFLUENCE_READ_ONLY;
|
|
468
476
|
|
|
469
477
|
if (envDomain && envToken) {
|
|
470
478
|
const authType = normalizeAuthType(envAuthType, Boolean(envEmail));
|
|
@@ -489,7 +497,8 @@ function getConfig(profileName) {
|
|
|
489
497
|
apiPath,
|
|
490
498
|
token: envToken.trim(),
|
|
491
499
|
email: envEmail ? envEmail.trim() : undefined,
|
|
492
|
-
authType
|
|
500
|
+
authType,
|
|
501
|
+
readOnly: envReadOnly === 'true'
|
|
493
502
|
};
|
|
494
503
|
}
|
|
495
504
|
|
|
@@ -547,13 +556,18 @@ function getConfig(profileName) {
|
|
|
547
556
|
process.exit(1);
|
|
548
557
|
}
|
|
549
558
|
|
|
559
|
+
const readOnly = envReadOnly !== undefined
|
|
560
|
+
? envReadOnly === 'true'
|
|
561
|
+
: Boolean(storedConfig.readOnly);
|
|
562
|
+
|
|
550
563
|
return {
|
|
551
564
|
domain: trimmedDomain,
|
|
552
565
|
protocol: normalizeProtocol(storedConfig.protocol),
|
|
553
566
|
apiPath,
|
|
554
567
|
token: trimmedToken,
|
|
555
568
|
email: trimmedEmail,
|
|
556
|
-
authType
|
|
569
|
+
authType,
|
|
570
|
+
readOnly
|
|
557
571
|
};
|
|
558
572
|
} catch (error) {
|
|
559
573
|
console.error(chalk.red('❌ Error reading configuration file:'), error.message);
|
|
@@ -572,7 +586,8 @@ function listProfiles() {
|
|
|
572
586
|
profiles: Object.keys(fileData.profiles).map(name => ({
|
|
573
587
|
name,
|
|
574
588
|
active: name === fileData.activeProfile,
|
|
575
|
-
domain: fileData.profiles[name].domain
|
|
589
|
+
domain: fileData.profiles[name].domain,
|
|
590
|
+
readOnly: Boolean(fileData.profiles[name].readOnly)
|
|
576
591
|
}))
|
|
577
592
|
};
|
|
578
593
|
}
|