logwork-cli 1.1.0 → 1.2.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 (70) hide show
  1. package/README.md +35 -9
  2. package/RELEASE.md +1 -0
  3. package/commands/commits.mjs +57 -6
  4. package/commands/history.mjs +0 -4
  5. package/commands/quick.mjs +38 -23
  6. package/commands/reminder.mjs +41 -2
  7. package/commands/setup.mjs +157 -19
  8. package/commands/tui.mjs +1 -0
  9. package/docs/advanced.md +12 -2
  10. package/docs/mcp-setup.md +57 -15
  11. package/docs/operations-guide.md +37 -2
  12. package/docs/recipes.md +27 -0
  13. package/docs/security.md +3 -1
  14. package/examples/mcp/antigravity.mcp_config.json +1 -1
  15. package/examples/mcp/codex.config.toml +1 -1
  16. package/examples/mcp/copilot.mcp.json +1 -1
  17. package/lib/api-auth-forms.mjs +393 -0
  18. package/lib/api-auth-http.mjs +184 -0
  19. package/lib/api-auth-support.mjs +101 -0
  20. package/lib/api-auth-tokens.mjs +129 -0
  21. package/lib/api-auth.mjs +39 -813
  22. package/lib/api.mjs +38 -1422
  23. package/lib/apply-ledger.mjs +1 -1
  24. package/lib/batch-parser.mjs +23 -0
  25. package/lib/batch-workflow.mjs +201 -2
  26. package/lib/commit-workflow.mjs +278 -30
  27. package/lib/config.mjs +7 -0
  28. package/lib/diagnostics.mjs +53 -4
  29. package/lib/jira-api.mjs +25 -3
  30. package/lib/jira-workflow.mjs +36 -6
  31. package/lib/keychain.mjs +19 -12
  32. package/lib/logwork-config.mjs +64 -13
  33. package/lib/manual-apply-state.mjs +15 -0
  34. package/lib/manual-commands.mjs +2 -2
  35. package/lib/manual-ink-app.mjs +108 -1281
  36. package/lib/manual-ink-draft-actions.mjs +227 -0
  37. package/lib/manual-ink-interaction.mjs +306 -0
  38. package/lib/manual-ink-session.mjs +41 -0
  39. package/lib/manual-ink-task-editor.mjs +338 -0
  40. package/lib/manual-ink-wizard.mjs +520 -0
  41. package/lib/manual-mcp.mjs +27 -15
  42. package/lib/manual-repl.mjs +39 -3
  43. package/lib/mcp-server.mjs +63 -21
  44. package/lib/node-runtime.mjs +115 -0
  45. package/lib/npm-command.mjs +29 -0
  46. package/lib/paths.mjs +11 -1
  47. package/lib/project-mapping-workflow.mjs +30 -7
  48. package/lib/query-workflow.mjs +0 -1
  49. package/lib/reconciliation-workflow.mjs +1 -1
  50. package/lib/release-doctor.mjs +4 -2
  51. package/lib/reminder-config.mjs +38 -2
  52. package/lib/reminder-scheduler.mjs +54 -3
  53. package/lib/reminder-service.mjs +28 -3
  54. package/lib/reminder-workflow.mjs +118 -22
  55. package/lib/ro-api-diagnostics.mjs +109 -0
  56. package/lib/ro-api-entries.mjs +359 -0
  57. package/lib/ro-api-fields.mjs +290 -0
  58. package/lib/ro-api-http.mjs +68 -0
  59. package/lib/ro-api-projects.mjs +137 -0
  60. package/lib/ro-api-tasks.mjs +50 -0
  61. package/lib/ro-api-timesheet.mjs +413 -0
  62. package/lib/ro-delete-workflow.mjs +5 -8
  63. package/lib/ro-edit-workflow.mjs +66 -8
  64. package/lib/ro-entry-reader.mjs +49 -6
  65. package/lib/ro-resubmit-workflow.mjs +35 -21
  66. package/lib/terminal-auth-prompts.mjs +7 -4
  67. package/lib/ui.mjs +80 -59
  68. package/lib/update-service.mjs +5 -2
  69. package/package.json +11 -4
  70. package/skills/logwork/SKILL.md +6 -4
