@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.
Files changed (92) hide show
  1. package/README.md +12 -2
  2. package/bin/confluence +9 -0
  3. package/bin/confluence-fetch +9 -0
  4. package/bin/jira +9 -0
  5. package/dist/engineering-pa-core/knowledge/content-voice/banned-ai-tells-taxonomy.md +1 -1
  6. package/dist/engineering-pa-core/knowledge/knowledge-memory/staging-and-observations.md +3 -3
  7. package/dist/engineering-pa-core/knowledge/platform-tooling/workspace-extensions.md +62 -0
  8. package/dist/engineering-pa-core-extensions-atlassian/README.md +81 -16
  9. package/dist/engineering-pa-core-extensions-atlassian/bin/confluence +2 -0
  10. package/dist/engineering-pa-core-extensions-atlassian/bin/confluence-fetch +2 -0
  11. package/dist/engineering-pa-core-extensions-atlassian/bin/jira +2 -0
  12. package/dist/engineering-pa-core-extensions-atlassian/config/jira-workflow-chains.default.json +4 -0
  13. package/dist/engineering-pa-core-extensions-atlassian/index.mjs +17 -6
  14. package/dist/engineering-pa-core-extensions-atlassian/package.json +11 -4
  15. package/dist/engineering-pa-core-extensions-atlassian/src/common/invalidate-client.mjs +137 -0
  16. package/dist/engineering-pa-core-extensions-atlassian/src/common/pa-name.mjs +58 -0
  17. package/dist/engineering-pa-core-extensions-atlassian/src/common/secrets.mjs +158 -0
  18. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/cli.mjs +165 -0
  19. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/client.mjs +294 -0
  20. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/auth.mjs +59 -0
  21. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/comment.mjs +174 -0
  22. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/create.mjs +136 -0
  23. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/label.mjs +98 -0
  24. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/mine.mjs +105 -0
  25. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/mirror-folder.mjs +487 -0
  26. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/mirror-page.mjs +351 -0
  27. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/page.mjs +127 -0
  28. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/pages.mjs +107 -0
  29. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/recent.mjs +107 -0
  30. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/search.mjs +123 -0
  31. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/spaces.mjs +82 -0
  32. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/test.mjs +155 -0
  33. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/tree.mjs +103 -0
  34. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/commands/update.mjs +134 -0
  35. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/format.mjs +260 -0
  36. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/mirror.mjs +353 -0
  37. package/dist/engineering-pa-core-extensions-atlassian/src/confluence/storage.mjs +880 -0
  38. package/dist/engineering-pa-core-extensions-atlassian/src/confluence-fetch/cli.mjs +313 -0
  39. package/dist/engineering-pa-core-extensions-atlassian/src/confluence-fetch/fetch.mjs +327 -0
  40. package/dist/engineering-pa-core-extensions-atlassian/src/jira/adf.mjs +388 -0
  41. package/dist/engineering-pa-core-extensions-atlassian/src/jira/bug-fields.mjs +294 -0
  42. package/dist/engineering-pa-core-extensions-atlassian/src/jira/cli.mjs +197 -0
  43. package/dist/engineering-pa-core-extensions-atlassian/src/jira/client.mjs +350 -0
  44. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/advance.mjs +486 -0
  45. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/attach.mjs +129 -0
  46. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/auth.mjs +53 -0
  47. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/boards.mjs +88 -0
  48. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/bulk-transition.mjs +223 -0
  49. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/cancel.mjs +135 -0
  50. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/comment.mjs +169 -0
  51. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/create.mjs +376 -0
  52. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/issue.mjs +105 -0
  53. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/link.mjs +204 -0
  54. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/mine.mjs +97 -0
  55. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/projects.mjs +91 -0
  56. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/search.mjs +80 -0
  57. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/sprint.mjs +111 -0
  58. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/sync-statuses.mjs +313 -0
  59. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/test.mjs +142 -0
  60. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/transition.mjs +197 -0
  61. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/update.mjs +284 -0
  62. package/dist/engineering-pa-core-extensions-atlassian/src/jira/commands/worklog.mjs +380 -0
  63. package/dist/engineering-pa-core-extensions-atlassian/src/jira/format.mjs +267 -0
  64. package/dist/engineering-pa-core-extensions-atlassian/src/jira/invalidate.mjs +72 -0
  65. package/dist/engineering-pa-core-extensions-atlassian/src/jira/metadata.mjs +225 -0
  66. package/dist/engineering-pa-core-extensions-atlassian/src/jira/required-fields.mjs +334 -0
  67. package/dist/engineering-pa-core-extensions-atlassian/src/jira/workflow-chains.mjs +405 -0
  68. package/dist/engineering-pa-core-extensions-atlassian/src/jira/workflow-map.mjs +107 -0
  69. package/dist/engineering-pa-scaffold/README.md +32 -1
  70. package/dist/engineering-pa-scaffold/src/cli.mjs +83 -6
  71. package/dist/engineering-pa-scaffold/src/commands/init-workspace-extension.mjs +234 -0
  72. package/dist/engineering-pa-scaffold/src/commands/init.mjs +6 -5
  73. package/dist/engineering-pa-scaffold/src/generator.mjs +240 -4
  74. package/dist/engineering-pa-scaffold/templates/CLAUDE.md.template +7 -7
  75. package/dist/engineering-pa-scaffold/templates/operator-README.md.template +4 -4
  76. package/dist/engineering-pa-scaffold/templates/root-CLAUDE.md.template +13 -0
  77. package/dist/engineering-pa-types-engineering/index.mjs +14 -6
  78. package/dist/engineering-pa-types-engineering/knowledge/rcf-lite/create-a-small-app.md +81 -0
  79. package/dist/engineering-pa-types-engineering/knowledge/rcf-lite/for-agents.md +25 -0
  80. package/dist/engineering-pa-types-engineering/knowledge/rcf-lite/index.md +34 -0
  81. package/dist/engineering-pa-types-engineering/knowledge/rcf-lite/reference.md +16 -0
  82. package/dist/engineering-pa-types-engineering/knowledge/rcf-lite/wire-your-agent.md +17 -0
  83. package/dist/engineering-pa-types-engineering/package.json +2 -2
  84. package/dist/engineering-pa-types-engineering/roles/rcf-lite-engineer/definition.md +183 -0
  85. package/dist/engineering-pa-workspace/app/home.html +7 -7
  86. package/dist/engineering-pa-workspace/app/onboarding.html +4 -4
  87. package/dist/engineering-pa-workspace/src/api.mjs +1 -1
  88. package/dist/engineering-pa-workspace/src/server.mjs +1 -1
  89. package/docs/installing.md +78 -0
  90. package/docs/onboarding-solo.md +162 -0
  91. package/docs/what-it-can-do.md +85 -0
  92. 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
+ }