logwork-cli 1.1.1 → 1.3.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/README.md +53 -25
- package/RELEASE.md +4 -2
- package/bin/logwork.mjs +2 -2
- package/commands/commits.mjs +128 -13
- package/commands/history.mjs +0 -4
- package/commands/mcp.mjs +266 -0
- package/commands/quick.mjs +38 -23
- package/commands/reminder.mjs +41 -2
- package/commands/setup.mjs +197 -42
- package/commands/tui.mjs +1 -0
- package/docs/advanced.md +18 -7
- package/docs/mcp-setup.md +120 -26
- package/docs/operations-guide.md +50 -2
- package/docs/recipes.md +40 -0
- package/docs/security.md +23 -2
- package/examples/mcp/antigravity.mcp_config.json +2 -2
- package/examples/mcp/codex.config.toml +2 -2
- package/examples/mcp/copilot.mcp.json +2 -2
- package/lib/api-auth-forms.mjs +393 -0
- package/lib/api-auth-http.mjs +184 -0
- package/lib/api-auth-support.mjs +101 -0
- package/lib/api-auth-tokens.mjs +129 -0
- package/lib/api-auth.mjs +39 -813
- package/lib/api.mjs +38 -1422
- package/lib/apply-ledger.mjs +1 -1
- package/lib/batch-workflow.mjs +187 -2
- package/lib/commit-workflow.mjs +345 -23
- package/lib/config.mjs +7 -0
- package/lib/diagnostics.mjs +116 -4
- package/lib/jira-api.mjs +25 -3
- package/lib/jira-workflow.mjs +36 -6
- package/lib/launcher.mjs +303 -0
- package/lib/logwork-config.mjs +64 -13
- package/lib/manual-apply-state.mjs +15 -0
- package/lib/manual-commands.mjs +2 -2
- package/lib/manual-ink-app.mjs +142 -1282
- package/lib/manual-ink-draft-actions.mjs +227 -0
- package/lib/manual-ink-interaction.mjs +318 -0
- package/lib/manual-ink-session.mjs +41 -0
- package/lib/manual-ink-task-editor.mjs +338 -0
- package/lib/manual-ink-ui.mjs +38 -0
- package/lib/manual-ink-wizard.mjs +520 -0
- package/lib/manual-mcp.mjs +78 -20
- package/lib/manual-repl.mjs +171 -22
- package/lib/mcp-install.mjs +930 -0
- package/lib/mcp-server.mjs +51 -20
- package/lib/node-runtime.mjs +155 -0
- package/lib/paths.mjs +7 -0
- package/lib/project-mapping-workflow.mjs +32 -27
- package/lib/query-workflow.mjs +19 -1
- package/lib/reconciliation-workflow.mjs +1 -1
- package/lib/reminder-config.mjs +38 -2
- package/lib/reminder-scheduler.mjs +85 -18
- package/lib/reminder-service.mjs +42 -3
- package/lib/reminder-workflow.mjs +118 -22
- package/lib/repo-project-suggestions.mjs +285 -0
- package/lib/ro-api-diagnostics.mjs +109 -0
- package/lib/ro-api-entries.mjs +359 -0
- package/lib/ro-api-fields.mjs +290 -0
- package/lib/ro-api-http.mjs +68 -0
- package/lib/ro-api-projects.mjs +137 -0
- package/lib/ro-api-tasks.mjs +50 -0
- package/lib/ro-api-timesheet.mjs +413 -0
- package/lib/ro-delete-workflow.mjs +2 -1
- package/lib/ro-entry-reader.mjs +11 -6
- package/lib/ro-resubmit-workflow.mjs +2 -1
- package/lib/terminal-auth-prompts.mjs +7 -4
- package/lib/ui.mjs +121 -56
- package/package.json +11 -4
- package/skills/logwork/SKILL.md +10 -4
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
// Logwork wizard flow for the Ink app: target, date and project pickers, live
|
|
2
|
+
// preview, apply and cancel. Called from ManualApp with its state and setters.
|
|
3
|
+
import { useCallback } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
buildCurrentWeekDateOptions,
|
|
6
|
+
buildLogworkText,
|
|
7
|
+
buildProjectOptions,
|
|
8
|
+
buildProjectOverrides,
|
|
9
|
+
buildWeekDateOptions,
|
|
10
|
+
canApplyPreview,
|
|
11
|
+
formatDraftPreview
|
|
12
|
+
} from './manual-logwork-wizard.mjs';
|
|
13
|
+
import {
|
|
14
|
+
BOTH_TARGET,
|
|
15
|
+
JIRA_TARGET,
|
|
16
|
+
previewTargets,
|
|
17
|
+
RO_TARGET,
|
|
18
|
+
targetLabel
|
|
19
|
+
} from './manual-targets.mjs';
|
|
20
|
+
import { loadManualDrafts } from './manual-drafts.mjs';
|
|
21
|
+
import { enrichRoPreviewWithJiraProjects } from './project-mapping-workflow.mjs';
|
|
22
|
+
import {
|
|
23
|
+
clearSessionPreviews,
|
|
24
|
+
hasSessionPreview,
|
|
25
|
+
sessionPreview,
|
|
26
|
+
setSessionPreview
|
|
27
|
+
} from './manual-ink-session.mjs';
|
|
28
|
+
|
|
29
|
+
export function useLogworkWizard(ctx) {
|
|
30
|
+
const {
|
|
31
|
+
cwd,
|
|
32
|
+
sessionRef,
|
|
33
|
+
wizard,
|
|
34
|
+
setWizard,
|
|
35
|
+
setActivePanel,
|
|
36
|
+
setLoading,
|
|
37
|
+
setInputValue,
|
|
38
|
+
setSelectedIndex,
|
|
39
|
+
inkWorkflows,
|
|
40
|
+
prompts,
|
|
41
|
+
runWithInlineAuth,
|
|
42
|
+
runWithJiraAuthInstruction,
|
|
43
|
+
executeCommand
|
|
44
|
+
} = ctx;
|
|
45
|
+
|
|
46
|
+
const startNewLogworkWizard = useCallback(async (target = RO_TARGET) => {
|
|
47
|
+
setInputValue('');
|
|
48
|
+
setSelectedIndex(0);
|
|
49
|
+
setLoading(target === JIRA_TARGET ? 'Building current week...' : 'Fetching current week...');
|
|
50
|
+
setActivePanel({
|
|
51
|
+
kind: 'logwork',
|
|
52
|
+
title: 'Logwork',
|
|
53
|
+
text: target === JIRA_TARGET
|
|
54
|
+
? 'Preparing Jira worklog dates...'
|
|
55
|
+
: 'Loading this week booking/logwork data...'
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
const weekly = target === JIRA_TARGET
|
|
59
|
+
? null
|
|
60
|
+
: await runWithInlineAuth(() => inkWorkflows.queryLogwork({
|
|
61
|
+
period: 'this_week',
|
|
62
|
+
cwd,
|
|
63
|
+
includeEntries: false
|
|
64
|
+
}));
|
|
65
|
+
const dateOptions = weekly ? buildWeekDateOptions(weekly) : buildCurrentWeekDateOptions();
|
|
66
|
+
if (!dateOptions.length) {
|
|
67
|
+
throw new Error('Unable to build week date options.');
|
|
68
|
+
}
|
|
69
|
+
const todayIndex = dateOptions.findIndex((option) => option.isToday);
|
|
70
|
+
setWizard({
|
|
71
|
+
step: 'select_date',
|
|
72
|
+
target,
|
|
73
|
+
weekly,
|
|
74
|
+
dateOptions,
|
|
75
|
+
dateIndex: todayIndex >= 0 ? todayIndex : 0,
|
|
76
|
+
projectsResult: null,
|
|
77
|
+
projectOptions: [],
|
|
78
|
+
projectIndex: 0,
|
|
79
|
+
selectedDate: null,
|
|
80
|
+
selectedProject: null,
|
|
81
|
+
tasks: [],
|
|
82
|
+
preview: null,
|
|
83
|
+
activeDraftId: null
|
|
84
|
+
});
|
|
85
|
+
setActivePanel({
|
|
86
|
+
kind: 'logwork',
|
|
87
|
+
title: 'Select Date',
|
|
88
|
+
text: `Choose a day for this ${targetLabel(target)} logwork session.`
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
setActivePanel({
|
|
92
|
+
kind: 'error',
|
|
93
|
+
title: 'Logwork Error',
|
|
94
|
+
text: error.message
|
|
95
|
+
});
|
|
96
|
+
} finally {
|
|
97
|
+
setLoading('');
|
|
98
|
+
}
|
|
99
|
+
}, [cwd, inkWorkflows, runWithInlineAuth, runWithJiraAuthInstruction]);
|
|
100
|
+
|
|
101
|
+
const startLogworkTargetPicker = useCallback(() => {
|
|
102
|
+
setInputValue('');
|
|
103
|
+
setSelectedIndex(0);
|
|
104
|
+
setWizard({
|
|
105
|
+
step: 'select_logwork_target',
|
|
106
|
+
targetIndex: 0
|
|
107
|
+
});
|
|
108
|
+
setActivePanel({
|
|
109
|
+
kind: 'logwork',
|
|
110
|
+
title: 'Select Logwork Target',
|
|
111
|
+
text: 'Choose where this logwork session should write.'
|
|
112
|
+
});
|
|
113
|
+
}, []);
|
|
114
|
+
|
|
115
|
+
const startAuthTargetPicker = useCallback(() => {
|
|
116
|
+
setInputValue('');
|
|
117
|
+
setSelectedIndex(0);
|
|
118
|
+
setWizard({
|
|
119
|
+
step: 'select_auth_target',
|
|
120
|
+
targetIndex: 0
|
|
121
|
+
});
|
|
122
|
+
setActivePanel({
|
|
123
|
+
kind: 'auth',
|
|
124
|
+
title: 'Select Auth Target',
|
|
125
|
+
text: 'Choose which service to authenticate.'
|
|
126
|
+
});
|
|
127
|
+
}, []);
|
|
128
|
+
|
|
129
|
+
const startReconciliationPeriodPicker = useCallback(() => {
|
|
130
|
+
setInputValue('');
|
|
131
|
+
setSelectedIndex(0);
|
|
132
|
+
setWizard({
|
|
133
|
+
step: 'select_reconciliation_period',
|
|
134
|
+
periodIndex: 2
|
|
135
|
+
});
|
|
136
|
+
setActivePanel({
|
|
137
|
+
kind: 'output',
|
|
138
|
+
title: 'RO / Jira Reconciliation',
|
|
139
|
+
text: 'Choose a preset period to compare.'
|
|
140
|
+
});
|
|
141
|
+
}, []);
|
|
142
|
+
|
|
143
|
+
const startLogworkWizard = useCallback(async (target) => {
|
|
144
|
+
if (!target) {
|
|
145
|
+
startLogworkTargetPicker();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (target !== RO_TARGET) {
|
|
150
|
+
await startNewLogworkWizard(target);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
setInputValue('');
|
|
155
|
+
setSelectedIndex(0);
|
|
156
|
+
setLoading('Loading saved drafts...');
|
|
157
|
+
try {
|
|
158
|
+
const drafts = await loadManualDrafts({ cwd });
|
|
159
|
+
if (drafts.length) {
|
|
160
|
+
setWizard({
|
|
161
|
+
step: 'select_draft',
|
|
162
|
+
target: RO_TARGET,
|
|
163
|
+
drafts,
|
|
164
|
+
draftIndex: 0,
|
|
165
|
+
draftSource: 'start',
|
|
166
|
+
weekly: null,
|
|
167
|
+
dateOptions: [],
|
|
168
|
+
dateIndex: 0,
|
|
169
|
+
projectsResult: null,
|
|
170
|
+
projectOptions: [],
|
|
171
|
+
projectIndex: 0,
|
|
172
|
+
selectedDate: null,
|
|
173
|
+
selectedProject: null,
|
|
174
|
+
tasks: [],
|
|
175
|
+
preview: null,
|
|
176
|
+
activeDraftId: null
|
|
177
|
+
});
|
|
178
|
+
setActivePanel({
|
|
179
|
+
kind: 'logwork',
|
|
180
|
+
title: 'Saved Drafts',
|
|
181
|
+
text: 'Resume a saved draft, delete one, or start a new logwork session.'
|
|
182
|
+
});
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
await startNewLogworkWizard(RO_TARGET);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
setActivePanel({
|
|
188
|
+
kind: 'error',
|
|
189
|
+
title: 'Draft Error',
|
|
190
|
+
text: error.message
|
|
191
|
+
});
|
|
192
|
+
} finally {
|
|
193
|
+
setLoading('');
|
|
194
|
+
}
|
|
195
|
+
}, [cwd, startLogworkTargetPicker, startNewLogworkWizard]);
|
|
196
|
+
|
|
197
|
+
const startMcpClientPicker = useCallback(() => {
|
|
198
|
+
setInputValue('');
|
|
199
|
+
setSelectedIndex(0);
|
|
200
|
+
setWizard({
|
|
201
|
+
step: 'select_mcp_client',
|
|
202
|
+
mcpClientIndex: 0
|
|
203
|
+
});
|
|
204
|
+
setActivePanel({
|
|
205
|
+
kind: 'output',
|
|
206
|
+
title: 'MCP Setup',
|
|
207
|
+
text: 'Choose an MCP client to print copy-ready setup.'
|
|
208
|
+
});
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
const cancelMcpClientPicker = useCallback(() => {
|
|
212
|
+
setWizard(null);
|
|
213
|
+
setActivePanel({
|
|
214
|
+
kind: 'idle',
|
|
215
|
+
title: 'Ready',
|
|
216
|
+
text: 'Type / for commands.'
|
|
217
|
+
});
|
|
218
|
+
}, []);
|
|
219
|
+
|
|
220
|
+
const selectWizardDate = useCallback(async (dateOption) => {
|
|
221
|
+
const target = wizard?.target || RO_TARGET;
|
|
222
|
+
if (target === JIRA_TARGET) {
|
|
223
|
+
const draft = {
|
|
224
|
+
target,
|
|
225
|
+
date: dateOption.date,
|
|
226
|
+
project: null,
|
|
227
|
+
tasks: [],
|
|
228
|
+
preview: null
|
|
229
|
+
};
|
|
230
|
+
sessionRef.current.logworkDraft = draft;
|
|
231
|
+
clearSessionPreviews(sessionRef.current);
|
|
232
|
+
sessionRef.current.activeTarget = target;
|
|
233
|
+
setWizard((current) => ({
|
|
234
|
+
...current,
|
|
235
|
+
step: 'edit_tasks',
|
|
236
|
+
selectedDate: dateOption.date,
|
|
237
|
+
selectedProject: null,
|
|
238
|
+
tasks: [],
|
|
239
|
+
preview: null,
|
|
240
|
+
previews: {}
|
|
241
|
+
}));
|
|
242
|
+
setActivePanel({
|
|
243
|
+
kind: 'logwork',
|
|
244
|
+
title: 'Jira Worklog Draft',
|
|
245
|
+
text: [
|
|
246
|
+
formatDraftPreview(draft),
|
|
247
|
+
'',
|
|
248
|
+
'Enter tasks one per line: +2 Feature Development | check ui/ux (SCB-213)',
|
|
249
|
+
'Empty Enter applies after preview is ready.',
|
|
250
|
+
'Type / for task commands. Esc cancels.'
|
|
251
|
+
].join('\n')
|
|
252
|
+
});
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
setLoading('Fetching project memberships...');
|
|
257
|
+
setActivePanel({
|
|
258
|
+
kind: 'logwork',
|
|
259
|
+
title: 'Select Project',
|
|
260
|
+
text: `Loading projects for ${dateOption.date}...`
|
|
261
|
+
});
|
|
262
|
+
try {
|
|
263
|
+
const projectsResult = await runWithInlineAuth(() => inkWorkflows.listLogworkProjects({ cwd }));
|
|
264
|
+
const projectOptions = buildProjectOptions({
|
|
265
|
+
projectsResult,
|
|
266
|
+
weekly: wizard.weekly,
|
|
267
|
+
date: dateOption.date
|
|
268
|
+
});
|
|
269
|
+
if (!projectOptions.length) {
|
|
270
|
+
throw new Error('No Resource Optimiser project memberships found.');
|
|
271
|
+
}
|
|
272
|
+
setWizard((current) => ({
|
|
273
|
+
...current,
|
|
274
|
+
step: 'select_project',
|
|
275
|
+
target,
|
|
276
|
+
selectedDate: dateOption.date,
|
|
277
|
+
projectsResult,
|
|
278
|
+
projectOptions,
|
|
279
|
+
projectIndex: 0,
|
|
280
|
+
selectedProject: null,
|
|
281
|
+
tasks: [],
|
|
282
|
+
preview: null
|
|
283
|
+
}));
|
|
284
|
+
setActivePanel({
|
|
285
|
+
kind: 'logwork',
|
|
286
|
+
title: 'Select Project',
|
|
287
|
+
text: `Choose a project for ${dateOption.date}.`
|
|
288
|
+
});
|
|
289
|
+
} catch (error) {
|
|
290
|
+
setActivePanel({
|
|
291
|
+
kind: 'error',
|
|
292
|
+
title: 'Project Error',
|
|
293
|
+
text: error.message
|
|
294
|
+
});
|
|
295
|
+
} finally {
|
|
296
|
+
setLoading('');
|
|
297
|
+
}
|
|
298
|
+
}, [cwd, inkWorkflows, runWithInlineAuth, wizard]);
|
|
299
|
+
|
|
300
|
+
const selectWizardProject = useCallback((project) => {
|
|
301
|
+
const target = wizard?.target || RO_TARGET;
|
|
302
|
+
const draft = {
|
|
303
|
+
target,
|
|
304
|
+
date: wizard.selectedDate,
|
|
305
|
+
project,
|
|
306
|
+
tasks: [],
|
|
307
|
+
preview: null
|
|
308
|
+
};
|
|
309
|
+
sessionRef.current.logworkDraft = draft;
|
|
310
|
+
clearSessionPreviews(sessionRef.current);
|
|
311
|
+
sessionRef.current.activeTarget = target;
|
|
312
|
+
setWizard((current) => ({
|
|
313
|
+
...current,
|
|
314
|
+
step: 'edit_tasks',
|
|
315
|
+
target,
|
|
316
|
+
selectedProject: project,
|
|
317
|
+
tasks: [],
|
|
318
|
+
preview: null,
|
|
319
|
+
previews: {}
|
|
320
|
+
}));
|
|
321
|
+
setActivePanel({
|
|
322
|
+
kind: 'logwork',
|
|
323
|
+
title: target === BOTH_TARGET ? 'RO + Jira Draft' : 'Logwork Draft',
|
|
324
|
+
text: [
|
|
325
|
+
formatDraftPreview(draft),
|
|
326
|
+
'',
|
|
327
|
+
target === BOTH_TARGET ? 'Enter tasks one per line: +2 Feature Development | check ui/ux (SCB-213)' : 'Enter tasks one per line: +2 Feature Development | check ui/ux',
|
|
328
|
+
'Empty Enter applies after preview is ready.',
|
|
329
|
+
'Type / for task commands. Esc cancels.'
|
|
330
|
+
].join('\n')
|
|
331
|
+
});
|
|
332
|
+
}, [wizard]);
|
|
333
|
+
|
|
334
|
+
const previewDraft = useCallback(async (nextWizard) => {
|
|
335
|
+
const target = nextWizard.target || RO_TARGET;
|
|
336
|
+
const draft = {
|
|
337
|
+
target,
|
|
338
|
+
date: nextWizard.selectedDate,
|
|
339
|
+
project: nextWizard.selectedProject,
|
|
340
|
+
tasks: nextWizard.tasks
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
if (!draft.tasks.length) {
|
|
344
|
+
sessionRef.current.logworkDraft = draft;
|
|
345
|
+
clearSessionPreviews(sessionRef.current);
|
|
346
|
+
sessionRef.current.activeTarget = target;
|
|
347
|
+
setWizard(nextWizard);
|
|
348
|
+
setActivePanel({
|
|
349
|
+
kind: 'logwork',
|
|
350
|
+
title: target === JIRA_TARGET ? 'Jira Worklog Draft' : target === BOTH_TARGET ? 'RO + Jira Draft' : 'Logwork Draft',
|
|
351
|
+
text: [
|
|
352
|
+
formatDraftPreview(draft),
|
|
353
|
+
'',
|
|
354
|
+
target === RO_TARGET ? 'Enter tasks one per line: +2 Feature Development | check ui/ux' : 'Enter tasks one per line: +2 Feature Development | check ui/ux (SCB-213)',
|
|
355
|
+
'Empty Enter applies after preview is ready.'
|
|
356
|
+
].join('\n')
|
|
357
|
+
});
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
setLoading('Building live preview...');
|
|
362
|
+
try {
|
|
363
|
+
const text = buildLogworkText({
|
|
364
|
+
date: draft.date,
|
|
365
|
+
tasks: draft.tasks
|
|
366
|
+
});
|
|
367
|
+
const previews = {};
|
|
368
|
+
if (previewTargets(target).includes(RO_TARGET)) {
|
|
369
|
+
const projectOverrides = buildProjectOverrides({
|
|
370
|
+
date: draft.date,
|
|
371
|
+
tasks: draft.tasks,
|
|
372
|
+
projectMemberId: draft.project.projectMemberId
|
|
373
|
+
});
|
|
374
|
+
previews[RO_TARGET] = await runWithInlineAuth(() => inkWorkflows.previewLogworkBatch({
|
|
375
|
+
text,
|
|
376
|
+
projectOverrides,
|
|
377
|
+
cwd
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
380
|
+
if (previewTargets(target).includes(JIRA_TARGET)) {
|
|
381
|
+
previews[JIRA_TARGET] = await runWithJiraAuthInstruction(() => inkWorkflows.previewJiraWorklogBatch({
|
|
382
|
+
text
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
385
|
+
if (target === BOTH_TARGET && previews[RO_TARGET] && previews[JIRA_TARGET]) {
|
|
386
|
+
previews[RO_TARGET] = enrichRoPreviewWithJiraProjects(previews[RO_TARGET], previews[JIRA_TARGET]);
|
|
387
|
+
}
|
|
388
|
+
const preview = target === JIRA_TARGET ? previews[JIRA_TARGET] : previews[RO_TARGET];
|
|
389
|
+
const withPreview = {
|
|
390
|
+
...draft,
|
|
391
|
+
preview,
|
|
392
|
+
previews
|
|
393
|
+
};
|
|
394
|
+
sessionRef.current.logworkDraft = withPreview;
|
|
395
|
+
sessionRef.current.activeTarget = target;
|
|
396
|
+
for (const previewTarget of previewTargets(target)) {
|
|
397
|
+
setSessionPreview(sessionRef.current, previewTarget, previews[previewTarget] || null);
|
|
398
|
+
}
|
|
399
|
+
setWizard({
|
|
400
|
+
...nextWizard,
|
|
401
|
+
target,
|
|
402
|
+
preview,
|
|
403
|
+
previews
|
|
404
|
+
});
|
|
405
|
+
const hasBlockedJira = previews[JIRA_TARGET]?.entries?.some((entry) => entry.status !== 'ready');
|
|
406
|
+
setActivePanel({
|
|
407
|
+
kind: preview?.unresolvedEntries?.length || preview?.errors?.length || hasBlockedJira ? 'error' : 'logwork',
|
|
408
|
+
title: target === JIRA_TARGET ? 'Jira Worklog Preview' : target === BOTH_TARGET ? 'RO + Jira Preview' : 'Logwork Preview',
|
|
409
|
+
text: [
|
|
410
|
+
formatDraftPreview(withPreview),
|
|
411
|
+
'',
|
|
412
|
+
'Empty Enter applies this preview.',
|
|
413
|
+
'Type another +hours task to add more.',
|
|
414
|
+
'/remove selects tasks to delete.'
|
|
415
|
+
].join('\n')
|
|
416
|
+
});
|
|
417
|
+
return preview;
|
|
418
|
+
} catch (error) {
|
|
419
|
+
setActivePanel({
|
|
420
|
+
kind: 'error',
|
|
421
|
+
title: 'Preview Error',
|
|
422
|
+
text: error.message
|
|
423
|
+
});
|
|
424
|
+
} finally {
|
|
425
|
+
setLoading('');
|
|
426
|
+
}
|
|
427
|
+
}, [cwd, inkWorkflows, runWithInlineAuth, runWithJiraAuthInstruction]);
|
|
428
|
+
|
|
429
|
+
const cancelCurrentLogwork = useCallback(async () => {
|
|
430
|
+
if (wizard?.tasks?.length) {
|
|
431
|
+
const ok = await prompts.confirm('Discard this unsaved logwork session?', false);
|
|
432
|
+
if (!ok) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
sessionRef.current.logworkDraft = null;
|
|
437
|
+
clearSessionPreviews(sessionRef.current);
|
|
438
|
+
setWizard(null);
|
|
439
|
+
setActivePanel({
|
|
440
|
+
kind: 'idle',
|
|
441
|
+
title: 'Ready',
|
|
442
|
+
text: 'Logwork session discarded. Type / for commands.'
|
|
443
|
+
});
|
|
444
|
+
}, [prompts, wizard]);
|
|
445
|
+
|
|
446
|
+
const applyWizardPreview = useCallback(async () => {
|
|
447
|
+
const target = wizard.target || RO_TARGET;
|
|
448
|
+
if (!wizard.tasks.length) {
|
|
449
|
+
setActivePanel({
|
|
450
|
+
kind: 'logwork',
|
|
451
|
+
title: 'Logwork Draft',
|
|
452
|
+
text: target === RO_TARGET ? 'Enter a task like +2 Feature Development | check ui/ux.' : 'Enter a task like +2 Feature Development | check ui/ux (SCB-213).'
|
|
453
|
+
});
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const targets = previewTargets(target);
|
|
458
|
+
const missingPreview = targets.some((previewTarget) => !sessionPreview(sessionRef.current, previewTarget));
|
|
459
|
+
if (missingPreview) {
|
|
460
|
+
await previewDraft(wizard);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
for (const previewTarget of targets) {
|
|
465
|
+
const applyState = canApplyPreview(sessionPreview(sessionRef.current, previewTarget), { target: previewTarget });
|
|
466
|
+
if (!applyState.ok) {
|
|
467
|
+
setActivePanel({
|
|
468
|
+
kind: 'error',
|
|
469
|
+
title: 'Apply Blocked',
|
|
470
|
+
text: `${targetLabel(previewTarget)}: ${applyState.reason}`
|
|
471
|
+
});
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
await executeCommand({ type: 'apply', target });
|
|
477
|
+
if (!hasSessionPreview(sessionRef.current)) {
|
|
478
|
+
setWizard(null);
|
|
479
|
+
}
|
|
480
|
+
}, [executeCommand, previewDraft, wizard]);
|
|
481
|
+
|
|
482
|
+
const restoreTaskEditorPanel = useCallback((nextWizard = wizard) => {
|
|
483
|
+
const target = nextWizard.target || RO_TARGET;
|
|
484
|
+
const draft = {
|
|
485
|
+
target,
|
|
486
|
+
date: nextWizard.selectedDate,
|
|
487
|
+
project: nextWizard.selectedProject,
|
|
488
|
+
tasks: nextWizard.tasks,
|
|
489
|
+
preview: nextWizard.preview,
|
|
490
|
+
previews: nextWizard.previews
|
|
491
|
+
};
|
|
492
|
+
setActivePanel({
|
|
493
|
+
kind: 'logwork',
|
|
494
|
+
title: nextWizard.preview ? `${targetLabel(target)} Preview` : `${targetLabel(target)} Draft`,
|
|
495
|
+
text: [
|
|
496
|
+
formatDraftPreview(draft),
|
|
497
|
+
'',
|
|
498
|
+
nextWizard.preview ? 'Empty Enter applies this preview.' : target === RO_TARGET ? 'Enter tasks one per line: +2 Feature Development | check ui/ux' : 'Enter tasks one per line: +2 Feature Development | check ui/ux (SCB-213)',
|
|
499
|
+
nextWizard.preview ? 'Type another +hours task to add more.' : 'Empty Enter applies after preview is ready.',
|
|
500
|
+
'Type / for task commands. Esc cancels.'
|
|
501
|
+
].join('\n')
|
|
502
|
+
});
|
|
503
|
+
}, [wizard]);
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
startNewLogworkWizard,
|
|
507
|
+
startLogworkTargetPicker,
|
|
508
|
+
startAuthTargetPicker,
|
|
509
|
+
startReconciliationPeriodPicker,
|
|
510
|
+
startLogworkWizard,
|
|
511
|
+
startMcpClientPicker,
|
|
512
|
+
cancelMcpClientPicker,
|
|
513
|
+
selectWizardDate,
|
|
514
|
+
selectWizardProject,
|
|
515
|
+
previewDraft,
|
|
516
|
+
cancelCurrentLogwork,
|
|
517
|
+
applyWizardPreview,
|
|
518
|
+
restoreTaskEditorPanel
|
|
519
|
+
};
|
|
520
|
+
}
|