@geneverse/workctl 0.2.5

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 (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +340 -0
  3. package/dist/packages/capabilities/assistant/src/commands.d.ts +10 -0
  4. package/dist/packages/capabilities/assistant/src/commands.js +33 -0
  5. package/dist/packages/capabilities/assistant/src/commands.js.map +1 -0
  6. package/dist/packages/capabilities/assistant/src/index.d.ts +2 -0
  7. package/dist/packages/capabilities/assistant/src/index.js +3 -0
  8. package/dist/packages/capabilities/assistant/src/index.js.map +1 -0
  9. package/dist/packages/capabilities/assistant/src/status.d.ts +18 -0
  10. package/dist/packages/capabilities/assistant/src/status.js +48 -0
  11. package/dist/packages/capabilities/assistant/src/status.js.map +1 -0
  12. package/dist/packages/capabilities/jenkins/src/commands.d.ts +9 -0
  13. package/dist/packages/capabilities/jenkins/src/commands.js +43 -0
  14. package/dist/packages/capabilities/jenkins/src/commands.js.map +1 -0
  15. package/dist/packages/capabilities/jenkins/src/definition.d.ts +10 -0
  16. package/dist/packages/capabilities/jenkins/src/definition.js +59 -0
  17. package/dist/packages/capabilities/jenkins/src/definition.js.map +1 -0
  18. package/dist/packages/capabilities/jenkins/src/index.d.ts +2 -0
  19. package/dist/packages/capabilities/jenkins/src/index.js +3 -0
  20. package/dist/packages/capabilities/jenkins/src/index.js.map +1 -0
  21. package/dist/packages/capabilities/jira/src/adapters/filesystem/config.store.d.ts +2 -0
  22. package/dist/packages/capabilities/jira/src/adapters/filesystem/config.store.js +43 -0
  23. package/dist/packages/capabilities/jira/src/adapters/filesystem/config.store.js.map +1 -0
  24. package/dist/packages/capabilities/jira/src/adapters/filesystem/local.paths.d.ts +16 -0
  25. package/dist/packages/capabilities/jira/src/adapters/filesystem/local.paths.js +46 -0
  26. package/dist/packages/capabilities/jira/src/adapters/filesystem/local.paths.js.map +1 -0
  27. package/dist/packages/capabilities/jira/src/adapters/filesystem/sync-lock.d.ts +2 -0
  28. package/dist/packages/capabilities/jira/src/adapters/filesystem/sync-lock.js +120 -0
  29. package/dist/packages/capabilities/jira/src/adapters/filesystem/sync-lock.js.map +1 -0
  30. package/dist/packages/capabilities/jira/src/adapters/jira/jira.client.d.ts +31 -0
  31. package/dist/packages/capabilities/jira/src/adapters/jira/jira.client.js +344 -0
  32. package/dist/packages/capabilities/jira/src/adapters/jira/jira.client.js.map +1 -0
  33. package/dist/packages/capabilities/jira/src/adapters/kawari/catalog.d.ts +9 -0
  34. package/dist/packages/capabilities/jira/src/adapters/kawari/catalog.js +17 -0
  35. package/dist/packages/capabilities/jira/src/adapters/kawari/catalog.js.map +1 -0
  36. package/dist/packages/capabilities/jira/src/adapters/kawari/orchestrator.d.ts +14 -0
  37. package/dist/packages/capabilities/jira/src/adapters/kawari/orchestrator.js +116 -0
  38. package/dist/packages/capabilities/jira/src/adapters/kawari/orchestrator.js.map +1 -0
  39. package/dist/packages/capabilities/jira/src/adapters/kawari/scraper.d.ts +32 -0
  40. package/dist/packages/capabilities/jira/src/adapters/kawari/scraper.js +2 -0
  41. package/dist/packages/capabilities/jira/src/adapters/kawari/scraper.js.map +1 -0
  42. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/detect.d.ts +2 -0
  43. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/detect.js +45 -0
  44. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/detect.js.map +1 -0
  45. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/extract.d.ts +3 -0
  46. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/extract.js +115 -0
  47. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/extract.js.map +1 -0
  48. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/index.d.ts +2 -0
  49. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/index.js +27 -0
  50. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/index.js.map +1 -0
  51. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/navigation.d.ts +3 -0
  52. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/navigation.js +104 -0
  53. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/navigation.js.map +1 -0
  54. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/ready.d.ts +2 -0
  55. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/ready.js +47 -0
  56. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/ready.js.map +1 -0
  57. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/shared.d.ts +4 -0
  58. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/shared.js +5 -0
  59. package/dist/packages/capabilities/jira/src/adapters/kawari/v1/shared.js.map +1 -0
  60. package/dist/packages/capabilities/jira/src/adapters/terminal/node-cli.io.d.ts +2 -0
  61. package/dist/packages/capabilities/jira/src/adapters/terminal/node-cli.io.js +40 -0
  62. package/dist/packages/capabilities/jira/src/adapters/terminal/node-cli.io.js.map +1 -0
  63. package/dist/packages/capabilities/jira/src/batch-interaction.d.ts +53 -0
  64. package/dist/packages/capabilities/jira/src/batch-interaction.js +201 -0
  65. package/dist/packages/capabilities/jira/src/batch-interaction.js.map +1 -0
  66. package/dist/packages/capabilities/jira/src/capability.d.ts +16 -0
  67. package/dist/packages/capabilities/jira/src/capability.js +56 -0
  68. package/dist/packages/capabilities/jira/src/capability.js.map +1 -0
  69. package/dist/packages/capabilities/jira/src/cli/contracts.d.ts +42 -0
  70. package/dist/packages/capabilities/jira/src/cli/contracts.js +2 -0
  71. package/dist/packages/capabilities/jira/src/cli/contracts.js.map +1 -0
  72. package/dist/packages/capabilities/jira/src/cli/errors.d.ts +3 -0
  73. package/dist/packages/capabilities/jira/src/cli/errors.js +7 -0
  74. package/dist/packages/capabilities/jira/src/cli/errors.js.map +1 -0
  75. package/dist/packages/capabilities/jira/src/cli/program.d.ts +6 -0
  76. package/dist/packages/capabilities/jira/src/cli/program.js +48 -0
  77. package/dist/packages/capabilities/jira/src/cli/program.js.map +1 -0
  78. package/dist/packages/capabilities/jira/src/cli/run-cli.d.ts +2 -0
  79. package/dist/packages/capabilities/jira/src/cli/run-cli.js +72 -0
  80. package/dist/packages/capabilities/jira/src/cli/run-cli.js.map +1 -0
  81. package/dist/packages/capabilities/jira/src/commands/audit-links/audit-links.command.d.ts +2 -0
  82. package/dist/packages/capabilities/jira/src/commands/audit-links/audit-links.command.js +165 -0
  83. package/dist/packages/capabilities/jira/src/commands/audit-links/audit-links.command.js.map +1 -0
  84. package/dist/packages/capabilities/jira/src/commands/audit-links/audit-links.service.d.ts +30 -0
  85. package/dist/packages/capabilities/jira/src/commands/audit-links/audit-links.service.js +174 -0
  86. package/dist/packages/capabilities/jira/src/commands/audit-links/audit-links.service.js.map +1 -0
  87. package/dist/packages/capabilities/jira/src/commands/command-context.d.ts +10 -0
  88. package/dist/packages/capabilities/jira/src/commands/command-context.js +2 -0
  89. package/dist/packages/capabilities/jira/src/commands/command-context.js.map +1 -0
  90. package/dist/packages/capabilities/jira/src/commands/config-show/config-show.command.d.ts +2 -0
  91. package/dist/packages/capabilities/jira/src/commands/config-show/config-show.command.js +26 -0
  92. package/dist/packages/capabilities/jira/src/commands/config-show/config-show.command.js.map +1 -0
  93. package/dist/packages/capabilities/jira/src/commands/configure/configure.command.d.ts +2 -0
  94. package/dist/packages/capabilities/jira/src/commands/configure/configure.command.js +26 -0
  95. package/dist/packages/capabilities/jira/src/commands/configure/configure.command.js.map +1 -0
  96. package/dist/packages/capabilities/jira/src/commands/configure/configure.service.d.ts +42 -0
  97. package/dist/packages/capabilities/jira/src/commands/configure/configure.service.js +205 -0
  98. package/dist/packages/capabilities/jira/src/commands/configure/configure.service.js.map +1 -0
  99. package/dist/packages/capabilities/jira/src/commands/doctor/doctor.command.d.ts +2 -0
  100. package/dist/packages/capabilities/jira/src/commands/doctor/doctor.command.js +17 -0
  101. package/dist/packages/capabilities/jira/src/commands/doctor/doctor.command.js.map +1 -0
  102. package/dist/packages/capabilities/jira/src/commands/doctor/doctor.service.d.ts +18 -0
  103. package/dist/packages/capabilities/jira/src/commands/doctor/doctor.service.js +84 -0
  104. package/dist/packages/capabilities/jira/src/commands/doctor/doctor.service.js.map +1 -0
  105. package/dist/packages/capabilities/jira/src/commands/rollback/rollback.command.d.ts +2 -0
  106. package/dist/packages/capabilities/jira/src/commands/rollback/rollback.command.js +198 -0
  107. package/dist/packages/capabilities/jira/src/commands/rollback/rollback.command.js.map +1 -0
  108. package/dist/packages/capabilities/jira/src/commands/rollback/rollback.service.d.ts +37 -0
  109. package/dist/packages/capabilities/jira/src/commands/rollback/rollback.service.js +112 -0
  110. package/dist/packages/capabilities/jira/src/commands/rollback/rollback.service.js.map +1 -0
  111. package/dist/packages/capabilities/jira/src/commands/sync/audit-export.service.d.ts +18 -0
  112. package/dist/packages/capabilities/jira/src/commands/sync/audit-export.service.js +96 -0
  113. package/dist/packages/capabilities/jira/src/commands/sync/audit-export.service.js.map +1 -0
  114. package/dist/packages/capabilities/jira/src/commands/sync/preview.service.d.ts +23 -0
  115. package/dist/packages/capabilities/jira/src/commands/sync/preview.service.js +201 -0
  116. package/dist/packages/capabilities/jira/src/commands/sync/preview.service.js.map +1 -0
  117. package/dist/packages/capabilities/jira/src/commands/sync/sync.command.d.ts +2 -0
  118. package/dist/packages/capabilities/jira/src/commands/sync/sync.command.js +537 -0
  119. package/dist/packages/capabilities/jira/src/commands/sync/sync.command.js.map +1 -0
  120. package/dist/packages/capabilities/jira/src/commands/sync/verification.service.d.ts +34 -0
  121. package/dist/packages/capabilities/jira/src/commands/sync/verification.service.js +164 -0
  122. package/dist/packages/capabilities/jira/src/commands/sync/verification.service.js.map +1 -0
  123. package/dist/packages/capabilities/jira/src/config.d.ts +37 -0
  124. package/dist/packages/capabilities/jira/src/config.js +217 -0
  125. package/dist/packages/capabilities/jira/src/config.js.map +1 -0
  126. package/dist/packages/capabilities/jira/src/dependencies.d.ts +2 -0
  127. package/dist/packages/capabilities/jira/src/dependencies.js +92 -0
  128. package/dist/packages/capabilities/jira/src/dependencies.js.map +1 -0
  129. package/dist/packages/capabilities/jira/src/domain/date-range.d.ts +10 -0
  130. package/dist/packages/capabilities/jira/src/domain/date-range.js +63 -0
  131. package/dist/packages/capabilities/jira/src/domain/date-range.js.map +1 -0
  132. package/dist/packages/capabilities/jira/src/domain/marker.d.ts +20 -0
  133. package/dist/packages/capabilities/jira/src/domain/marker.js +100 -0
  134. package/dist/packages/capabilities/jira/src/domain/marker.js.map +1 -0
  135. package/dist/packages/capabilities/jira/src/domain/timesheet-entry.d.ts +45 -0
  136. package/dist/packages/capabilities/jira/src/domain/timesheet-entry.js +117 -0
  137. package/dist/packages/capabilities/jira/src/domain/timesheet-entry.js.map +1 -0
  138. package/dist/packages/capabilities/jira/src/index.d.ts +5 -0
  139. package/dist/packages/capabilities/jira/src/index.js +4 -0
  140. package/dist/packages/capabilities/jira/src/index.js.map +1 -0
  141. package/dist/packages/capabilities/jira/src/ports/jira.d.ts +49 -0
  142. package/dist/packages/capabilities/jira/src/ports/jira.js +11 -0
  143. package/dist/packages/capabilities/jira/src/ports/jira.js.map +1 -0
  144. package/dist/packages/capabilities/jira/src/ports/sync-lock.d.ts +6 -0
  145. package/dist/packages/capabilities/jira/src/ports/sync-lock.js +7 -0
  146. package/dist/packages/capabilities/jira/src/ports/sync-lock.js.map +1 -0
  147. package/dist/packages/capabilities/jira/src/ports/timesheet.d.ts +11 -0
  148. package/dist/packages/capabilities/jira/src/ports/timesheet.js +7 -0
  149. package/dist/packages/capabilities/jira/src/ports/timesheet.js.map +1 -0
  150. package/dist/packages/capabilities/jira/src/presentation.d.ts +37 -0
  151. package/dist/packages/capabilities/jira/src/presentation.js +111 -0
  152. package/dist/packages/capabilities/jira/src/presentation.js.map +1 -0
  153. package/dist/packages/cli/src/bin.d.ts +2 -0
  154. package/dist/packages/cli/src/bin.js +4 -0
  155. package/dist/packages/cli/src/bin.js.map +1 -0
  156. package/dist/packages/cli/src/commands/browsers/browsers.command.d.ts +7 -0
  157. package/dist/packages/cli/src/commands/browsers/browsers.command.js +22 -0
  158. package/dist/packages/cli/src/commands/browsers/browsers.command.js.map +1 -0
  159. package/dist/packages/cli/src/commands/browsers/browsers.service.d.ts +16 -0
  160. package/dist/packages/cli/src/commands/browsers/browsers.service.js +37 -0
  161. package/dist/packages/cli/src/commands/browsers/browsers.service.js.map +1 -0
  162. package/dist/packages/cli/src/commands/setup/setup.command.d.ts +4 -0
  163. package/dist/packages/cli/src/commands/setup/setup.command.js +41 -0
  164. package/dist/packages/cli/src/commands/setup/setup.command.js.map +1 -0
  165. package/dist/packages/cli/src/commands/setup/setup.service.d.ts +23 -0
  166. package/dist/packages/cli/src/commands/setup/setup.service.js +32 -0
  167. package/dist/packages/cli/src/commands/setup/setup.service.js.map +1 -0
  168. package/dist/packages/cli/src/commands/update/update.command.d.ts +7 -0
  169. package/dist/packages/cli/src/commands/update/update.command.js +34 -0
  170. package/dist/packages/cli/src/commands/update/update.command.js.map +1 -0
  171. package/dist/packages/cli/src/commands/update/update.service.d.ts +35 -0
  172. package/dist/packages/cli/src/commands/update/update.service.js +134 -0
  173. package/dist/packages/cli/src/commands/update/update.service.js.map +1 -0
  174. package/dist/packages/cli/src/index.d.ts +1 -0
  175. package/dist/packages/cli/src/index.js +2 -0
  176. package/dist/packages/cli/src/index.js.map +1 -0
  177. package/dist/packages/cli/src/program.d.ts +21 -0
  178. package/dist/packages/cli/src/program.js +74 -0
  179. package/dist/packages/cli/src/program.js.map +1 -0
  180. package/dist/packages/cli/src/release-version.d.ts +1 -0
  181. package/dist/packages/cli/src/release-version.js +2 -0
  182. package/package.json +57 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Geneverse
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,340 @@
1
+ # workctl
2
+
3
+ Deterministic internal developer workflows in one CLI. The Time Sync capability
4
+ is available through `workctl jira`.
5
+
6
+ ```bash
7
+ workctl jira sync
8
+ workctl update
9
+ workctl setup
10
+ workctl jenkins job create --help
11
+ workctl assistant status
12
+ ```
13
+
14
+ The npm package contains the compiled CLI, this README, and the MIT license.
15
+ Project planning and architecture notes live in the source repository and are
16
+ not included in the published package.
17
+
18
+ ## jira capability
19
+
20
+ Deterministic local CLI for synchronizing Kawari timesheets to Jira Cloud.
21
+ No double-entry — scrape your timesheet, review what will be created, and
22
+ approve once.
23
+
24
+ ## Quick Start
25
+
26
+ **Prerequisites:** Node.js ≥22, a Jira Cloud API token, and access to your
27
+ Kawari timesheet.
28
+
29
+ ```bash
30
+ npm install -g @geneverse/workctl
31
+ workctl browsers install
32
+ workctl setup
33
+ ```
34
+
35
+ The onboarding hub always lets you select setup-capable capabilities and
36
+ requires at least one selection. jira is selected by default; compact
37
+ onboarding asks only for Jira email and API token, supplies the company
38
+ defaults, verifies the result, and writes a private user-global configuration
39
+ file. Re-running setup skips configuration that is already valid and verifies
40
+ it again.
41
+
42
+ ## Configuration
43
+
44
+ Run `workctl jira configure` to review the current configuration and select
45
+ only the sections you want to edit: Jira credentials, service defaults, or
46
+ Link Status Audit. On macOS and Linux, configuration is stored at
47
+ `~/.config/workctl/config.json`; on Windows, it is stored under
48
+ `%APPDATA%\workctl\config.json`. POSIX directories and files use private
49
+ permissions. The Jira token is stored as plain text in that file, so use
50
+ workctl only on a trusted personal workstation.
51
+
52
+ | Config field | Environment override | Description |
53
+ |---|---|---|
54
+ | `jiraBaseUrl` | `JIRA_BASE_URL` | Jira Cloud HTTPS origin |
55
+ | `jiraEmail` | `JIRA_EMAIL` | Your Jira account email |
56
+ | `jiraApiToken` | `JIRA_API_TOKEN` | Jira Cloud API token ([create one here](https://id.atlassian.com/manage-profile/security/api-tokens)) |
57
+ | `timesheetUrl` | `TIMESHEET_URL` | Full URL to the Kawari timesheet |
58
+ | `timezone` | `TZ` | Must be `Asia/Bangkok` |
59
+ | `linkAudit.projects` | — | Optional Jira project map with `doneStatuses` for `audit-links` |
60
+
61
+ Real process environment variables override the config file field by field.
62
+ workctl does not load `.env` files. `NO_COLOR=1` disables colored output.
63
+
64
+ Use `workctl jira config show` to inspect effective values and their source. The token
65
+ is always redacted. Use `workctl jira doctor` to check permissions, Jira credentials,
66
+ and the Playwright Chromium installation.
67
+
68
+ ## Commands
69
+
70
+ ```
71
+ workctl jira sync [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--export csv|json]
72
+ workctl jira preview [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--export csv|json]
73
+ workctl jira verify [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--export csv|json]
74
+ workctl jira rollback [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--issue KEY] [--dry-run]
75
+ workctl jira audit-links [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--export csv|json]
76
+ workctl jira configure
77
+ workctl jira config show
78
+ workctl jira doctor
79
+ workctl browsers install
80
+ workctl update
81
+ ```
82
+
83
+ `workctl setup` selects, compactly configures, and verifies capabilities.
84
+ `workctl jira configure` is the full editor for Jira-specific settings.
85
+
86
+ | Command | Writes to Jira? | Purpose |
87
+ |---|---|---|
88
+ | `sync` | Yes (with approval) | Full pipeline: scrape, preview, approve, create, verify |
89
+ | `preview` | No | Read-only: scrape and show what would happen |
90
+ | `verify` | No | Compare eligible entries against existing tool-managed Jira worklogs |
91
+ | `rollback` | Yes (typed approval) | Delete matching tool-managed worklogs and restore their time to Remaining Estimate |
92
+ | `audit-links` | No | Audit linked issue statuses for Jira issues found in Kawari |
93
+ | `browsers install` | No | Install the Playwright Chromium browser required by Kawari scraping |
94
+ | `update` | No | Install the latest stable `@geneverse/workctl` release from npm after approval |
95
+
96
+ ### Rolling back a sync
97
+
98
+ `workctl jira rollback` reads the selected Kawari entries and previews only Jira
99
+ worklogs carrying matching `jlog:v1` Source Identities. The worklog must belong
100
+ to the current Jira account. Type the displayed `delete N` phrase to proceed,
101
+ or use `--dry-run` for a read-only check.
102
+
103
+ Each deletion sends `adjustEstimate=auto`, so deleting a 2h worklog from an
104
+ issue with 3h remaining restores Remaining Estimate to 5h. The Jira capability does not set
105
+ Original Estimate or an absolute remaining value. Use repeatable
106
+ `--issue OLD-1` options if a source entry's Jira key changed before rollback.
107
+ Once deletion begins, the Jira capability writes a private JSON receipt under
108
+ the local workctl data directory.
109
+
110
+ **Date range:** interactive `workctl jira sync`, `workctl jira rollback`, and `workctl jira audit-links`
111
+ ask whether to use
112
+ the current calendar month in `Asia/Bangkok` (showing its inclusive dates) or
113
+ enter custom `From` and `To` dates. The current month is selected by default.
114
+ `preview`, `verify`, and non-interactive commands continue to use the current
115
+ month when dates are omitted. Explicit ranges bypass the prompt and must fall
116
+ within one calendar month.
117
+
118
+ ### Auditing linked issue statuses
119
+
120
+ `workctl jira audit-links` reads Jira keys from Kawari for the selected date range. It
121
+ checks links only for source issues whose current status is configured as done
122
+ for that Jira project. Every unique linked pair is reported as `Mismatch` or
123
+ `Aligned`; completed source issues without links are reported as `No Links`.
124
+ Link type and direction are displayed exactly as Jira returns them.
125
+
126
+ Configure the Link Audit Scope with `workctl jira configure`. The wizard reads
127
+ accessible projects and their statuses from Jira, then lets you select the
128
+ Done Statuses for each project:
129
+
130
+ ```json
131
+ {
132
+ "version": 1,
133
+ "capabilities": {
134
+ "jira": {
135
+ "linkAudit": {
136
+ "projects": {
137
+ "ABC": {
138
+ "doneStatuses": ["DONE", "READY TO DEPLOY"]
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+ }
145
+ ```
146
+
147
+ Status comparison ignores surrounding whitespace and letter case. This scope
148
+ does not restrict worklog sync, preview, or verification. Source issues outside
149
+ the scope are skipped and summarized. Cross-project targets use their own
150
+ configured Done Statuses; targets outside the scope are reported as
151
+ `Not Configured`. Invalid Kawari entries and incomplete Jira responses still
152
+ block the audit. Mismatches do not produce a failing exit code, and the command
153
+ never changes Jira. Use `--export csv|json` to retain the complete report,
154
+ including skipped sources and unconfigured targets.
155
+
156
+ Interactive command output uses one guided terminal presentation for prompts,
157
+ progress, results, and errors. `--help`, `--version`, and non-interactive output
158
+ remain plain text for piping and automation.
159
+
160
+ **Audit export** (`--export`): writes a timestamped CSV or JSON file to the
161
+ local workctl data directory for your records.
162
+
163
+ ### Updating workctl
164
+
165
+ Run `workctl update` to check the npm registry for `@geneverse/workctl`. If a
166
+ newer stable version exists, workctl shows the target package, requires you to
167
+ type `yes`, and runs `npm install --global @geneverse/workctl@<version>` without
168
+ `sudo`. This command does not require Jira or Kawari configuration.
169
+
170
+ You can also update directly with npm:
171
+
172
+ ```bash
173
+ npm update -g @geneverse/workctl
174
+ ```
175
+
176
+ ## How It Works
177
+
178
+ ### Pipeline
179
+
180
+ Each `sync` runs these steps:
181
+
182
+ 1. **Resolve date range** — interactive `sync` offers the displayed current
183
+ month or a custom range before opening Kawari
184
+ 2. **Scrape Kawari** — launches a browser; prompts for manual login if the
185
+ session expired
186
+ 3. **Classify** — each hours cell becomes a Timesheet Entry and is classified as:
187
+ - **Eligible** — exactly one Jira issue key, ready to sync
188
+ - **Unmapped** — no Jira key (e.g. internal meetings), skipped with a warning
189
+ - **Invalid** — bad data, multiple keys, or ambiguous identity
190
+ 4. **Read Jira and repair keys** — fetches existing worklogs to check for prior
191
+ syncs. If a Jira issue does not exist, an interactive run can replace that
192
+ key for the current batch and validate the replacement immediately.
193
+ 5. **Preview** — shows what will be created, skipped, or flagged as a conflict
194
+ 6. **Approve** — interactive confirmation before any writes
195
+ 7. **Create worklogs** — each new worklog gets a `jlog:v1:` marker in its
196
+ comment
197
+ 8. **Verify** — re-reads Jira to confirm every expected worklog exists
198
+
199
+ `audit-links` shares date selection, Kawari classification, and temporary
200
+ missing-key repair, but reads Jira link metadata through a separate read-only
201
+ pipeline and never creates or transitions issues.
202
+
203
+ ### Safety Gates
204
+
205
+ - **Invalid entries** and **sync conflicts** (a previously-synced entry whose
206
+ values changed) block the entire batch. Nothing is written until all issues
207
+ are resolved.
208
+ - A Jira key correction is a **Batch Override**. It applies to every affected
209
+ entry in the current command only; it does not edit Kawari or persist a local
210
+ mapping. Non-interactive runs fail with repair guidance instead.
211
+ - The approval prompt shows exact counts and hours before you commit.
212
+
213
+ ### Idempotency
214
+
215
+ Every worklog created by the Jira capability contains a legacy `jlog:v1:` marker — a base64url-encoded
216
+ snapshot of the source entry. On re-runs, entries with a matching marker are
217
+ skipped automatically. The marker survives local state loss and prevents
218
+ duplicates across machines.
219
+
220
+ ## Example
221
+
222
+ ```
223
+ $ workctl jira sync --from 2026-01-05 --to 2026-01-09
224
+
225
+ ──────────────────────────────────────────────────
226
+ Sync Batch: 2026-01-05 → 2026-01-09
227
+ ──────────────────────────────────────────────────
228
+
229
+ ╔═══════════════════════════════════════════════════════╗
230
+ ║ PREVIEW ║
231
+ ╠═══════════════════════════════════════════════════════╣
232
+ ║ Create 3 worklog(s) 12.0h ║
233
+ ║ Skip 1 worklog(s) 2.0h ║
234
+ ╠═══════════════════════════════════════════════════════╣
235
+ ║ Total eligible 14.0h ║
236
+ ╚═══════════════════════════════════════════════════════╝
237
+
238
+ Create 3 Jira worklog(s) totaling 12h? Yes
239
+
240
+ ◇ Ownership rechecked
241
+ │ ╭───────────────────────────────────────────────────╮
242
+ │ │ PROGRESS DATE ISSUE TIME STATUS │
243
+ │ ├───────────────────────────────────────────────────┤
244
+ ✓ │ 1/3 2026-01-07 PROJ-123 4h Created │
245
+ ✓ │ 2/3 2026-01-08 PROJ-456 4h Created │
246
+ ✓ │ 3/3 2026-01-09 PROJ-789 4h Created │
247
+ │ ╰───────────────────────────────────────────────────╯
248
+ ● Created 3 (12h) · Failed 0 (0h)
249
+
250
+ Verified: 3 of 3 expected worklogs found on Jira.
251
+ ```
252
+
253
+ ## Development
254
+
255
+ **Prerequisites:** Node.js ≥22, npm, a Jira Cloud API token, and access to the
256
+ Kawari timesheet.
257
+
258
+ For a fresh clone, bootstrap the project once. This installs npm dependencies
259
+ and Playwright Chromium, then opens the onboarding hub:
260
+
261
+ ```bash
262
+ npm run bootstrap
263
+ ```
264
+
265
+ Run the CLI directly from TypeScript without building or linking it:
266
+
267
+ ```bash
268
+ npm run dev -- jira sync
269
+ npm run dev -- jira preview --from 2026-07-01 --to 2026-07-04
270
+ ```
271
+
272
+ The compiled workflow remains available when testing the packaged executable:
273
+
274
+ ```bash
275
+ npm ci
276
+ npm run build
277
+ npm link
278
+ workctl jira sync
279
+ ```
280
+
281
+ Development checks:
282
+
283
+ ```bash
284
+ npm test # run tests (vitest)
285
+ npm run typecheck # type-check only, no emit
286
+ npm run build # compile TypeScript → dist/
287
+ npm run test:package # pack and install the CLI in a temporary npm prefix
288
+ ```
289
+
290
+ ### Releasing
291
+
292
+ Start from a clean `main` branch, then run the development checks before
293
+ creating a patch release:
294
+
295
+ ```bash
296
+ npm test
297
+ npm run typecheck
298
+ npm run build
299
+ npm run test:package
300
+ npm version patch -m "chore(release): %s"
301
+ git push origin main --follow-tags
302
+ ```
303
+
304
+ `npm version` updates `package.json` and `package-lock.json`, creates the
305
+ release commit, and tags it as `v<version>`. The tag workflow publishes
306
+ `@geneverse/workctl` to the npm registry with public access.
307
+
308
+ ### Project Structure
309
+
310
+ ```
311
+ packages/
312
+ ├── cli/ workctl composition root and binary
313
+ └── capabilities/
314
+ ├── jira/ Kawari-to-Jira time synchronization
315
+ ├── jenkins/ Git-backed Jenkins definitions
316
+ └── assistant/ vendor CLI installation status
317
+ ```
318
+
319
+ Each capability owns its commands, dependencies, implementation, tests, and
320
+ domain glossary. Capabilities are built into one release and cannot import one
321
+ another. The CLI imports only their public entrypoints.
322
+
323
+ ### Local State
324
+
325
+ Runtime data is user-global. On macOS and Linux it lives under
326
+ `~/.local/share/workctl/jira/`; on Windows it lives under
327
+ `%LOCALAPPDATA%\workctl\jira\`:
328
+
329
+ - `browser-profile/` — persistent Playwright Chromium session
330
+ - `audit-exports/` — timestamped CSV/JSON exports
331
+ - `state/` — sync state
332
+
333
+ Run `workctl setup` after installation; the first sync will require Kawari
334
+ login.
335
+
336
+ Uninstall the CLI with npm:
337
+
338
+ ```bash
339
+ npm uninstall -g @geneverse/workctl
340
+ ```
@@ -0,0 +1,10 @@
1
+ import type { Command } from "commander";
2
+ import { type AssistantStatus } from "./status.js";
3
+ export interface AssistantCommandIO {
4
+ stdout(message: string): void;
5
+ }
6
+ export interface AssistantDependencies {
7
+ statuses(): Promise<readonly AssistantStatus[]>;
8
+ }
9
+ export declare function createAssistantDependencies(): AssistantDependencies;
10
+ export declare function registerAssistantCommands(program: Command, io: AssistantCommandIO, dependencies?: AssistantDependencies): void;
@@ -0,0 +1,33 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { assistantStatuses } from "./status.js";
4
+ const execFileAsync = promisify(execFile);
5
+ export function createAssistantDependencies() {
6
+ return {
7
+ statuses: () => assistantStatuses(async (command) => {
8
+ try {
9
+ await execFileAsync("which", [command]);
10
+ return true;
11
+ }
12
+ catch {
13
+ return false;
14
+ }
15
+ }),
16
+ };
17
+ }
18
+ export function registerAssistantCommands(program, io, dependencies = createAssistantDependencies()) {
19
+ program.command("assistant").description("inspect supported assistant installations")
20
+ .command("status")
21
+ .action(async () => {
22
+ for (const status of await dependencies.statuses()) {
23
+ io.stdout(`${status.displayName} (${status.command}): ` +
24
+ `${status.installed ? "installed" : "not installed"}\n`);
25
+ if (status.installed) {
26
+ io.stdout(` Sign in directly with the vendor CLI: ${status.loginCommand}\n`);
27
+ }
28
+ io.stdout(` Vendor documentation: ${status.documentationUrl}\n`);
29
+ }
30
+ io.stdout("workctl does not run login commands or read, store, configure, or receipt provider credentials.\n");
31
+ });
32
+ }
33
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../../../packages/capabilities/assistant/src/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,iBAAiB,EAAwB,MAAM,aAAa,CAAC;AAUtE,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C,MAAM,UAAU,2BAA2B;IACzC,OAAO;QACL,QAAQ,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;YAClD,IAAI,CAAC;gBACH,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;gBACxC,OAAO,IAAI,CAAC;YACd,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,KAAK,CAAC;YACf,CAAC;QACH,CAAC,CAAC;KACH,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,OAAgB,EAChB,EAAsB,EACtB,eAAsC,2BAA2B,EAAE;IAEnE,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC,WAAW,CAAC,2CAA2C,CAAC;SAClF,OAAO,CAAC,QAAQ,CAAC;SACjB,MAAM,CAAC,KAAK,IAAI,EAAE;QACjB,KAAK,MAAM,MAAM,IAAI,MAAM,YAAY,CAAC,QAAQ,EAAE,EAAE,CAAC;YACnD,EAAE,CAAC,MAAM,CACP,GAAG,MAAM,CAAC,WAAW,KAAK,MAAM,CAAC,OAAO,KAAK;gBAC7C,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,eAAe,IAAI,CACxD,CAAC;YACF,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;gBACrB,EAAE,CAAC,MAAM,CAAC,2CAA2C,MAAM,CAAC,YAAY,IAAI,CAAC,CAAC;YAChF,CAAC;YACD,EAAE,CAAC,MAAM,CAAC,2BAA2B,MAAM,CAAC,gBAAgB,IAAI,CAAC,CAAC;QACpE,CAAC;QACD,EAAE,CAAC,MAAM,CACP,mGAAmG,CACpG,CAAC;IACJ,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from "./commands.js";
2
+ export * from "./status.js";
@@ -0,0 +1,3 @@
1
+ export * from "./commands.js";
2
+ export * from "./status.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/capabilities/assistant/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC"}
@@ -0,0 +1,18 @@
1
+ export type AssistantName = "cursor" | "codex" | "claude";
2
+ export interface AssistantDefinition {
3
+ name: AssistantName;
4
+ displayName: string;
5
+ commands: readonly string[];
6
+ loginCommand: string;
7
+ documentationUrl: string;
8
+ }
9
+ export interface AssistantStatus {
10
+ name: AssistantName;
11
+ displayName: string;
12
+ command: string;
13
+ installed: boolean;
14
+ loginCommand: string;
15
+ documentationUrl: string;
16
+ }
17
+ export declare const supportedAssistants: readonly AssistantDefinition[];
18
+ export declare function assistantStatuses(exists?: (command: string) => Promise<boolean>): Promise<readonly AssistantStatus[]>;
@@ -0,0 +1,48 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execFileAsync = promisify(execFile);
4
+ export const supportedAssistants = [
5
+ {
6
+ name: "cursor",
7
+ displayName: "Cursor Agent",
8
+ commands: ["cursor-agent"],
9
+ loginCommand: "cursor-agent login",
10
+ documentationUrl: "https://docs.cursor.com/en/cli/reference/authentication",
11
+ },
12
+ {
13
+ name: "codex",
14
+ displayName: "OpenAI Codex",
15
+ commands: ["codex"],
16
+ loginCommand: "codex login",
17
+ documentationUrl: "https://developers.openai.com/codex/auth/",
18
+ },
19
+ {
20
+ name: "claude",
21
+ displayName: "Claude Code",
22
+ commands: ["claude"],
23
+ loginCommand: "claude",
24
+ documentationUrl: "https://docs.anthropic.com/en/docs/claude-code/getting-started",
25
+ },
26
+ ];
27
+ export async function assistantStatuses(exists = async (command) => {
28
+ try {
29
+ await execFileAsync("which", [command]);
30
+ return true;
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ }) {
36
+ return Promise.all(supportedAssistants.map(async (assistant) => {
37
+ const command = assistant.commands[0];
38
+ return {
39
+ name: assistant.name,
40
+ displayName: assistant.displayName,
41
+ command,
42
+ installed: await exists(command),
43
+ loginCommand: assistant.loginCommand,
44
+ documentationUrl: assistant.documentationUrl,
45
+ };
46
+ }));
47
+ }
48
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../../../../packages/capabilities/assistant/src/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAqB1C,MAAM,CAAC,MAAM,mBAAmB,GAAmC;IACjE;QACE,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,cAAc;QAC3B,QAAQ,EAAE,CAAC,cAAc,CAAC;QAC1B,YAAY,EAAE,oBAAoB;QAClC,gBAAgB,EAAE,yDAAyD;KAC5E;IACD;QACE,IAAI,EAAE,OAAO;QACb,WAAW,EAAE,cAAc;QAC3B,QAAQ,EAAE,CAAC,OAAO,CAAC;QACnB,YAAY,EAAE,aAAa;QAC3B,gBAAgB,EAAE,2CAA2C;KAC9D;IACD;QACE,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,aAAa;QAC1B,QAAQ,EAAE,CAAC,QAAQ,CAAC;QACpB,YAAY,EAAE,QAAQ;QACtB,gBAAgB,EAAE,gEAAgE;KACnF;CACF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAgD,KAAK,EAAE,OAAO,EAAE,EAAE;IAChE,IAAI,CAAC;QACH,MAAM,aAAa,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QACxC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;IAED,OAAO,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE;QAC7D,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC;QACvC,OAAO;YACL,IAAI,EAAE,SAAS,CAAC,IAAI;YACpB,WAAW,EAAE,SAAS,CAAC,WAAW;YAClC,OAAO;YACP,SAAS,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC;YAChC,YAAY,EAAE,SAAS,CAAC,YAAY;YACpC,gBAAgB,EAAE,SAAS,CAAC,gBAAgB;SAC7C,CAAC;IACJ,CAAC,CAAC,CAAC,CAAC;AACN,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { Command } from "commander";
2
+ export interface JenkinsCommandIO {
3
+ stdout(message: string): void;
4
+ }
5
+ export interface JenkinsDependencies {
6
+ writeFile(file: string, content: string): Promise<void>;
7
+ }
8
+ export declare function createJenkinsDependencies(): JenkinsDependencies;
9
+ export declare function registerJenkinsCommands(program: Command, io: JenkinsCommandIO, dependencies?: JenkinsDependencies): void;
@@ -0,0 +1,43 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createJenkinsJobDefinition, jenkinsDefinitionPath, } from "./definition.js";
4
+ export function createJenkinsDependencies() {
5
+ return {
6
+ writeFile: async (file, content) => {
7
+ await mkdir(path.dirname(file), { recursive: true });
8
+ await writeFile(file, content, { flag: "wx", mode: 0o644 });
9
+ },
10
+ };
11
+ }
12
+ export function registerJenkinsCommands(program, io, dependencies = createJenkinsDependencies()) {
13
+ const job = program.command("jenkins").description("manage Git-backed Jenkins definitions")
14
+ .command("job").description("manage Jenkins jobs");
15
+ job.command("create")
16
+ .requiredOption("--name <name>")
17
+ .requiredOption("--repo <url>")
18
+ .option("--branch <branch>", "SCM branch", "main")
19
+ .requiredOption("--target-host <host>")
20
+ .requiredOption("--credential-id <id>")
21
+ .option("--output <directory>", "definition directory", "jenkins/jobs")
22
+ .option("--write", "write the reviewed definition; otherwise preview only")
23
+ .action(async (options) => {
24
+ const input = {
25
+ name: options.name,
26
+ repositoryUrl: options.repo,
27
+ branch: options.branch,
28
+ targetHost: options.targetHost,
29
+ credentialId: options.credentialId,
30
+ };
31
+ const definition = createJenkinsJobDefinition(input);
32
+ const destination = jenkinsDefinitionPath(options.output, options.name);
33
+ io.stdout(`${definition}\nDestination: ${destination}\n`);
34
+ if (options.write) {
35
+ await dependencies.writeFile(destination, definition);
36
+ io.stdout("Definition written. Review and commit it through the normal Git workflow.\n");
37
+ }
38
+ else {
39
+ io.stdout("Preview only; pass --write to create the file.\n");
40
+ }
41
+ });
42
+ }
43
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../../../../../packages/capabilities/jenkins/src/commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,GAEtB,MAAM,iBAAiB,CAAC;AAUzB,MAAM,UAAU,yBAAyB;IACvC,OAAO;QACL,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;YACjC,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACrD,MAAM,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9D,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,OAAgB,EAChB,EAAoB,EACpB,eAAoC,yBAAyB,EAAE;IAE/D,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,WAAW,CAAC,uCAAuC,CAAC;SACxF,OAAO,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,qBAAqB,CAAC,CAAC;IACrD,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC;SAClB,cAAc,CAAC,eAAe,CAAC;SAC/B,cAAc,CAAC,cAAc,CAAC;SAC9B,MAAM,CAAC,mBAAmB,EAAE,YAAY,EAAE,MAAM,CAAC;SACjD,cAAc,CAAC,sBAAsB,CAAC;SACtC,cAAc,CAAC,sBAAsB,CAAC;SACtC,MAAM,CAAC,sBAAsB,EAAE,sBAAsB,EAAE,cAAc,CAAC;SACtE,MAAM,CAAC,SAAS,EAAE,uDAAuD,CAAC;SAC1E,MAAM,CAAC,KAAK,EAAE,OAQd,EAAE,EAAE;QACH,MAAM,KAAK,GAAoB;YAC7B,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,aAAa,EAAE,OAAO,CAAC,IAAI;YAC3B,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,UAAU,EAAE,OAAO,CAAC,UAAU;YAC9B,YAAY,EAAE,OAAO,CAAC,YAAY;SACnC,CAAC;QACF,MAAM,UAAU,GAAG,0BAA0B,CAAC,KAAK,CAAC,CAAC;QACrD,MAAM,WAAW,GAAG,qBAAqB,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;QACxE,EAAE,CAAC,MAAM,CAAC,GAAG,UAAU,kBAAkB,WAAW,IAAI,CAAC,CAAC;QAC1D,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,MAAM,YAAY,CAAC,SAAS,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;YACtD,EAAE,CAAC,MAAM,CAAC,6EAA6E,CAAC,CAAC;QAC3F,CAAC;aAAM,CAAC;YACN,EAAE,CAAC,MAAM,CAAC,kDAAkD,CAAC,CAAC;QAChE,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,10 @@
1
+ export interface JenkinsJobInput {
2
+ name: string;
3
+ repositoryUrl: string;
4
+ branch: string;
5
+ targetHost: string;
6
+ credentialId: string;
7
+ }
8
+ export declare function validateJenkinsJobInput(input: JenkinsJobInput): void;
9
+ export declare function createJenkinsJobDefinition(input: JenkinsJobInput): string;
10
+ export declare function jenkinsDefinitionPath(outputDirectory: string, name: string): string;
@@ -0,0 +1,59 @@
1
+ import path from "node:path";
2
+ const SAFE_NAME = /^[a-z0-9][a-z0-9._-]*$/i;
3
+ const SAFE_CREDENTIAL_ID = /^[a-z0-9][a-z0-9._-]*$/i;
4
+ function xml(value) {
5
+ return value
6
+ .replaceAll("&", "&amp;")
7
+ .replaceAll("<", "&lt;")
8
+ .replaceAll(">", "&gt;")
9
+ .replaceAll('"', "&quot;")
10
+ .replaceAll("'", "&apos;");
11
+ }
12
+ export function validateJenkinsJobInput(input) {
13
+ if (!SAFE_NAME.test(input.name))
14
+ throw new Error("Job name contains unsupported characters.");
15
+ if (!/^https:\/\/[^/\s]+\/.+/.test(input.repositoryUrl) && !/^git@[^:\s]+:.+/.test(input.repositoryUrl)) {
16
+ throw new Error("Repository URL must use HTTPS or Git SSH syntax.");
17
+ }
18
+ if (!input.branch.trim())
19
+ throw new Error("Branch is required.");
20
+ if (!/^[a-z0-9][a-z0-9.-]*$/i.test(input.targetHost)) {
21
+ throw new Error("Target host must be a hostname or IPv4 address without credentials.");
22
+ }
23
+ if (!SAFE_CREDENTIAL_ID.test(input.credentialId)) {
24
+ throw new Error("Credential ID contains unsupported characters.");
25
+ }
26
+ }
27
+ export function createJenkinsJobDefinition(input) {
28
+ validateJenkinsJobInput(input);
29
+ return `<?xml version="1.0" encoding="UTF-8"?>
30
+ <flow-definition plugin="workflow-job">
31
+ <description>Managed by workctl. Do not edit in Jenkins.</description>
32
+ <keepDependencies>false</keepDependencies>
33
+ <properties/>
34
+ <definition class="org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition" plugin="workflow-cps">
35
+ <scm class="hudson.plugins.git.GitSCM" plugin="git">
36
+ <configVersion>2</configVersion>
37
+ <userRemoteConfigs>
38
+ <hudson.plugins.git.UserRemoteConfig>
39
+ <url>${xml(input.repositoryUrl)}</url>
40
+ <credentialsId>${xml(input.credentialId)}</credentialsId>
41
+ </hudson.plugins.git.UserRemoteConfig>
42
+ </userRemoteConfigs>
43
+ <branches><hudson.plugins.git.BranchSpec><name>*/${xml(input.branch)}</name></hudson.plugins.git.BranchSpec></branches>
44
+ </scm>
45
+ <scriptPath>Jenkinsfile</scriptPath>
46
+ <lightweight>true</lightweight>
47
+ </definition>
48
+ <disabled>false</disabled>
49
+ <actions/>
50
+ <workctlTargetHost>${xml(input.targetHost)}</workctlTargetHost>
51
+ </flow-definition>
52
+ `;
53
+ }
54
+ export function jenkinsDefinitionPath(outputDirectory, name) {
55
+ if (!SAFE_NAME.test(name))
56
+ throw new Error("Job name contains unsupported characters.");
57
+ return path.join(outputDirectory, `${name}.xml`);
58
+ }
59
+ //# sourceMappingURL=definition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"definition.js","sourceRoot":"","sources":["../../../../../packages/capabilities/jenkins/src/definition.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAU7B,MAAM,SAAS,GAAG,yBAAyB,CAAC;AAC5C,MAAM,kBAAkB,GAAG,yBAAyB,CAAC;AAErD,SAAS,GAAG,CAAC,KAAa;IACxB,OAAO,KAAK;SACT,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;SACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC;SACzB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAsB;IAC5D,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC9F,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;QACxG,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACtE,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;IACjE,IAAI,CAAC,wBAAwB,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,qEAAqE,CAAC,CAAC;IACzF,CAAC;IACD,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;IACpE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,KAAsB;IAC/D,uBAAuB,CAAC,KAAK,CAAC,CAAC;IAC/B,OAAO;;;;;;;;;;iBAUQ,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC;2BACd,GAAG,CAAC,KAAK,CAAC,YAAY,CAAC;;;yDAGO,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC;;;;;;;uBAOnD,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC;;CAE3C,CAAC;AACF,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,eAAuB,EAAE,IAAY;IACzE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IACxF,OAAO,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from "./commands.js";
2
+ export * from "./definition.js";
@@ -0,0 +1,3 @@
1
+ export * from "./commands.js";
2
+ export * from "./definition.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/capabilities/jenkins/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { AppConfig } from "../../config.js";
2
+ export declare function writeConfigAtomic(configPath: string, config: AppConfig): Promise<void>;