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.
- package/LICENSE +11 -0
- package/README.md +287 -2
- package/RELEASE.md +89 -0
- package/bin/logwork.mjs +101 -0
- package/commands/auth.mjs +227 -0
- package/commands/delete.mjs +69 -0
- package/commands/doctor.mjs +24 -0
- package/commands/edit.mjs +88 -0
- package/commands/history.mjs +62 -0
- package/commands/query.mjs +52 -0
- package/commands/quick.mjs +163 -0
- package/commands/reconcile.mjs +55 -0
- package/commands/release.mjs +77 -0
- package/commands/reminder.mjs +172 -0
- package/commands/setup.mjs +504 -0
- package/commands/tui.mjs +50 -0
- package/commands/update.mjs +161 -0
- package/docs/advanced.md +334 -0
- package/docs/mcp-setup.md +347 -0
- package/docs/operations-guide.md +587 -0
- package/docs/recipes.md +259 -0
- package/docs/security.md +151 -0
- package/examples/mcp/antigravity.mcp_config.json +8 -0
- package/examples/mcp/codex.config.toml +5 -0
- package/examples/mcp/copilot.mcp.json +9 -0
- package/lib/api-auth.mjs +1259 -0
- package/lib/api.mjs +1422 -0
- package/lib/apply-ledger.mjs +227 -0
- package/lib/atomic-file.mjs +24 -0
- package/lib/auth-diagnostics.mjs +113 -0
- package/lib/auth-errors.mjs +22 -0
- package/lib/auth-redaction.mjs +78 -0
- package/lib/auth-terminal.mjs +97 -0
- package/lib/auth.mjs +323 -0
- package/lib/batch-parser.mjs +145 -0
- package/lib/batch-workflow.mjs +842 -0
- package/lib/config.mjs +191 -0
- package/lib/credential-store.mjs +130 -0
- package/lib/diagnostics.mjs +307 -0
- package/lib/entrypoint.mjs +14 -0
- package/lib/file-lock.mjs +44 -0
- package/lib/http.mjs +125 -0
- package/lib/jira-api.mjs +258 -0
- package/lib/jira-auth.mjs +203 -0
- package/lib/jira-workflow.mjs +523 -0
- package/lib/keychain.mjs +105 -0
- package/lib/legacy-migration.mjs +66 -0
- package/lib/list-navigation.mjs +19 -0
- package/lib/logwork-config.mjs +199 -0
- package/lib/logwork-f03-enrich.mjs +41 -0
- package/lib/macos.mjs +33 -0
- package/lib/manual-apply-state.mjs +35 -0
- package/lib/manual-commands.mjs +294 -0
- package/lib/manual-drafts.mjs +154 -0
- package/lib/manual-ink-app.mjs +1654 -0
- package/lib/manual-ink-auth.mjs +349 -0
- package/lib/manual-ink-ui.mjs +675 -0
- package/lib/manual-input.mjs +21 -0
- package/lib/manual-logwork-wizard.mjs +310 -0
- package/lib/manual-mcp.mjs +187 -0
- package/lib/manual-repl.mjs +1037 -0
- package/lib/manual-targets.mjs +78 -0
- package/lib/mcp-server.mjs +793 -0
- package/lib/os-notification.mjs +105 -0
- package/lib/package-info.mjs +17 -0
- package/lib/paths.mjs +68 -0
- package/lib/project-identity.mjs +66 -0
- package/lib/project-mapping-workflow.mjs +309 -0
- package/lib/project-resolver.mjs +373 -0
- package/lib/query-workflow.mjs +553 -0
- package/lib/reconciliation-workflow.mjs +446 -0
- package/lib/release-doctor.mjs +448 -0
- package/lib/reminder-config.mjs +71 -0
- package/lib/reminder-scheduler.mjs +301 -0
- package/lib/reminder-service.mjs +118 -0
- package/lib/reminder-state.mjs +38 -0
- package/lib/reminder-workflow.mjs +299 -0
- package/lib/ro-delete-args.mjs +38 -0
- package/lib/ro-delete-workflow.mjs +115 -0
- package/lib/ro-edit-args.mjs +142 -0
- package/lib/ro-edit-workflow.mjs +632 -0
- package/lib/ro-entry-reader.mjs +56 -0
- package/lib/ro-resubmit-workflow.mjs +134 -0
- package/lib/terminal-auth-prompts.mjs +120 -0
- package/lib/ui.mjs +133 -0
- package/lib/update-service.mjs +350 -0
- package/lib/util.mjs +66 -0
- package/lib/windows-credential-manager.mjs +260 -0
- package/lib/worklog-task-resolver.mjs +113 -0
- package/package.json +72 -4
- 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
|
+
}
|