logwork-cli 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/LICENSE +11 -0
  2. package/README.md +287 -2
  3. package/RELEASE.md +89 -0
  4. package/bin/logwork.mjs +101 -0
  5. package/commands/auth.mjs +227 -0
  6. package/commands/delete.mjs +69 -0
  7. package/commands/doctor.mjs +24 -0
  8. package/commands/edit.mjs +88 -0
  9. package/commands/history.mjs +62 -0
  10. package/commands/query.mjs +52 -0
  11. package/commands/quick.mjs +163 -0
  12. package/commands/reconcile.mjs +55 -0
  13. package/commands/release.mjs +77 -0
  14. package/commands/reminder.mjs +172 -0
  15. package/commands/setup.mjs +504 -0
  16. package/commands/tui.mjs +50 -0
  17. package/commands/update.mjs +161 -0
  18. package/docs/advanced.md +334 -0
  19. package/docs/mcp-setup.md +347 -0
  20. package/docs/operations-guide.md +587 -0
  21. package/docs/recipes.md +259 -0
  22. package/docs/security.md +151 -0
  23. package/examples/mcp/antigravity.mcp_config.json +8 -0
  24. package/examples/mcp/codex.config.toml +5 -0
  25. package/examples/mcp/copilot.mcp.json +9 -0
  26. package/lib/api-auth.mjs +1259 -0
  27. package/lib/api.mjs +1422 -0
  28. package/lib/apply-ledger.mjs +227 -0
  29. package/lib/atomic-file.mjs +24 -0
  30. package/lib/auth-diagnostics.mjs +113 -0
  31. package/lib/auth-errors.mjs +22 -0
  32. package/lib/auth-redaction.mjs +78 -0
  33. package/lib/auth-terminal.mjs +97 -0
  34. package/lib/auth.mjs +323 -0
  35. package/lib/batch-parser.mjs +145 -0
  36. package/lib/batch-workflow.mjs +842 -0
  37. package/lib/config.mjs +191 -0
  38. package/lib/credential-store.mjs +130 -0
  39. package/lib/diagnostics.mjs +307 -0
  40. package/lib/entrypoint.mjs +14 -0
  41. package/lib/file-lock.mjs +44 -0
  42. package/lib/http.mjs +125 -0
  43. package/lib/jira-api.mjs +258 -0
  44. package/lib/jira-auth.mjs +203 -0
  45. package/lib/jira-workflow.mjs +523 -0
  46. package/lib/keychain.mjs +105 -0
  47. package/lib/legacy-migration.mjs +66 -0
  48. package/lib/list-navigation.mjs +19 -0
  49. package/lib/logwork-config.mjs +199 -0
  50. package/lib/logwork-f03-enrich.mjs +41 -0
  51. package/lib/macos.mjs +33 -0
  52. package/lib/manual-apply-state.mjs +35 -0
  53. package/lib/manual-commands.mjs +294 -0
  54. package/lib/manual-drafts.mjs +154 -0
  55. package/lib/manual-ink-app.mjs +1654 -0
  56. package/lib/manual-ink-auth.mjs +349 -0
  57. package/lib/manual-ink-ui.mjs +675 -0
  58. package/lib/manual-input.mjs +21 -0
  59. package/lib/manual-logwork-wizard.mjs +310 -0
  60. package/lib/manual-mcp.mjs +187 -0
  61. package/lib/manual-repl.mjs +1037 -0
  62. package/lib/manual-targets.mjs +78 -0
  63. package/lib/mcp-server.mjs +793 -0
  64. package/lib/os-notification.mjs +105 -0
  65. package/lib/package-info.mjs +17 -0
  66. package/lib/paths.mjs +68 -0
  67. package/lib/project-identity.mjs +66 -0
  68. package/lib/project-mapping-workflow.mjs +309 -0
  69. package/lib/project-resolver.mjs +373 -0
  70. package/lib/query-workflow.mjs +553 -0
  71. package/lib/reconciliation-workflow.mjs +446 -0
  72. package/lib/release-doctor.mjs +448 -0
  73. package/lib/reminder-config.mjs +71 -0
  74. package/lib/reminder-scheduler.mjs +301 -0
  75. package/lib/reminder-service.mjs +118 -0
  76. package/lib/reminder-state.mjs +38 -0
  77. package/lib/reminder-workflow.mjs +299 -0
  78. package/lib/ro-delete-args.mjs +38 -0
  79. package/lib/ro-delete-workflow.mjs +115 -0
  80. package/lib/ro-edit-args.mjs +142 -0
  81. package/lib/ro-edit-workflow.mjs +632 -0
  82. package/lib/ro-entry-reader.mjs +56 -0
  83. package/lib/ro-resubmit-workflow.mjs +134 -0
  84. package/lib/terminal-auth-prompts.mjs +120 -0
  85. package/lib/ui.mjs +133 -0
  86. package/lib/update-service.mjs +350 -0
  87. package/lib/util.mjs +66 -0
  88. package/lib/windows-credential-manager.mjs +260 -0
  89. package/lib/worklog-task-resolver.mjs +113 -0
  90. package/package.json +72 -4
  91. package/setup.sh +22 -0
