@stravica-ai/digital-operator-pa 0.1.39 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/bin/confluence +9 -0
- package/bin/confluence-fetch +9 -0
- package/bin/jira +9 -0
- package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-taxonomy.md +1 -1
- package/dist/engineering-pa-core/knowledge/knowledge-memory/staging-and-observations.md +3 -3
- package/dist/engineering-pa-core/knowledge/platform-tooling/workspace-extensions.md +62 -0
- package/dist/engineering-pa-core-extensions-atlassian/README.md +81 -16
- package/dist/engineering-pa-core-extensions-atlassian/bin/confluence +2 -0
- package/dist/engineering-pa-core-extensions-atlassian/bin/confluence-fetch +2 -0
- package/dist/engineering-pa-core-extensions-atlassian/bin/jira +2 -0
- package/dist/engineering-pa-core-extensions-atlassian/config/jira-workflow-chains.default.json +4 -0
- package/dist/engineering-pa-core-extensions-atlassian/index.mjs +17 -6
- package/dist/engineering-pa-core-extensions-atlassian/package.json +11 -4
- package/dist/engineering-pa-core-extensions-atlassian/src/common/invalidate-client.mjs +137 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/common/pa-name.mjs +58 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/common/secrets.mjs +158 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/cli.mjs +165 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/client.mjs +294 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/auth.mjs +59 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/comment.mjs +174 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/create.mjs +136 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/label.mjs +98 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/mine.mjs +105 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/mirror-folder.mjs +487 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/mirror-page.mjs +351 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/page.mjs +127 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/pages.mjs +107 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/recent.mjs +107 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/search.mjs +123 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/spaces.mjs +82 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/test.mjs +155 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/tree.mjs +103 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/update.mjs +134 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/format.mjs +260 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/mirror.mjs +353 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence/storage.mjs +880 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence-fetch/cli.mjs +313 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/confluence-fetch/fetch.mjs +327 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/adf.mjs +388 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/bug-fields.mjs +294 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/cli.mjs +197 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/client.mjs +350 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/advance.mjs +486 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/attach.mjs +129 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/auth.mjs +53 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/boards.mjs +88 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/bulk-transition.mjs +223 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/cancel.mjs +135 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/comment.mjs +169 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/create.mjs +376 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/issue.mjs +105 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/link.mjs +204 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/mine.mjs +97 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/projects.mjs +91 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/search.mjs +80 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/sprint.mjs +111 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/sync-statuses.mjs +313 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/test.mjs +142 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/transition.mjs +197 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/update.mjs +284 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/worklog.mjs +380 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/format.mjs +267 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/invalidate.mjs +72 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/metadata.mjs +225 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/required-fields.mjs +334 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/workflow-chains.mjs +405 -0
- package/dist/engineering-pa-core-extensions-atlassian/src/jira/workflow-map.mjs +107 -0
- package/dist/engineering-pa-scaffold/README.md +32 -1
- package/dist/engineering-pa-scaffold/src/cli.mjs +83 -6
- package/dist/engineering-pa-scaffold/src/commands/init-workspace-extension.mjs +234 -0
- package/dist/engineering-pa-scaffold/src/commands/init.mjs +6 -5
- package/dist/engineering-pa-scaffold/src/generator.mjs +240 -4
- package/dist/engineering-pa-scaffold/templates/CLAUDE.md.template +7 -7
- package/dist/engineering-pa-scaffold/templates/operator-README.md.template +4 -4
- package/dist/engineering-pa-scaffold/templates/root-CLAUDE.md.template +13 -0
- package/dist/engineering-pa-types-engineering/index.mjs +14 -6
- package/dist/engineering-pa-types-engineering/knowledge/rcf-lite/create-a-small-app.md +81 -0
- package/dist/engineering-pa-types-engineering/knowledge/rcf-lite/for-agents.md +25 -0
- package/dist/engineering-pa-types-engineering/knowledge/rcf-lite/index.md +34 -0
- package/dist/engineering-pa-types-engineering/knowledge/rcf-lite/reference.md +16 -0
- package/dist/engineering-pa-types-engineering/knowledge/rcf-lite/wire-your-agent.md +17 -0
- package/dist/engineering-pa-types-engineering/package.json +2 -2
- package/dist/engineering-pa-types-engineering/roles/rcf-lite-engineer/definition.md +183 -0
- package/dist/engineering-pa-workspace/app/home.html +7 -7
- package/dist/engineering-pa-workspace/app/onboarding.html +4 -4
- package/dist/engineering-pa-workspace/src/api.mjs +1 -1
- package/dist/engineering-pa-workspace/src/server.mjs +1 -1
- package/docs/installing.md +78 -0
- package/docs/onboarding-solo.md +162 -0
- package/docs/what-it-can-do.md +85 -0
- package/package.json +6 -2
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// comment.mjs - Add or list comments on a Confluence page.
|
|
2
|
+
//
|
|
3
|
+
// Usage:
|
|
4
|
+
// confluence comment <ID> <TEXT...> Add a comment
|
|
5
|
+
// confluence comment <ID> --list List comments
|
|
6
|
+
//
|
|
7
|
+
// Uses v1 endpoint for writing comments (v2 POST returns 405).
|
|
8
|
+
// Uses v2 endpoint for reading comments.
|
|
9
|
+
|
|
10
|
+
import { createClient, ConfluenceError } from '../client.mjs';
|
|
11
|
+
import { textToStorage, storageToText } from '../storage.mjs';
|
|
12
|
+
import { formatComment, bold, dim, green } from '../format.mjs';
|
|
13
|
+
|
|
14
|
+
const HELP = `confluence comment - Add or list comments on a page
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
confluence comment <ID> <TEXT> Add a comment to a page
|
|
18
|
+
confluence comment <ID> -- <TEXT> Add a comment (text after --)
|
|
19
|
+
confluence comment <ID> --list List recent comments
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
--list List comments instead of adding one
|
|
23
|
+
--help, -h Show this help
|
|
24
|
+
|
|
25
|
+
Comments are added via the v1 API (v2 POST returns 405).
|
|
26
|
+
Comment text is converted to Confluence storage format.
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
confluence comment 12345 "This page needs updating."
|
|
30
|
+
confluence comment 12345 --list
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse comment command args.
|
|
35
|
+
* Handles -- separator, --list flag, and positional args.
|
|
36
|
+
*/
|
|
37
|
+
function parseCommentArgs(argv) {
|
|
38
|
+
let pageId = null;
|
|
39
|
+
let list = false;
|
|
40
|
+
const textParts = [];
|
|
41
|
+
let afterDash = false;
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < argv.length; i++) {
|
|
44
|
+
if (argv[i] === '--') {
|
|
45
|
+
afterDash = true;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (afterDash) {
|
|
49
|
+
textParts.push(argv[i]);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (argv[i] === '--list') {
|
|
53
|
+
list = true;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (argv[i] === '--help' || argv[i] === '-h') {
|
|
57
|
+
return { pageId: null, text: null, list: false, help: true };
|
|
58
|
+
}
|
|
59
|
+
if (!pageId) {
|
|
60
|
+
pageId = argv[i];
|
|
61
|
+
} else {
|
|
62
|
+
textParts.push(argv[i]);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const text = textParts.length > 0 ? textParts.join(' ') : null;
|
|
67
|
+
return { pageId, text, list, help: false };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Run the comment command.
|
|
72
|
+
*
|
|
73
|
+
* @param {string[]} argv Remaining args after 'comment'
|
|
74
|
+
* @param {object} [deps] Dependency overrides
|
|
75
|
+
* @returns {Promise<number>} Exit code
|
|
76
|
+
*/
|
|
77
|
+
export async function run(argv = [], deps = {}) {
|
|
78
|
+
const log = deps.log || console.log;
|
|
79
|
+
const logError = deps.logError || console.error;
|
|
80
|
+
|
|
81
|
+
const { pageId, text, list, help } = parseCommentArgs(argv);
|
|
82
|
+
|
|
83
|
+
if (help) {
|
|
84
|
+
log(HELP);
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!pageId) {
|
|
89
|
+
logError('Usage: confluence comment <ID> <TEXT>\n confluence comment <ID> --list');
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const client = deps.client || createClient();
|
|
94
|
+
|
|
95
|
+
if (list) {
|
|
96
|
+
return listComments(client, pageId, log, logError);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!text) {
|
|
100
|
+
logError('Usage: confluence comment <ID> <TEXT>\nProvide comment text as arguments or after --');
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return addComment(client, pageId, text, log, logError);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* List comments on a page (via v2 endpoint).
|
|
109
|
+
*/
|
|
110
|
+
async function listComments(client, pageId, log, logError) {
|
|
111
|
+
try {
|
|
112
|
+
const comments = [];
|
|
113
|
+
for await (const comment of client.paginateCursor(
|
|
114
|
+
`/wiki/api/v2/pages/${encodeURIComponent(pageId)}/footer-comments`,
|
|
115
|
+
{ 'body-format': 'storage' }
|
|
116
|
+
)) {
|
|
117
|
+
comments.push(comment);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (comments.length === 0) {
|
|
121
|
+
log(dim(`No comments on page ${pageId}.`));
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
log(bold(`Comments on page ${pageId}`) + dim(` (${comments.length})\n`));
|
|
126
|
+
|
|
127
|
+
for (const comment of comments) {
|
|
128
|
+
// Render storage body to text
|
|
129
|
+
const bodyStorage = comment.body?.storage?.value || '';
|
|
130
|
+
comment._bodyText = storageToText(bodyStorage);
|
|
131
|
+
log(formatComment(comment));
|
|
132
|
+
log('');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return 0;
|
|
136
|
+
} catch (err) {
|
|
137
|
+
if (err instanceof ConfluenceError) {
|
|
138
|
+
logError(`Failed to list comments: ${err.message}`);
|
|
139
|
+
return err.exitCode;
|
|
140
|
+
}
|
|
141
|
+
throw err;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Add a comment to a page (via v1 endpoint - v2 POST returns 405).
|
|
147
|
+
*/
|
|
148
|
+
async function addComment(client, pageId, text, log, logError) {
|
|
149
|
+
try {
|
|
150
|
+
const storageBody = textToStorage(text);
|
|
151
|
+
const { data } = await client.post('/wiki/rest/api/content', {
|
|
152
|
+
type: 'comment',
|
|
153
|
+
container: {
|
|
154
|
+
id: pageId,
|
|
155
|
+
type: 'page',
|
|
156
|
+
},
|
|
157
|
+
body: {
|
|
158
|
+
storage: {
|
|
159
|
+
value: storageBody,
|
|
160
|
+
representation: 'storage',
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
log(green(`Comment added to page ${pageId}.`) + dim(` (id: ${data.id})`));
|
|
166
|
+
return 0;
|
|
167
|
+
} catch (err) {
|
|
168
|
+
if (err instanceof ConfluenceError) {
|
|
169
|
+
logError(`Failed to add comment: ${err.message}`);
|
|
170
|
+
return err.exitCode;
|
|
171
|
+
}
|
|
172
|
+
throw err;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// create.mjs - Create a Confluence page.
|
|
2
|
+
//
|
|
3
|
+
// Usage:
|
|
4
|
+
// confluence create -s SPACE_KEY -t TITLE [--from-file FILE.md] [-b BODY] [--parent ID]
|
|
5
|
+
//
|
|
6
|
+
// If --from-file, reads the markdown file and converts to storage format.
|
|
7
|
+
// If -b, converts inline body text to storage format.
|
|
8
|
+
// Returns page ID + URL.
|
|
9
|
+
|
|
10
|
+
import { createClient, ConfluenceError } from '../client.mjs';
|
|
11
|
+
import { markdownToStorage, textToStorage } from '../storage.mjs';
|
|
12
|
+
import { bold, green } from '../format.mjs';
|
|
13
|
+
import { readFileSync } from 'node:fs';
|
|
14
|
+
|
|
15
|
+
const HELP = `confluence create - Create a new page
|
|
16
|
+
|
|
17
|
+
Usage:
|
|
18
|
+
confluence create -s SPACE_KEY -t TITLE [--from-file FILE.md] [-b BODY] [--parent ID]
|
|
19
|
+
|
|
20
|
+
Options:
|
|
21
|
+
-s SPACE_KEY Space key (required)
|
|
22
|
+
-t TITLE Page title (required)
|
|
23
|
+
--from-file FILE Read markdown content from file
|
|
24
|
+
-b BODY Inline body text (converted to storage format)
|
|
25
|
+
--parent ID Parent page ID (for nesting)
|
|
26
|
+
--help, -h Show this help
|
|
27
|
+
|
|
28
|
+
If --from-file is given, the file is read as markdown and converted to
|
|
29
|
+
Confluence storage format. If -b is given, the text is converted to basic
|
|
30
|
+
storage format. If neither is given, an empty page is created.
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
confluence create -s DEV -t "API Docs" --from-file ./docs/api.md
|
|
34
|
+
confluence create -s DEV -t "Quick Note" -b "This is a quick note."
|
|
35
|
+
confluence create -s DEV -t "Sub Page" --parent 12345
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a space key to a space ID via the v2 API.
|
|
40
|
+
*/
|
|
41
|
+
async function resolveSpaceId(client, spaceKey) {
|
|
42
|
+
const { data } = await client.get(
|
|
43
|
+
`/wiki/api/v2/spaces?keys=${encodeURIComponent(spaceKey)}&limit=1`
|
|
44
|
+
);
|
|
45
|
+
const results = data.results || [];
|
|
46
|
+
if (results.length === 0) {
|
|
47
|
+
throw new ConfluenceError(`Space not found: ${spaceKey}`, 2);
|
|
48
|
+
}
|
|
49
|
+
return results[0].id;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Run the create command.
|
|
54
|
+
*
|
|
55
|
+
* @param {string[]} argv Remaining args after 'create'
|
|
56
|
+
* @param {object} [deps] Dependency overrides
|
|
57
|
+
* @returns {Promise<number>} Exit code
|
|
58
|
+
*/
|
|
59
|
+
export async function run(argv = [], deps = {}) {
|
|
60
|
+
const log = deps.log || console.log;
|
|
61
|
+
const logError = deps.logError || console.error;
|
|
62
|
+
const { parseArgs } = await import('../cli.mjs');
|
|
63
|
+
const args = parseArgs(argv);
|
|
64
|
+
|
|
65
|
+
if (args.help || args.h) {
|
|
66
|
+
log(HELP);
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const spaceKey = args.s || args.space;
|
|
71
|
+
const title = args.t || args.title;
|
|
72
|
+
const fromFile = args['from-file'] || null;
|
|
73
|
+
const bodyText = args.b || args.body || null;
|
|
74
|
+
const parentId = args.parent || null;
|
|
75
|
+
|
|
76
|
+
// Validate required fields
|
|
77
|
+
const missing = [];
|
|
78
|
+
if (!spaceKey) missing.push('-s SPACE_KEY');
|
|
79
|
+
if (!title) missing.push('-t TITLE');
|
|
80
|
+
|
|
81
|
+
if (missing.length > 0) {
|
|
82
|
+
logError(`Missing required fields: ${missing.join(', ')}`);
|
|
83
|
+
logError('Usage: confluence create -s SPACE_KEY -t TITLE [--from-file FILE.md] [-b BODY]');
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const client = deps.client || createClient();
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
// Resolve space key to ID
|
|
91
|
+
const spaceId = await resolveSpaceId(client, spaceKey.toUpperCase());
|
|
92
|
+
|
|
93
|
+
// Build storage body
|
|
94
|
+
let storageBody = '';
|
|
95
|
+
if (fromFile) {
|
|
96
|
+
const readFn = deps.readFileSync || readFileSync;
|
|
97
|
+
const md = readFn(fromFile, 'utf-8');
|
|
98
|
+
storageBody = markdownToStorage(md);
|
|
99
|
+
} else if (bodyText) {
|
|
100
|
+
storageBody = textToStorage(bodyText);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Build page payload (v2 API)
|
|
104
|
+
const payload = {
|
|
105
|
+
spaceId,
|
|
106
|
+
status: 'current',
|
|
107
|
+
title,
|
|
108
|
+
body: {
|
|
109
|
+
representation: 'storage',
|
|
110
|
+
value: storageBody,
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
if (parentId) {
|
|
115
|
+
payload.parentId = parentId;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const { data } = await client.post('/wiki/api/v2/pages', payload);
|
|
119
|
+
const creds = client.getCredentials();
|
|
120
|
+
const webUrl = data._links?.webui
|
|
121
|
+
? `https://${creds.domain}.atlassian.net/wiki${data._links.webui}`
|
|
122
|
+
: `https://${creds.domain}.atlassian.net/wiki/pages/${data.id}`;
|
|
123
|
+
|
|
124
|
+
log(green(`Page created: ${bold(data.title || title)}`));
|
|
125
|
+
log(` ID: ${data.id}`);
|
|
126
|
+
log(` URL: ${webUrl}`);
|
|
127
|
+
|
|
128
|
+
return 0;
|
|
129
|
+
} catch (err) {
|
|
130
|
+
if (err instanceof ConfluenceError) {
|
|
131
|
+
logError(`Failed to create page: ${err.message}`);
|
|
132
|
+
return err.exitCode;
|
|
133
|
+
}
|
|
134
|
+
throw err;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// label.mjs - Add or remove labels on a Confluence page.
|
|
2
|
+
//
|
|
3
|
+
// Usage:
|
|
4
|
+
// confluence label <ID> <LABEL...> Add labels
|
|
5
|
+
// confluence label <ID> <LABEL> --remove Remove a label
|
|
6
|
+
//
|
|
7
|
+
// Uses v1 endpoint for writing labels (v2 POST returns 405).
|
|
8
|
+
// Multiple labels can be space-separated for adding.
|
|
9
|
+
|
|
10
|
+
import { createClient, ConfluenceError } from '../client.mjs';
|
|
11
|
+
import { bold, green, red, dim } from '../format.mjs';
|
|
12
|
+
|
|
13
|
+
const HELP = `confluence label - Add or remove labels on a page
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
confluence label <ID> <LABEL...> Add one or more labels
|
|
17
|
+
confluence label <ID> <LABEL> --remove Remove a label
|
|
18
|
+
|
|
19
|
+
Arguments:
|
|
20
|
+
ID Page ID (numeric)
|
|
21
|
+
LABEL Label name(s), space-separated
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--remove Remove the specified label instead of adding
|
|
25
|
+
--help, -h Show this help
|
|
26
|
+
|
|
27
|
+
Labels are added via the v1 API (v2 POST returns 405).
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
confluence label 12345 important
|
|
31
|
+
confluence label 12345 api docs reviewed
|
|
32
|
+
confluence label 12345 outdated --remove
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Run the label command.
|
|
37
|
+
*
|
|
38
|
+
* @param {string[]} argv Remaining args after 'label'
|
|
39
|
+
* @param {object} [deps] Dependency overrides
|
|
40
|
+
* @returns {Promise<number>} Exit code
|
|
41
|
+
*/
|
|
42
|
+
export async function run(argv = [], deps = {}) {
|
|
43
|
+
const log = deps.log || console.log;
|
|
44
|
+
const logError = deps.logError || console.error;
|
|
45
|
+
const { parseArgs } = await import('../cli.mjs');
|
|
46
|
+
const args = parseArgs(argv);
|
|
47
|
+
|
|
48
|
+
if (args.help || args.h) {
|
|
49
|
+
log(HELP);
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const pageId = args._[0];
|
|
54
|
+
const labels = args._.slice(1);
|
|
55
|
+
const remove = args.remove === true;
|
|
56
|
+
|
|
57
|
+
if (!pageId) {
|
|
58
|
+
logError('Usage: confluence label <ID> <LABEL...> [--remove]');
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (labels.length === 0) {
|
|
63
|
+
logError('Provide at least one label.');
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const client = deps.client || createClient();
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
if (remove) {
|
|
71
|
+
// Remove label - v1 DELETE endpoint
|
|
72
|
+
const label = labels[0]; // Only one label can be removed at a time
|
|
73
|
+
await client.del(
|
|
74
|
+
`/wiki/rest/api/content/${encodeURIComponent(pageId)}/label/${encodeURIComponent(label)}`
|
|
75
|
+
);
|
|
76
|
+
log(green(`Label ${bold(label)} removed from page ${pageId}.`));
|
|
77
|
+
} else {
|
|
78
|
+
// Add labels - v1 POST endpoint
|
|
79
|
+
const payload = labels.map((name) => ({
|
|
80
|
+
prefix: 'global',
|
|
81
|
+
name,
|
|
82
|
+
}));
|
|
83
|
+
await client.post(
|
|
84
|
+
`/wiki/rest/api/content/${encodeURIComponent(pageId)}/label`,
|
|
85
|
+
payload
|
|
86
|
+
);
|
|
87
|
+
log(green(`Labels added to page ${pageId}: ${bold(labels.join(', '))}`));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return 0;
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (err instanceof ConfluenceError) {
|
|
93
|
+
logError(`Failed to ${remove ? 'remove' : 'add'} label: ${err.message}`);
|
|
94
|
+
return err.exitCode;
|
|
95
|
+
}
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// mine.mjs - Current user's Confluence pages.
|
|
2
|
+
//
|
|
3
|
+
// Usage:
|
|
4
|
+
// confluence mine [--days N]
|
|
5
|
+
//
|
|
6
|
+
// Uses CQL: creator = currentUser()
|
|
7
|
+
// Default: 7 days.
|
|
8
|
+
|
|
9
|
+
import { createClient, ConfluenceError } from '../client.mjs';
|
|
10
|
+
import { formatTable, bold, dim } from '../format.mjs';
|
|
11
|
+
|
|
12
|
+
const HELP = `confluence mine - Your pages
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
confluence mine [--days N]
|
|
16
|
+
|
|
17
|
+
Options:
|
|
18
|
+
--days N How many days back to look (default: 7)
|
|
19
|
+
--help, -h Show this help
|
|
20
|
+
|
|
21
|
+
Shows pages created by the current user within the specified time window.
|
|
22
|
+
Uses CQL: creator = currentUser() AND lastModified > now("-Nd")
|
|
23
|
+
|
|
24
|
+
Examples:
|
|
25
|
+
confluence mine
|
|
26
|
+
confluence mine --days 30
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Run the mine command.
|
|
31
|
+
*
|
|
32
|
+
* @param {string[]} argv Remaining args after 'mine'
|
|
33
|
+
* @param {object} [deps] Dependency overrides
|
|
34
|
+
* @returns {Promise<number>} Exit code
|
|
35
|
+
*/
|
|
36
|
+
export async function run(argv = [], deps = {}) {
|
|
37
|
+
const log = deps.log || console.log;
|
|
38
|
+
const logError = deps.logError || console.error;
|
|
39
|
+
const { parseArgs } = await import('../cli.mjs');
|
|
40
|
+
const args = parseArgs(argv);
|
|
41
|
+
|
|
42
|
+
if (args.help || args.h) {
|
|
43
|
+
log(HELP);
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const days = args.days ? parseInt(args.days, 10) : 7;
|
|
48
|
+
|
|
49
|
+
if (isNaN(days) || days < 1) {
|
|
50
|
+
logError('--days must be a positive integer');
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const client = deps.client || createClient();
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const cql = `type = page AND creator = currentUser() AND lastModified > now("-${days}d") ORDER BY lastModified DESC`;
|
|
58
|
+
|
|
59
|
+
const results = [];
|
|
60
|
+
for await (const item of client.paginateOffset('/wiki/rest/api/content/search', {
|
|
61
|
+
cql,
|
|
62
|
+
}, 25)) {
|
|
63
|
+
results.push(item);
|
|
64
|
+
if (results.length >= 25) break;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (results.length === 0) {
|
|
68
|
+
log(dim(`No pages by you in the last ${days} day(s).`));
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
log(bold(`Your pages`) + dim(` (last ${days} days)\n`));
|
|
73
|
+
|
|
74
|
+
const rows = results.map((item) => ({
|
|
75
|
+
id: item.id || '',
|
|
76
|
+
title: item.title || '',
|
|
77
|
+
space: item.space?.key || '',
|
|
78
|
+
modified: item.version?.when ? formatDate(item.version.when) : '',
|
|
79
|
+
}));
|
|
80
|
+
|
|
81
|
+
log(formatTable(rows, [
|
|
82
|
+
{ key: 'id', label: 'ID', width: 12 },
|
|
83
|
+
{ key: 'title', label: 'Title', width: 50 },
|
|
84
|
+
{ key: 'space', label: 'Space', width: 10 },
|
|
85
|
+
{ key: 'modified', label: 'Modified', width: 12 },
|
|
86
|
+
]));
|
|
87
|
+
|
|
88
|
+
return 0;
|
|
89
|
+
} catch (err) {
|
|
90
|
+
if (err instanceof ConfluenceError) {
|
|
91
|
+
logError(`Failed to fetch your pages: ${err.message}`);
|
|
92
|
+
return err.exitCode;
|
|
93
|
+
}
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function formatDate(isoString) {
|
|
99
|
+
if (!isoString) return '';
|
|
100
|
+
try {
|
|
101
|
+
return new Date(isoString).toISOString().slice(0, 10);
|
|
102
|
+
} catch {
|
|
103
|
+
return isoString;
|
|
104
|
+
}
|
|
105
|
+
}
|