@@ -0,0 +1,338 @@
1
+ // Task editor actions for the Ink logwork wizard: task lines, slash commands,
2
+ // edit/replace and multi-select remove.
3
+ import { useCallback } from 'react';
4
+ import {
5
+ parseTaskLine,
6
+ removeDraftTasks,
7
+ replaceDraftTask
8
+ } from './manual-logwork-wizard.mjs';
9
+ import { JIRA_TARGET } from './manual-targets.mjs';
10
+ import { formatTaskHours } from './manual-ink-ui.mjs';
11
+ import { clearSessionPreviews } from './manual-ink-session.mjs';
12
+
13
+ export function useTaskEditorActions(ctx) {
14
+ const {
15
+ sessionRef,
16
+ wizard,
17
+ setWizard,
18
+ setActivePanel,
19
+ setInputValue,
20
+ prompts,
21
+ executeCommand,
22
+ previewDraft,
23
+ applyWizardPreview,
24
+ cancelCurrentLogwork,
25
+ restoreTaskEditorPanel,
26
+ saveCurrentDraft,
27
+ openDraftPicker
28
+ } = ctx;
29
+
30
+ const cancelRemoveTasks = useCallback(() => {
31
+ setWizard((current) => ({
32
+ ...current,
33
+ step: 'edit_tasks',
34
+ removeIndex: 0,
35
+ removeSelectedIndexes: []
36
+ }));
37
+ restoreTaskEditorPanel();
38
+ }, [restoreTaskEditorPanel]);
39
+
40
+ const confirmRemoveTasks = useCallback(async (selectedIndexes) => {
41
+ if (!selectedIndexes.length) {
42
+ setActivePanel({
43
+ kind: 'logwork',
44
+ title: 'Remove Tasks',
45
+ text: 'No tasks selected. Use Space to select tasks, Enter to remove, Esc to cancel.'
46
+ });
47
+ return;
48
+ }
49
+
50
+ const ok = await prompts.confirm(`Remove ${selectedIndexes.length} selected task${selectedIndexes.length === 1 ? '' : 's'}?`);
51
+ if (!ok) {
52
+ cancelRemoveTasks();
53
+ return;
54
+ }
55
+
56
+ await previewDraft({
57
+ ...wizard,
58
+ step: 'edit_tasks',
59
+ tasks: removeDraftTasks(wizard.tasks, selectedIndexes),
60
+ preview: null,
61
+ removeIndex: 0,
62
+ removeSelectedIndexes: []
63
+ });
64
+ }, [cancelRemoveTasks, previewDraft, prompts, wizard]);
65
+
66
+ const submitWizardTaskInput = useCallback(async (value) => {
67
+ const text = String(value || '').trim();
68
+ setInputValue('');
69
+
70
+ if (!text) {
71
+ await applyWizardPreview();
72
+ return;
73
+ }
74
+
75
+ if (text === '/diagnostics') {
76
+ await executeCommand({ type: 'diagnostics' });
77
+ return;
78
+ }
79
+
80
+ if (text === '/cancel' || text === '/discard') {
81
+ await cancelCurrentLogwork();
82
+ return;
83
+ }
84
+
85
+ if (text === '/back') {
86
+ sessionRef.current.logworkDraft = null;
87
+ clearSessionPreviews(sessionRef.current);
88
+ if (wizard.target === JIRA_TARGET) {
89
+ setWizard((current) => ({
90
+ ...current,
91
+ step: 'select_date',
92
+ selectedDate: null,
93
+ tasks: [],
94
+ preview: null,
95
+ previews: {}
96
+ }));
97
+ setActivePanel({
98
+ kind: 'logwork',
99
+ title: 'Select Date',
100
+ text: 'Choose a day for this Jira worklog session.'
101
+ });
102
+ } else {
103
+ setWizard((current) => ({
104
+ ...current,
105
+ step: 'select_project',
106
+ selectedProject: null,
107
+ tasks: [],
108
+ preview: null,
109
+ previews: {}
110
+ }));
111
+ setActivePanel({
112
+ kind: 'logwork',
113
+ title: 'Select Project',
114
+ text: `Choose a project for ${wizard.selectedDate}.`
115
+ });
116
+ }
117
+ return;
118
+ }
119
+
120
+ if (text === '/clear') {
121
+ const ok = !wizard.tasks.length || await prompts.confirm('Clear all current tasks?', false);
122
+ if (!ok) {
123
+ restoreTaskEditorPanel();
124
+ return;
125
+ }
126
+ await previewDraft({
127
+ ...wizard,
128
+ tasks: [],
129
+ preview: null
130
+ });
131
+ return;
132
+ }
133
+
134
+ if (text === '/save') {
135
+ await saveCurrentDraft();
136
+ return;
137
+ }
138
+
139
+ if (text === '/drafts') {
140
+ await openDraftPicker('editor');
141
+ return;
142
+ }
143
+
144
+ if (text === '/edit') {
145
+ if (!wizard.tasks.length) {
146
+ setActivePanel({
147
+ kind: 'error',
148
+ title: 'Edit Task',
149
+ text: 'No tasks to edit.'
150
+ });
151
+ return;
152
+ }
153
+ setWizard((current) => ({
154
+ ...current,
155
+ step: 'edit_task_select',
156
+ editIndex: 0,
157
+ replacementError: ''
158
+ }));
159
+ setActivePanel({
160
+ kind: 'logwork',
161
+ title: 'Edit Task',
162
+ text: 'Select a task to replace. Esc returns to the editor.'
163
+ });
164
+ return;
165
+ }
166
+
167
+ if (text === '/remove') {
168
+ if (!wizard.tasks.length) {
169
+ setActivePanel({
170
+ kind: 'error',
171
+ title: 'Remove Task',
172
+ text: 'No tasks to remove.'
173
+ });
174
+ return;
175
+ }
176
+ setWizard((current) => ({
177
+ ...current,
178
+ step: 'remove_tasks',
179
+ removeIndex: 0,
180
+ removeSelectedIndexes: []
181
+ }));
182
+ setActivePanel({
183
+ kind: 'logwork',
184
+ title: 'Remove Tasks',
185
+ text: 'Select tasks to remove. Space toggles, Enter confirms, Esc cancels.'
186
+ });
187
+ return;
188
+ }
189
+
190
+ if (text.startsWith('/remove ')) {
191
+ const index = Number(text.slice('/remove '.length).trim());
192
+ if (!Number.isInteger(index)) {
193
+ setActivePanel({
194
+ kind: 'error',
195
+ title: 'Remove Task',
196
+ text: 'Usage: /remove <task number>'
197
+ });
198
+ return;
199
+ }
200
+ if (index < 1 || index > wizard.tasks.length) {
201
+ setActivePanel({
202
+ kind: 'error',
203
+ title: 'Remove Task',
204
+ text: `No task found at index ${index}.`
205
+ });
206
+ return;
207
+ }
208
+ await previewDraft({
209
+ ...wizard,
210
+ tasks: wizard.tasks.filter((_, currentIndex) => currentIndex !== index - 1),
211
+ preview: null
212
+ });
213
+ return;
214
+ }
215
+
216
+ if (text === '/done') {
217
+ if (!wizard.tasks.length) {
218
+ setActivePanel({
219
+ kind: 'error',
220
+ title: 'Logwork Draft',
221
+ text: 'Add at least one task before /done.'
222
+ });
223
+ return;
224
+ }
225
+ await previewDraft(wizard);
226
+ return;
227
+ }
228
+
229
+ if (text === '/apply') {
230
+ await applyWizardPreview();
231
+ return;
232
+ }
233
+
234
+ if (text.startsWith('/')) {
235
+ setActivePanel({
236
+ kind: 'error',
237
+ title: 'Logwork Draft',
238
+ text: `Unknown logwork command: ${text}`
239
+ });
240
+ return;
241
+ }
242
+
243
+ try {
244
+ const task = parseTaskLine(text);
245
+ await previewDraft({
246
+ ...wizard,
247
+ tasks: [...wizard.tasks, task],
248
+ preview: null
249
+ });
250
+ } catch (error) {
251
+ setActivePanel({
252
+ kind: 'error',
253
+ title: 'Task Error',
254
+ text: error.message
255
+ });
256
+ }
257
+ }, [
258
+ applyWizardPreview,
259
+ cancelCurrentLogwork,
260
+ executeCommand,
261
+ openDraftPicker,
262
+ previewDraft,
263
+ prompts,
264
+ restoreTaskEditorPanel,
265
+ saveCurrentDraft,
266
+ wizard
267
+ ]);
268
+
269
+ const selectTaskForEdit = useCallback((index) => {
270
+ const task = wizard.tasks[index];
271
+ setInputValue('');
272
+ setWizard((current) => ({
273
+ ...current,
274
+ step: 'edit_task_replace',
275
+ editIndex: index,
276
+ replacementError: ''
277
+ }));
278
+ setActivePanel({
279
+ kind: 'logwork',
280
+ title: 'Replace Task',
281
+ text: [
282
+ `Replacing ${index + 1}. +${formatTaskHours(task.hours)} ${task.taskName}`,
283
+ 'Enter replacement line using format: +2 Feature Development | check ui/ux',
284
+ 'Esc returns without changing the task.'
285
+ ].join('\n')
286
+ });
287
+ }, [wizard]);
288
+
289
+ const submitTaskReplacement = useCallback(async (value) => {
290
+ const text = String(value || '').trim();
291
+ setInputValue('');
292
+ if (!text) {
293
+ setActivePanel({
294
+ kind: 'error',
295
+ title: 'Replace Task',
296
+ text: 'Replacement task is empty. Use format: +2 Feature Development | check ui/ux'
297
+ });
298
+ return;
299
+ }
300
+ if (text === '/cancel') {
301
+ setWizard((current) => ({
302
+ ...current,
303
+ step: 'edit_tasks',
304
+ replacementError: ''
305
+ }));
306
+ restoreTaskEditorPanel();
307
+ return;
308
+ }
309
+ try {
310
+ const task = parseTaskLine(text);
311
+ await previewDraft({
312
+ ...wizard,
313
+ step: 'edit_tasks',
314
+ tasks: replaceDraftTask(wizard.tasks, wizard.editIndex, task),
315
+ preview: null,
316
+ replacementError: ''
317
+ });
318
+ } catch (error) {
319
+ setWizard((current) => ({
320
+ ...current,
321
+ replacementError: error.message
322
+ }));
323
+ setActivePanel({
324
+ kind: 'error',
325
+ title: 'Replace Task',
326
+ text: error.message
327
+ });
328
+ }
329
+ }, [previewDraft, restoreTaskEditorPanel, wizard]);
330
+
331
+ return {
332
+ cancelRemoveTasks,
333
+ confirmRemoveTasks,
334
+ submitWizardTaskInput,
335
+ selectTaskForEdit,
336
+ submitTaskReplacement
337
+ };
338
+ }