@@ -0,0 +1,349 @@
1
+ import React from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ import TextInput from 'ink-text-input';
4
+ import { SelectionList } from './manual-ink-ui.mjs';
5
+ import {
6
+ nextIndex,
7
+ previousIndex
8
+ } from './list-navigation.mjs';
9
+
10
+ const h = React.createElement;
11
+
12
+ export function AuthPrompt({
13
+ prompt,
14
+ inputValue = '',
15
+ onInputChange,
16
+ onPromptChange,
17
+ onResolve,
18
+ onReject
19
+ }) {
20
+ if (!prompt) {
21
+ return null;
22
+ }
23
+
24
+ if (prompt.step === 'credentials') {
25
+ return h(AuthCredentialsPrompt, {
26
+ prompt,
27
+ inputValue,
28
+ onInputChange,
29
+ onPromptChange,
30
+ onResolve,
31
+ onReject
32
+ });
33
+ }
34
+
35
+ if (prompt.step === 'password-update') {
36
+ return h(AuthPasswordUpdatePrompt, {
37
+ prompt,
38
+ inputValue,
39
+ onInputChange,
40
+ onPromptChange,
41
+ onResolve,
42
+ onReject
43
+ });
44
+ }
45
+
46
+ if (prompt.step === 'device') {
47
+ return h(AuthDevicePrompt, {
48
+ prompt,
49
+ onPromptChange,
50
+ onResolve,
51
+ onReject
52
+ });
53
+ }
54
+
55
+ if (prompt.step === 'otp') {
56
+ return h(AuthOtpPrompt, {
57
+ prompt,
58
+ inputValue,
59
+ onInputChange,
60
+ onResolve,
61
+ onReject
62
+ });
63
+ }
64
+
65
+ return h(Box, {
66
+ borderStyle: 'round',
67
+ borderColor: 'cyan',
68
+ paddingX: 1
69
+ }, h(Text, { color: 'cyan' }, 'Authenticating Resource Optimiser...'));
70
+ }
71
+
72
+ export function AuthCredentialsPrompt({
73
+ prompt,
74
+ inputValue = '',
75
+ onInputChange,
76
+ onPromptChange,
77
+ onResolve,
78
+ onReject
79
+ }) {
80
+ useInput((input, key) => {
81
+ if (key.escape) {
82
+ onReject(new Error('Resource Optimiser authentication cancelled.'));
83
+ }
84
+ });
85
+
86
+ const field = prompt.field || 'email';
87
+ const isPassword = field === 'password';
88
+ return h(Box, {
89
+ flexDirection: 'column',
90
+ borderStyle: 'round',
91
+ borderColor: 'cyan',
92
+ paddingX: 1
93
+ },
94
+ h(Text, { color: 'cyan' }, 'Resource Optimiser authentication'),
95
+ h(Text, { color: 'gray' }, 'Secrets stay in memory and are not parsed as commands. Esc cancels.'),
96
+ h(Box, null,
97
+ h(Text, { color: 'cyan' }, isPassword ? 'password › ' : 'email › '),
98
+ h(TextInput, {
99
+ value: inputValue,
100
+ onChange: onInputChange,
101
+ mask: isPassword ? '•' : undefined,
102
+ placeholder: isPassword ? 'Resource Optimiser password' : 'name@example.com',
103
+ onSubmit(value) {
104
+ const nextValue = String(value || '').trim();
105
+ if (!nextValue) {
106
+ return;
107
+ }
108
+ if (!isPassword) {
109
+ onPromptChange({
110
+ ...prompt,
111
+ field: 'password',
112
+ email: nextValue
113
+ });
114
+ onInputChange('');
115
+ return;
116
+ }
117
+ onResolve({
118
+ email: prompt.email,
119
+ password: String(value || '')
120
+ });
121
+ }
122
+ })));
123
+ }
124
+
125
+ export function AuthPasswordUpdatePrompt({
126
+ prompt,
127
+ inputValue = '',
128
+ onInputChange,
129
+ onPromptChange,
130
+ onResolve,
131
+ onReject
132
+ }) {
133
+ useInput((input, key) => {
134
+ if (key.escape) {
135
+ onReject(new Error('Resource Optimiser authentication cancelled.'));
136
+ }
137
+ });
138
+
139
+ const isConfirmation = prompt.field === 'confirmPassword';
140
+ return h(Box, {
141
+ flexDirection: 'column',
142
+ borderStyle: 'round',
143
+ borderColor: 'cyan',
144
+ paddingX: 1
145
+ },
146
+ h(Text, { color: 'cyan' }, 'Update Resource Optimiser password'),
147
+ h(Text, { color: 'gray' }, 'Keycloak requires this change to activate the account. Esc cancels.'),
148
+ prompt.error ? h(Text, { color: 'red' }, prompt.error) : null,
149
+ h(Box, null,
150
+ h(Text, { color: 'cyan' }, isConfirmation ? 'confirm › ' : 'new password › '),
151
+ h(TextInput, {
152
+ value: inputValue,
153
+ onChange: onInputChange,
154
+ mask: '•',
155
+ placeholder: isConfirmation ? 'Confirm new password' : 'New password',
156
+ onSubmit(value) {
157
+ const nextValue = String(value || '');
158
+ if (!nextValue) {
159
+ return;
160
+ }
161
+ if (!isConfirmation) {
162
+ onPromptChange({
163
+ ...prompt,
164
+ field: 'confirmPassword',
165
+ newPassword: nextValue,
166
+ error: null
167
+ });
168
+ onInputChange('');
169
+ return;
170
+ }
171
+ if (nextValue !== prompt.newPassword) {
172
+ onPromptChange({
173
+ ...prompt,
174
+ error: 'Passwords do not match.'
175
+ });
176
+ onInputChange('');
177
+ return;
178
+ }
179
+ onResolve({
180
+ newPassword: prompt.newPassword,
181
+ confirmPassword: nextValue
182
+ });
183
+ }
184
+ })));
185
+ }
186
+
187
+ export function AuthDevicePrompt({
188
+ prompt,
189
+ onPromptChange,
190
+ onResolve,
191
+ onReject
192
+ }) {
193
+ const devices = prompt.devices || [];
194
+ const selectedIndex = prompt.selectedIndex || 0;
195
+
196
+ useInput((input, key) => {
197
+ if (key.escape) {
198
+ onReject(new Error('Resource Optimiser authentication cancelled.'));
199
+ return;
200
+ }
201
+ if (!devices.length) {
202
+ return;
203
+ }
204
+ if (key.upArrow) {
205
+ onPromptChange({
206
+ ...prompt,
207
+ selectedIndex: previousIndex(selectedIndex, devices.length)
208
+ });
209
+ return;
210
+ }
211
+ if (key.downArrow) {
212
+ onPromptChange({
213
+ ...prompt,
214
+ selectedIndex: nextIndex(selectedIndex, devices.length)
215
+ });
216
+ return;
217
+ }
218
+ if (key.return) {
219
+ onResolve(devices[selectedIndex]);
220
+ }
221
+ });
222
+
223
+ return h(SelectionList, {
224
+ title: 'Choose 2FA device · Esc cancel',
225
+ items: devices.map((device, index) => ({
226
+ key: String(device.id ?? device.value ?? index),
227
+ label: device.label || `Device ${index + 1}`
228
+ })),
229
+ selectedIndex
230
+ });
231
+ }
232
+
233
+ export function AuthOtpPrompt({
234
+ inputValue = '',
235
+ onInputChange,
236
+ onResolve,
237
+ onReject
238
+ }) {
239
+ useInput((input, key) => {
240
+ if (key.escape) {
241
+ onReject(new Error('Resource Optimiser authentication cancelled.'));
242
+ }
243
+ });
244
+
245
+ return h(Box, {
246
+ flexDirection: 'column',
247
+ borderStyle: 'round',
248
+ borderColor: 'cyan',
249
+ paddingX: 1
250
+ },
251
+ h(Text, { color: 'cyan' }, 'Enter 2FA code'),
252
+ h(Text, { color: 'gray' }, 'Esc cancels. Code is not stored.'),
253
+ h(Box, null,
254
+ h(Text, { color: 'cyan' }, '2FA › '),
255
+ h(TextInput, {
256
+ value: inputValue,
257
+ onChange: onInputChange,
258
+ mask: '•',
259
+ placeholder: '2FA code',
260
+ onSubmit(value) {
261
+ const otp = String(value || '').trim();
262
+ if (otp) {
263
+ onResolve(otp);
264
+ }
265
+ }
266
+ })));
267
+ }
268
+
269
+ export function createInkCredentialProvider({
270
+ resolverRef,
271
+ rejecterRef,
272
+ setAuthPrompt,
273
+ setActivePanel,
274
+ setInputValue,
275
+ setSelectedIndex
276
+ }) {
277
+ function openPrompt(prompt) {
278
+ setInputValue('');
279
+ setSelectedIndex(0);
280
+ setAuthPrompt(prompt);
281
+ }
282
+
283
+ function waitForPrompt(prompt) {
284
+ return new Promise((resolveValue, reject) => {
285
+ resolverRef.current = resolveValue;
286
+ rejecterRef.current = reject;
287
+ openPrompt(prompt);
288
+ });
289
+ }
290
+
291
+ return {
292
+ async requestCredentials() {
293
+ setActivePanel({
294
+ kind: 'auth',
295
+ title: 'Authentication',
296
+ text: 'Enter Resource Optimiser email and password in the auth panel below.'
297
+ });
298
+ return waitForPrompt({
299
+ step: 'credentials',
300
+ field: 'email',
301
+ email: ''
302
+ });
303
+ },
304
+
305
+ async requestPasswordUpdate() {
306
+ setActivePanel({
307
+ kind: 'auth',
308
+ title: 'Password update required',
309
+ text: 'Keycloak requires a new Resource Optimiser password before login can continue.'
310
+ });
311
+ return waitForPrompt({
312
+ step: 'password-update',
313
+ field: 'newPassword',
314
+ newPassword: '',
315
+ error: null
316
+ });
317
+ },
318
+
319
+ async requestDeviceSelection(devices) {
320
+ if (!Array.isArray(devices) || devices.length === 0) {
321
+ return null;
322
+ }
323
+ if (devices.length === 1) {
324
+ return devices[0];
325
+ }
326
+ setActivePanel({
327
+ kind: 'auth',
328
+ title: 'Authentication',
329
+ text: 'Choose the 2FA device in the auth panel below.'
330
+ });
331
+ return waitForPrompt({
332
+ step: 'device',
333
+ devices,
334
+ selectedIndex: 0
335
+ });
336
+ },
337
+
338
+ async requestOtp() {
339
+ setActivePanel({
340
+ kind: 'auth',
341
+ title: 'Authentication',
342
+ text: 'Enter the 2FA code in the auth panel below.'
343
+ });
344
+ return waitForPrompt({
345
+ step: 'otp'
346
+ });
347
+ }
348
+ };
349
+ }