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.
Files changed (70) hide show
  1. package/README.md +53 -25
  2. package/RELEASE.md +4 -2
  3. package/bin/logwork.mjs +2 -2
  4. package/commands/commits.mjs +128 -13
  5. package/commands/history.mjs +0 -4
  6. package/commands/mcp.mjs +266 -0
  7. package/commands/quick.mjs +38 -23
  8. package/commands/reminder.mjs +41 -2
  9. package/commands/setup.mjs +197 -42
  10. package/commands/tui.mjs +1 -0
  11. package/docs/advanced.md +18 -7
  12. package/docs/mcp-setup.md +120 -26
  13. package/docs/operations-guide.md +50 -2
  14. package/docs/recipes.md +40 -0
  15. package/docs/security.md +23 -2
  16. package/examples/mcp/antigravity.mcp_config.json +2 -2
  17. package/examples/mcp/codex.config.toml +2 -2
  18. package/examples/mcp/copilot.mcp.json +2 -2
  19. package/lib/api-auth-forms.mjs +393 -0
  20. package/lib/api-auth-http.mjs +184 -0
  21. package/lib/api-auth-support.mjs +101 -0
  22. package/lib/api-auth-tokens.mjs +129 -0
  23. package/lib/api-auth.mjs +39 -813
  24. package/lib/api.mjs +38 -1422
  25. package/lib/apply-ledger.mjs +1 -1
  26. package/lib/batch-workflow.mjs +187 -2
  27. package/lib/commit-workflow.mjs +345 -23
  28. package/lib/config.mjs +7 -0
  29. package/lib/diagnostics.mjs +116 -4
  30. package/lib/jira-api.mjs +25 -3
  31. package/lib/jira-workflow.mjs +36 -6
  32. package/lib/launcher.mjs +303 -0
  33. package/lib/logwork-config.mjs +64 -13
  34. package/lib/manual-apply-state.mjs +15 -0
  35. package/lib/manual-commands.mjs +2 -2
  36. package/lib/manual-ink-app.mjs +142 -1282
  37. package/lib/manual-ink-draft-actions.mjs +227 -0
  38. package/lib/manual-ink-interaction.mjs +318 -0
  39. package/lib/manual-ink-session.mjs +41 -0
  40. package/lib/manual-ink-task-editor.mjs +338 -0
  41. package/lib/manual-ink-ui.mjs +38 -0
  42. package/lib/manual-ink-wizard.mjs +520 -0
  43. package/lib/manual-mcp.mjs +78 -20
  44. package/lib/manual-repl.mjs +171 -22
  45. package/lib/mcp-install.mjs +930 -0
  46. package/lib/mcp-server.mjs +51 -20
  47. package/lib/node-runtime.mjs +155 -0
  48. package/lib/paths.mjs +7 -0
  49. package/lib/project-mapping-workflow.mjs +32 -27
  50. package/lib/query-workflow.mjs +19 -1
  51. package/lib/reconciliation-workflow.mjs +1 -1
  52. package/lib/reminder-config.mjs +38 -2
  53. package/lib/reminder-scheduler.mjs +85 -18
  54. package/lib/reminder-service.mjs +42 -3
  55. package/lib/reminder-workflow.mjs +118 -22
  56. package/lib/repo-project-suggestions.mjs +285 -0
  57. package/lib/ro-api-diagnostics.mjs +109 -0
  58. package/lib/ro-api-entries.mjs +359 -0
  59. package/lib/ro-api-fields.mjs +290 -0
  60. package/lib/ro-api-http.mjs +68 -0
  61. package/lib/ro-api-projects.mjs +137 -0
  62. package/lib/ro-api-tasks.mjs +50 -0
  63. package/lib/ro-api-timesheet.mjs +413 -0
  64. package/lib/ro-delete-workflow.mjs +2 -1
  65. package/lib/ro-entry-reader.mjs +11 -6
  66. package/lib/ro-resubmit-workflow.mjs +2 -1
  67. package/lib/terminal-auth-prompts.mjs +7 -4
  68. package/lib/ui.mjs +121 -56
  69. package/package.json +11 -4
  70. package/skills/logwork/SKILL.md +10 -4
@@ -6,65 +6,31 @@ import React, {
6
6
  } from 'react';
7
7
  import { Box, render, useApp } from 'ink';
8
8
  import { isAuthRequiredError } from './auth-errors.mjs';
9
+ import { isDryRun } from './config.mjs';
9
10
  import { isJiraAuthRequiredError } from './jira-auth.mjs';
10
- import {
11
- buildLogworkText,
12
- buildCurrentWeekDateOptions,
13
- buildProjectOverrides,
14
- buildProjectOptions,
15
- buildWeekDateOptions,
16
- canApplyPreview,
17
- formatDraftPreview,
18
- parseTaskLine,
19
- removeDraftTasks,
20
- replaceDraftTask,
21
- toggleTaskSelection
22
- } from './manual-logwork-wizard.mjs';
23
11
  import {
24
12
  getCommandSuggestions,
25
13
  TASK_COMMANDS
26
14
  } from './manual-commands.mjs';
15
+ import { jiraLoginInstruction } from './manual-targets.mjs';
27
16
  import {
28
- AUTH_TARGETS,
29
- BOTH_TARGET,
30
- jiraLoginInstruction,
31
- JIRA_TARGET,
32
- LOGWORK_TARGETS,
33
- previewTargets,
34
- RO_TARGET,
35
- targetLabel
36
- } from './manual-targets.mjs';
37
- import {
38
- deleteManualDraft,
39
- formatManualDraftLabel,
40
- loadManualDrafts,
41
- saveManualDraft
42
- } from './manual-drafts.mjs';
43
- import { enrichRoPreviewWithJiraProjects } from './project-mapping-workflow.mjs';
44
- import {
45
- CommandInput,
46
- ConfirmDialog,
47
17
  completeCommandValue,
48
18
  CurrentPanel,
49
- DatePicker,
50
- DraftPicker,
51
- formatTaskHours,
52
19
  HeaderBar,
53
20
  LoadingLine,
54
21
  loadingMessageForCommand,
55
- McpClientPicker,
56
22
  panelTitleForCommand,
57
- ProjectPicker,
58
- sameProjectIdentity,
59
- StatusBar,
60
- TargetPicker,
61
- TaskEditPicker,
62
- TaskRemovePicker
23
+ StatusBar
63
24
  } from './manual-ink-ui.mjs';
25
+ import { createInkCredentialProvider } from './manual-ink-auth.mjs';
64
26
  import {
65
- AuthPrompt,
66
- createInkCredentialProvider
67
- } from './manual-ink-auth.mjs';
27
+ activeSessionPreview,
28
+ hasSessionPreview
29
+ } from './manual-ink-session.mjs';
30
+ import { useLogworkWizard } from './manual-ink-wizard.mjs';
31
+ import { useDraftActions } from './manual-ink-draft-actions.mjs';
32
+ import { useTaskEditorActions } from './manual-ink-task-editor.mjs';
33
+ import { renderManualInteraction } from './manual-ink-interaction.mjs';
68
34
 
69
35
  export {
70
36
  CommandInput,
@@ -78,6 +44,7 @@ export {
78
44
  OutputPanel,
79
45
  ProjectChart,
80
46
  ProjectPicker,
47
+ SelectPrompt,
81
48
  SelectionList,
82
49
  SlashMenu,
83
50
  StatusBar,
@@ -102,50 +69,6 @@ export {
102
69
  } from './manual-ink-auth.mjs';
103
70
 
104
71
  const h = React.createElement;
105
- const RECONCILIATION_PERIOD_OPTIONS = [
106
- { key: 'today', label: 'Today', description: 'Compare today' },
107
- { key: 'yesterday', label: 'Yesterday', description: 'Compare yesterday' },
108
- { key: 'this_week', label: 'This week', description: 'Compare Monday through Sunday' },
109
- { key: 'last_week', label: 'Last week', description: 'Compare the previous week' },
110
- { key: 'this_month', label: 'This month', description: 'Compare the current month' },
111
- { key: 'last_month', label: 'Last month', description: 'Compare the previous month' }
112
- ];
113
-
114
- function setSessionPreview(session, target, preview) {
115
- session.activeTarget = target;
116
- session.previews = {
117
- ...(session.previews || {}),
118
- [target]: preview
119
- };
120
- }
121
-
122
- function clearSessionPreview(session, target) {
123
- if (session.previews) {
124
- session.previews[target] = null;
125
- }
126
- }
127
-
128
- function clearSessionPreviews(session) {
129
- session.previews = {
130
- [RO_TARGET]: null,
131
- [JIRA_TARGET]: null
132
- };
133
- }
134
-
135
- function sessionPreview(session, target) {
136
- return session.previews?.[target] || null;
137
- }
138
-
139
- function hasSessionPreview(session) {
140
- return Boolean(session.previews?.[RO_TARGET] || session.previews?.[JIRA_TARGET]);
141
- }
142
-
143
- function activeSessionPreview(session) {
144
- if (session.activeTarget === JIRA_TARGET) {
145
- return sessionPreview(session, JIRA_TARGET);
146
- }
147
- return sessionPreview(session, RO_TARGET);
148
- }
149
72
 
150
73
  export async function runManualInkApp({
151
74
  input,
@@ -180,6 +103,7 @@ export function ManualApp({
180
103
  const { exit } = useApp();
181
104
  const sessionRef = useRef(controller.createManualSession());
182
105
  const confirmResolverRef = useRef(null);
106
+ const selectResolverRef = useRef(null);
183
107
  const authResolverRef = useRef(null);
184
108
  const authRejecterRef = useRef(null);
185
109
  const [inputValue, setInputValue] = useState('');
@@ -193,6 +117,7 @@ export function ManualApp({
193
117
  }));
194
118
  const [loading, setLoading] = useState('');
195
119
  const [pendingConfirm, setPendingConfirm] = useState(null);
120
+ const [pendingSelect, setPendingSelect] = useState(null);
196
121
  const [authPrompt, setAuthPrompt] = useState(null);
197
122
  const [authActive, setAuthActive] = useState(false);
198
123
  const [wizard, setWizard] = useState(null);
@@ -206,9 +131,35 @@ export function ManualApp({
206
131
  initialValue
207
132
  });
208
133
  });
134
+ },
135
+ // { message, options: [{ value, label, hint }], initialValue } -> the
136
+ // chosen value, or null on Escape.
137
+ select({ message, options = [], initialValue }) {
138
+ return new Promise((resolveValue) => {
139
+ selectResolverRef.current = resolveValue;
140
+ const initialIndex = Math.max(0, options.findIndex((option) => option.value === initialValue));
141
+ setPendingSelect({
142
+ message,
143
+ options,
144
+ selectedIndex: initialIndex
145
+ });
146
+ });
209
147
  }
210
148
  }), []);
211
149
 
150
+ const changeSelect = useCallback((index) => {
151
+ setPendingSelect((current) => (current ? { ...current, selectedIndex: index } : current));
152
+ }, []);
153
+
154
+ const resolveSelect = useCallback((value) => {
155
+ const resolver = selectResolverRef.current;
156
+ selectResolverRef.current = null;
157
+ setPendingSelect(null);
158
+ if (resolver) {
159
+ resolver(value ?? null);
160
+ }
161
+ }, []);
162
+
212
163
  const resolveConfirm = useCallback((value) => {
213
164
  const resolver = confirmResolverRef.current;
214
165
  confirmResolverRef.current = null;
@@ -346,958 +297,82 @@ export function ManualApp({
346
297
  }
347
298
  }, [controller, cwd, exit, inkWorkflows, prompts, setCommandPanel]);
348
299
 
349
- const startNewLogworkWizard = useCallback(async (target = RO_TARGET) => {
350
- setInputValue('');
351
- setSelectedIndex(0);
352
- setLoading(target === JIRA_TARGET ? 'Building current week...' : 'Fetching current week...');
353
- setActivePanel({
354
- kind: 'logwork',
355
- title: 'Logwork',
356
- text: target === JIRA_TARGET
357
- ? 'Preparing Jira worklog dates...'
358
- : 'Loading this week booking/logwork data...'
359
- });
360
- try {
361
- const weekly = target === JIRA_TARGET
362
- ? null
363
- : await runWithInlineAuth(() => inkWorkflows.queryLogwork({
364
- period: 'this_week',
365
- cwd,
366
- includeEntries: false
367
- }));
368
- const dateOptions = weekly ? buildWeekDateOptions(weekly) : buildCurrentWeekDateOptions();
369
- if (!dateOptions.length) {
370
- throw new Error('Unable to build week date options.');
371
- }
372
- const todayIndex = dateOptions.findIndex((option) => option.isToday);
373
- setWizard({
374
- step: 'select_date',
375
- target,
376
- weekly,
377
- dateOptions,
378
- dateIndex: todayIndex >= 0 ? todayIndex : 0,
379
- projectsResult: null,
380
- projectOptions: [],
381
- projectIndex: 0,
382
- selectedDate: null,
383
- selectedProject: null,
384
- tasks: [],
385
- preview: null,
386
- activeDraftId: null
387
- });
388
- setActivePanel({
389
- kind: 'logwork',
390
- title: 'Select Date',
391
- text: `Choose a day for this ${targetLabel(target)} logwork session.`
392
- });
393
- } catch (error) {
394
- setActivePanel({
395
- kind: 'error',
396
- title: 'Logwork Error',
397
- text: error.message
398
- });
399
- } finally {
400
- setLoading('');
401
- }
402
- }, [cwd, inkWorkflows, runWithInlineAuth, runWithJiraAuthInstruction]);
403
-
404
- const startLogworkTargetPicker = useCallback(() => {
405
- setInputValue('');
406
- setSelectedIndex(0);
407
- setWizard({
408
- step: 'select_logwork_target',
409
- targetIndex: 0
410
- });
411
- setActivePanel({
412
- kind: 'logwork',
413
- title: 'Select Logwork Target',
414
- text: 'Choose where this logwork session should write.'
415
- });
416
- }, []);
417
-
418
- const startAuthTargetPicker = useCallback(() => {
419
- setInputValue('');
420
- setSelectedIndex(0);
421
- setWizard({
422
- step: 'select_auth_target',
423
- targetIndex: 0
424
- });
425
- setActivePanel({
426
- kind: 'auth',
427
- title: 'Select Auth Target',
428
- text: 'Choose which service to authenticate.'
429
- });
430
- }, []);
431
-
432
- const startReconciliationPeriodPicker = useCallback(() => {
433
- setInputValue('');
434
- setSelectedIndex(0);
435
- setWizard({
436
- step: 'select_reconciliation_period',
437
- periodIndex: 2
438
- });
439
- setActivePanel({
440
- kind: 'output',
441
- title: 'RO / Jira Reconciliation',
442
- text: 'Choose a preset period to compare.'
443
- });
444
- }, []);
445
-
446
- const startLogworkWizard = useCallback(async (target) => {
447
- if (!target) {
448
- startLogworkTargetPicker();
449
- return;
450
- }
451
-
452
- if (target !== RO_TARGET) {
453
- await startNewLogworkWizard(target);
454
- return;
455
- }
456
-
457
- setInputValue('');
458
- setSelectedIndex(0);
459
- setLoading('Loading saved drafts...');
460
- try {
461
- const drafts = await loadManualDrafts({ cwd });
462
- if (drafts.length) {
463
- setWizard({
464
- step: 'select_draft',
465
- target: RO_TARGET,
466
- drafts,
467
- draftIndex: 0,
468
- draftSource: 'start',
469
- weekly: null,
470
- dateOptions: [],
471
- dateIndex: 0,
472
- projectsResult: null,
473
- projectOptions: [],
474
- projectIndex: 0,
475
- selectedDate: null,
476
- selectedProject: null,
477
- tasks: [],
478
- preview: null,
479
- activeDraftId: null
480
- });
481
- setActivePanel({
482
- kind: 'logwork',
483
- title: 'Saved Drafts',
484
- text: 'Resume a saved draft, delete one, or start a new logwork session.'
485
- });
486
- return;
487
- }
488
- await startNewLogworkWizard(RO_TARGET);
489
- } catch (error) {
490
- setActivePanel({
491
- kind: 'error',
492
- title: 'Draft Error',
493
- text: error.message
494
- });
495
- } finally {
496
- setLoading('');
497
- }
498
- }, [cwd, startLogworkTargetPicker, startNewLogworkWizard]);
499
-
500
- const startMcpClientPicker = useCallback(() => {
501
- setInputValue('');
502
- setSelectedIndex(0);
503
- setWizard({
504
- step: 'select_mcp_client',
505
- mcpClientIndex: 0
506
- });
507
- setActivePanel({
508
- kind: 'output',
509
- title: 'MCP Setup',
510
- text: 'Choose an MCP client to print copy-ready setup.'
511
- });
512
- }, []);
513
-
514
- const cancelMcpClientPicker = useCallback(() => {
515
- setWizard(null);
516
- setActivePanel({
517
- kind: 'idle',
518
- title: 'Ready',
519
- text: 'Type / for commands.'
520
- });
521
- }, []);
522
-
523
- const selectWizardDate = useCallback(async (dateOption) => {
524
- const target = wizard?.target || RO_TARGET;
525
- if (target === JIRA_TARGET) {
526
- const draft = {
527
- target,
528
- date: dateOption.date,
529
- project: null,
530
- tasks: [],
531
- preview: null
532
- };
533
- sessionRef.current.logworkDraft = draft;
534
- clearSessionPreviews(sessionRef.current);
535
- sessionRef.current.activeTarget = target;
536
- setWizard((current) => ({
537
- ...current,
538
- step: 'edit_tasks',
539
- selectedDate: dateOption.date,
540
- selectedProject: null,
541
- tasks: [],
542
- preview: null,
543
- previews: {}
544
- }));
545
- setActivePanel({
546
- kind: 'logwork',
547
- title: 'Jira Worklog Draft',
548
- text: [
549
- formatDraftPreview(draft),
550
- '',
551
- 'Enter tasks one per line: +2 Feature Development | check ui/ux (SCB-213)',
552
- 'Empty Enter applies after preview is ready.',
553
- 'Type / for task commands. Esc cancels.'
554
- ].join('\n')
555
- });
556
- return;
557
- }
558
-
559
- setLoading('Fetching project memberships...');
560
- setActivePanel({
561
- kind: 'logwork',
562
- title: 'Select Project',
563
- text: `Loading projects for ${dateOption.date}...`
564
- });
565
- try {
566
- const projectsResult = await runWithInlineAuth(() => inkWorkflows.listLogworkProjects({ cwd }));
567
- const projectOptions = buildProjectOptions({
568
- projectsResult,
569
- weekly: wizard.weekly,
570
- date: dateOption.date
571
- });
572
- if (!projectOptions.length) {
573
- throw new Error('No Resource Optimiser project memberships found.');
574
- }
575
- setWizard((current) => ({
576
- ...current,
577
- step: 'select_project',
578
- target,
579
- selectedDate: dateOption.date,
580
- projectsResult,
581
- projectOptions,
582
- projectIndex: 0,
583
- selectedProject: null,
584
- tasks: [],
585
- preview: null
586
- }));
587
- setActivePanel({
588
- kind: 'logwork',
589
- title: 'Select Project',
590
- text: `Choose a project for ${dateOption.date}.`
591
- });
592
- } catch (error) {
593
- setActivePanel({
594
- kind: 'error',
595
- title: 'Project Error',
596
- text: error.message
597
- });
598
- } finally {
599
- setLoading('');
600
- }
601
- }, [cwd, inkWorkflows, runWithInlineAuth, wizard]);
602
-
603
- const selectWizardProject = useCallback((project) => {
604
- const target = wizard?.target || RO_TARGET;
605
- const draft = {
606
- target,
607
- date: wizard.selectedDate,
608
- project,
609
- tasks: [],
610
- preview: null
611
- };
612
- sessionRef.current.logworkDraft = draft;
613
- clearSessionPreviews(sessionRef.current);
614
- sessionRef.current.activeTarget = target;
615
- setWizard((current) => ({
616
- ...current,
617
- step: 'edit_tasks',
618
- target,
619
- selectedProject: project,
620
- tasks: [],
621
- preview: null,
622
- previews: {}
623
- }));
624
- setActivePanel({
625
- kind: 'logwork',
626
- title: target === BOTH_TARGET ? 'RO + Jira Draft' : 'Logwork Draft',
627
- text: [
628
- formatDraftPreview(draft),
629
- '',
630
- 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',
631
- 'Empty Enter applies after preview is ready.',
632
- 'Type / for task commands. Esc cancels.'
633
- ].join('\n')
634
- });
635
- }, [wizard]);
636
-
637
- const previewDraft = useCallback(async (nextWizard) => {
638
- const target = nextWizard.target || RO_TARGET;
639
- const draft = {
640
- target,
641
- date: nextWizard.selectedDate,
642
- project: nextWizard.selectedProject,
643
- tasks: nextWizard.tasks
644
- };
645
-
646
- if (!draft.tasks.length) {
647
- sessionRef.current.logworkDraft = draft;
648
- clearSessionPreviews(sessionRef.current);
649
- sessionRef.current.activeTarget = target;
650
- setWizard(nextWizard);
651
- setActivePanel({
652
- kind: 'logwork',
653
- title: target === JIRA_TARGET ? 'Jira Worklog Draft' : target === BOTH_TARGET ? 'RO + Jira Draft' : 'Logwork Draft',
654
- text: [
655
- formatDraftPreview(draft),
656
- '',
657
- 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)',
658
- 'Empty Enter applies after preview is ready.'
659
- ].join('\n')
660
- });
661
- return;
662
- }
663
-
664
- setLoading('Building live preview...');
665
- try {
666
- const text = buildLogworkText({
667
- date: draft.date,
668
- tasks: draft.tasks
669
- });
670
- const previews = {};
671
- if (previewTargets(target).includes(RO_TARGET)) {
672
- const projectOverrides = buildProjectOverrides({
673
- date: draft.date,
674
- tasks: draft.tasks,
675
- projectMemberId: draft.project.projectMemberId
676
- });
677
- previews[RO_TARGET] = await runWithInlineAuth(() => inkWorkflows.previewLogworkBatch({
678
- text,
679
- projectOverrides,
680
- cwd
681
- }));
682
- }
683
- if (previewTargets(target).includes(JIRA_TARGET)) {
684
- previews[JIRA_TARGET] = await runWithJiraAuthInstruction(() => inkWorkflows.previewJiraWorklogBatch({
685
- text
686
- }));
687
- }
688
- if (target === BOTH_TARGET && previews[RO_TARGET] && previews[JIRA_TARGET]) {
689
- previews[RO_TARGET] = enrichRoPreviewWithJiraProjects(previews[RO_TARGET], previews[JIRA_TARGET]);
690
- }
691
- const preview = target === JIRA_TARGET ? previews[JIRA_TARGET] : previews[RO_TARGET];
692
- const withPreview = {
693
- ...draft,
694
- preview,
695
- previews
696
- };
697
- sessionRef.current.logworkDraft = withPreview;
698
- sessionRef.current.activeTarget = target;
699
- for (const previewTarget of previewTargets(target)) {
700
- setSessionPreview(sessionRef.current, previewTarget, previews[previewTarget] || null);
701
- }
702
- setWizard({
703
- ...nextWizard,
704
- target,
705
- preview,
706
- previews
707
- });
708
- const hasBlockedJira = previews[JIRA_TARGET]?.entries?.some((entry) => entry.status !== 'ready');
709
- setActivePanel({
710
- kind: preview?.unresolvedEntries?.length || preview?.errors?.length || hasBlockedJira ? 'error' : 'logwork',
711
- title: target === JIRA_TARGET ? 'Jira Worklog Preview' : target === BOTH_TARGET ? 'RO + Jira Preview' : 'Logwork Preview',
712
- text: [
713
- formatDraftPreview(withPreview),
714
- '',
715
- 'Empty Enter applies this preview.',
716
- 'Type another +hours task to add more.',
717
- '/remove selects tasks to delete.'
718
- ].join('\n')
719
- });
720
- return preview;
721
- } catch (error) {
722
- setActivePanel({
723
- kind: 'error',
724
- title: 'Preview Error',
725
- text: error.message
726
- });
727
- } finally {
728
- setLoading('');
729
- }
730
- }, [cwd, inkWorkflows, runWithInlineAuth, runWithJiraAuthInstruction]);
731
-
732
- const saveCurrentDraft = useCallback(async () => {
733
- if (wizard?.target === JIRA_TARGET) {
734
- setActivePanel({
735
- kind: 'logwork',
736
- title: 'Save Draft',
737
- text: 'Saved drafts are only supported for Resource Optimiser logwork in this version.'
738
- });
739
- return null;
740
- }
741
-
742
- if (!wizard?.selectedDate || !wizard?.selectedProject || !wizard?.tasks?.length) {
743
- setActivePanel({
744
- kind: 'logwork',
745
- title: 'Save Draft',
746
- text: 'Add at least one task before saving a draft.'
747
- });
748
- return null;
749
- }
750
-
751
- try {
752
- const saved = await saveManualDraft({
753
- id: wizard.activeDraftId,
754
- cwd,
755
- date: wizard.selectedDate,
756
- project: wizard.selectedProject,
757
- tasks: wizard.tasks,
758
- latestPreviewStatus: wizard.preview?.status
759
- }, { cwd });
760
- setWizard((current) => ({
761
- ...current,
762
- activeDraftId: saved.id
763
- }));
764
- setActivePanel({
765
- kind: 'success',
766
- title: 'Draft Saved',
767
- text: [
768
- `Saved ${saved.tasks.length} task${saved.tasks.length === 1 ? '' : 's'} locally.`,
769
- formatManualDraftLabel(saved),
770
- '',
771
- 'Continue editing, press empty Enter to apply, or type /drafts.'
772
- ].join('\n')
773
- });
774
- return saved;
775
- } catch (error) {
776
- setActivePanel({
777
- kind: 'error',
778
- title: 'Save Draft Error',
779
- text: error.message
780
- });
781
- return null;
782
- }
783
- }, [cwd, wizard]);
784
-
785
- const openDraftPicker = useCallback(async (source = 'editor') => {
786
- setLoading('Loading saved drafts...');
787
- try {
788
- const drafts = await loadManualDrafts({ cwd });
789
- if (!drafts.length) {
790
- setActivePanel({
791
- kind: 'logwork',
792
- title: 'Saved Drafts',
793
- text: 'No saved drafts found.'
794
- });
795
- return;
796
- }
797
- setWizard((current) => ({
798
- ...current,
799
- step: 'select_draft',
800
- drafts,
801
- draftIndex: 0,
802
- draftSource: source
803
- }));
804
- setActivePanel({
805
- kind: 'logwork',
806
- title: 'Saved Drafts',
807
- text: 'Enter resumes · d deletes · Esc returns.'
808
- });
809
- } catch (error) {
810
- setActivePanel({
811
- kind: 'error',
812
- title: 'Draft Error',
813
- text: error.message
814
- });
815
- } finally {
816
- setLoading('');
817
- }
818
- }, [cwd]);
819
-
820
- const resumeManualDraft = useCallback(async (draft) => {
821
- setLoading('Resuming saved draft...');
822
- try {
823
- const weekly = await runWithInlineAuth(() => inkWorkflows.queryLogwork({
824
- period: 'this_week',
825
- cwd,
826
- includeEntries: false
827
- }));
828
- const dateOptions = buildWeekDateOptions(weekly);
829
- const projectsResult = await runWithInlineAuth(() => inkWorkflows.listLogworkProjects({ cwd }));
830
- const projectOptions = buildProjectOptions({
831
- projectsResult,
832
- weekly,
833
- date: draft.date
834
- });
835
- const selectedProject = projectOptions.find((project) => sameProjectIdentity(project, draft.project)) || draft.project;
836
- const nextWizard = {
837
- step: 'edit_tasks',
838
- target: RO_TARGET,
839
- weekly,
840
- dateOptions,
841
- dateIndex: Math.max(0, dateOptions.findIndex((option) => option.date === draft.date)),
842
- projectsResult,
843
- projectOptions,
844
- projectIndex: Math.max(0, projectOptions.findIndex((project) => sameProjectIdentity(project, selectedProject))),
845
- selectedDate: draft.date,
846
- selectedProject,
847
- tasks: draft.tasks,
848
- preview: null,
849
- activeDraftId: draft.id
850
- };
851
- sessionRef.current.logworkDraft = {
852
- target: RO_TARGET,
853
- date: draft.date,
854
- project: selectedProject,
855
- tasks: draft.tasks,
856
- preview: null
857
- };
858
- clearSessionPreviews(sessionRef.current);
859
- sessionRef.current.activeTarget = RO_TARGET;
860
- await previewDraft(nextWizard);
861
- } catch (error) {
862
- setActivePanel({
863
- kind: 'error',
864
- title: 'Resume Draft Error',
865
- text: error.message
866
- });
867
- } finally {
868
- setLoading('');
869
- }
870
- }, [cwd, inkWorkflows, previewDraft, runWithInlineAuth]);
871
-
872
- const deleteSavedDraft = useCallback(async (draft) => {
873
- const ok = await prompts.confirm(`Delete saved draft "${formatManualDraftLabel(draft)}"?`, false);
874
- if (!ok) {
875
- return;
876
- }
877
- await deleteManualDraft(draft.id);
878
- const drafts = await loadManualDrafts({ cwd });
879
- setWizard((current) => ({
880
- ...current,
881
- drafts,
882
- draftIndex: 0
883
- }));
884
- setActivePanel({
885
- kind: 'success',
886
- title: 'Draft Deleted',
887
- text: drafts.length ? 'Draft deleted. Choose another saved draft or start new.' : 'Draft deleted. No saved drafts remain.'
888
- });
889
- }, [cwd, prompts]);
890
-
891
- const returnFromDraftPicker = useCallback(() => {
892
- if (wizard?.draftSource === 'start') {
893
- startNewLogworkWizard(RO_TARGET);
894
- return;
895
- }
896
- setWizard((current) => ({
897
- ...current,
898
- step: 'edit_tasks',
899
- draftSource: undefined
900
- }));
901
- const draft = {
902
- target: wizard?.target || RO_TARGET,
903
- date: wizard?.selectedDate,
904
- project: wizard?.selectedProject,
905
- tasks: wizard?.tasks || [],
906
- preview: wizard?.preview
907
- };
908
- setActivePanel({
909
- kind: 'logwork',
910
- title: wizard?.preview ? 'Logwork Preview' : 'Logwork Draft',
911
- text: [
912
- formatDraftPreview(draft),
913
- '',
914
- 'Type / for task commands. Empty Enter applies when ready.'
915
- ].join('\n')
916
- });
917
- }, [startNewLogworkWizard, wizard]);
918
-
919
- const cancelCurrentLogwork = useCallback(async () => {
920
- if (wizard?.tasks?.length) {
921
- const ok = await prompts.confirm('Discard this unsaved logwork session?', false);
922
- if (!ok) {
923
- return;
924
- }
925
- }
926
- sessionRef.current.logworkDraft = null;
927
- clearSessionPreviews(sessionRef.current);
928
- setWizard(null);
929
- setActivePanel({
930
- kind: 'idle',
931
- title: 'Ready',
932
- text: 'Logwork session discarded. Type / for commands.'
933
- });
934
- }, [prompts, wizard]);
935
-
936
- const applyWizardPreview = useCallback(async () => {
937
- const target = wizard.target || RO_TARGET;
938
- if (!wizard.tasks.length) {
939
- setActivePanel({
940
- kind: 'logwork',
941
- title: 'Logwork Draft',
942
- 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).'
943
- });
944
- return;
945
- }
946
-
947
- const targets = previewTargets(target);
948
- const missingPreview = targets.some((previewTarget) => !sessionPreview(sessionRef.current, previewTarget));
949
- if (missingPreview) {
950
- await previewDraft(wizard);
951
- return;
952
- }
953
-
954
- for (const previewTarget of targets) {
955
- const applyState = canApplyPreview(sessionPreview(sessionRef.current, previewTarget), { target: previewTarget });
956
- if (!applyState.ok) {
957
- setActivePanel({
958
- kind: 'error',
959
- title: 'Apply Blocked',
960
- text: `${targetLabel(previewTarget)}: ${applyState.reason}`
961
- });
962
- return;
963
- }
964
- }
965
-
966
- await executeCommand({ type: 'apply', target });
967
- if (!hasSessionPreview(sessionRef.current)) {
968
- setWizard(null);
969
- }
970
- }, [executeCommand, previewDraft, wizard]);
971
-
972
- const restoreTaskEditorPanel = useCallback((nextWizard = wizard) => {
973
- const target = nextWizard.target || RO_TARGET;
974
- const draft = {
975
- target,
976
- date: nextWizard.selectedDate,
977
- project: nextWizard.selectedProject,
978
- tasks: nextWizard.tasks,
979
- preview: nextWizard.preview,
980
- previews: nextWizard.previews
981
- };
982
- setActivePanel({
983
- kind: 'logwork',
984
- title: nextWizard.preview ? `${targetLabel(target)} Preview` : `${targetLabel(target)} Draft`,
985
- text: [
986
- formatDraftPreview(draft),
987
- '',
988
- 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)',
989
- nextWizard.preview ? 'Type another +hours task to add more.' : 'Empty Enter applies after preview is ready.',
990
- 'Type / for task commands. Esc cancels.'
991
- ].join('\n')
992
- });
993
- }, [wizard]);
994
-
995
- const cancelRemoveTasks = useCallback(() => {
996
- setWizard((current) => ({
997
- ...current,
998
- step: 'edit_tasks',
999
- removeIndex: 0,
1000
- removeSelectedIndexes: []
1001
- }));
1002
- restoreTaskEditorPanel();
1003
- }, [restoreTaskEditorPanel]);
1004
-
1005
- const confirmRemoveTasks = useCallback(async (selectedIndexes) => {
1006
- if (!selectedIndexes.length) {
1007
- setActivePanel({
1008
- kind: 'logwork',
1009
- title: 'Remove Tasks',
1010
- text: 'No tasks selected. Use Space to select tasks, Enter to remove, Esc to cancel.'
1011
- });
1012
- return;
1013
- }
1014
-
1015
- const ok = await prompts.confirm(`Remove ${selectedIndexes.length} selected task${selectedIndexes.length === 1 ? '' : 's'}?`);
1016
- if (!ok) {
1017
- cancelRemoveTasks();
1018
- return;
1019
- }
1020
-
1021
- await previewDraft({
1022
- ...wizard,
1023
- step: 'edit_tasks',
1024
- tasks: removeDraftTasks(wizard.tasks, selectedIndexes),
1025
- preview: null,
1026
- removeIndex: 0,
1027
- removeSelectedIndexes: []
1028
- });
1029
- }, [cancelRemoveTasks, previewDraft, prompts, wizard]);
1030
-
1031
- const submitWizardTaskInput = useCallback(async (value) => {
1032
- const text = String(value || '').trim();
1033
- setInputValue('');
1034
-
1035
- if (!text) {
1036
- await applyWizardPreview();
1037
- return;
1038
- }
1039
-
1040
- if (text === '/diagnostics') {
1041
- await executeCommand({ type: 'diagnostics' });
1042
- return;
1043
- }
1044
-
1045
- if (text === '/cancel' || text === '/discard') {
1046
- await cancelCurrentLogwork();
1047
- return;
1048
- }
1049
-
1050
- if (text === '/back') {
1051
- sessionRef.current.logworkDraft = null;
1052
- clearSessionPreviews(sessionRef.current);
1053
- if (wizard.target === JIRA_TARGET) {
1054
- setWizard((current) => ({
1055
- ...current,
1056
- step: 'select_date',
1057
- selectedDate: null,
1058
- tasks: [],
1059
- preview: null,
1060
- previews: {}
1061
- }));
1062
- setActivePanel({
1063
- kind: 'logwork',
1064
- title: 'Select Date',
1065
- text: 'Choose a day for this Jira worklog session.'
1066
- });
1067
- } else {
1068
- setWizard((current) => ({
1069
- ...current,
1070
- step: 'select_project',
1071
- selectedProject: null,
1072
- tasks: [],
1073
- preview: null,
1074
- previews: {}
1075
- }));
1076
- setActivePanel({
1077
- kind: 'logwork',
1078
- title: 'Select Project',
1079
- text: `Choose a project for ${wizard.selectedDate}.`
1080
- });
1081
- }
1082
- return;
1083
- }
1084
-
1085
- if (text === '/clear') {
1086
- const ok = !wizard.tasks.length || await prompts.confirm('Clear all current tasks?', false);
1087
- if (!ok) {
1088
- restoreTaskEditorPanel();
1089
- return;
1090
- }
1091
- await previewDraft({
1092
- ...wizard,
1093
- tasks: [],
1094
- preview: null
1095
- });
1096
- return;
1097
- }
1098
-
1099
- if (text === '/save') {
1100
- await saveCurrentDraft();
1101
- return;
1102
- }
1103
-
1104
- if (text === '/drafts') {
1105
- await openDraftPicker('editor');
1106
- return;
1107
- }
1108
-
1109
- if (text === '/edit') {
1110
- if (!wizard.tasks.length) {
1111
- setActivePanel({
1112
- kind: 'error',
1113
- title: 'Edit Task',
1114
- text: 'No tasks to edit.'
1115
- });
1116
- return;
1117
- }
1118
- setWizard((current) => ({
1119
- ...current,
1120
- step: 'edit_task_select',
1121
- editIndex: 0,
1122
- replacementError: ''
1123
- }));
1124
- setActivePanel({
1125
- kind: 'logwork',
1126
- title: 'Edit Task',
1127
- text: 'Select a task to replace. Esc returns to the editor.'
1128
- });
1129
- return;
1130
- }
1131
-
1132
- if (text === '/remove') {
1133
- if (!wizard.tasks.length) {
1134
- setActivePanel({
1135
- kind: 'error',
1136
- title: 'Remove Task',
1137
- text: 'No tasks to remove.'
1138
- });
1139
- return;
1140
- }
1141
- setWizard((current) => ({
1142
- ...current,
1143
- step: 'remove_tasks',
1144
- removeIndex: 0,
1145
- removeSelectedIndexes: []
1146
- }));
1147
- setActivePanel({
1148
- kind: 'logwork',
1149
- title: 'Remove Tasks',
1150
- text: 'Select tasks to remove. Space toggles, Enter confirms, Esc cancels.'
1151
- });
1152
- return;
1153
- }
1154
-
1155
- if (text.startsWith('/remove ')) {
1156
- const index = Number(text.slice('/remove '.length).trim());
1157
- if (!Number.isInteger(index)) {
1158
- setActivePanel({
1159
- kind: 'error',
1160
- title: 'Remove Task',
1161
- text: 'Usage: /remove <task number>'
1162
- });
1163
- return;
1164
- }
1165
- if (index < 1 || index > wizard.tasks.length) {
1166
- setActivePanel({
1167
- kind: 'error',
1168
- title: 'Remove Task',
1169
- text: `No task found at index ${index}.`
1170
- });
1171
- return;
1172
- }
1173
- await previewDraft({
1174
- ...wizard,
1175
- tasks: wizard.tasks.filter((_, currentIndex) => currentIndex !== index - 1),
1176
- preview: null
1177
- });
1178
- return;
1179
- }
1180
-
1181
- if (text === '/done') {
1182
- if (!wizard.tasks.length) {
1183
- setActivePanel({
1184
- kind: 'error',
1185
- title: 'Logwork Draft',
1186
- text: 'Add at least one task before /done.'
1187
- });
1188
- return;
1189
- }
1190
- await previewDraft(wizard);
1191
- return;
1192
- }
1193
-
1194
- if (text === '/apply') {
1195
- await applyWizardPreview();
1196
- return;
1197
- }
1198
-
1199
- if (text.startsWith('/')) {
1200
- setActivePanel({
1201
- kind: 'error',
1202
- title: 'Logwork Draft',
1203
- text: `Unknown logwork command: ${text}`
1204
- });
1205
- return;
1206
- }
1207
-
1208
- try {
1209
- const task = parseTaskLine(text);
1210
- await previewDraft({
1211
- ...wizard,
1212
- tasks: [...wizard.tasks, task],
1213
- preview: null
1214
- });
1215
- } catch (error) {
1216
- setActivePanel({
1217
- kind: 'error',
1218
- title: 'Task Error',
1219
- text: error.message
1220
- });
1221
- }
1222
- }, [
1223
- applyWizardPreview,
300
+ const {
301
+ startNewLogworkWizard,
302
+ startAuthTargetPicker,
303
+ startReconciliationPeriodPicker,
304
+ startLogworkWizard,
305
+ startMcpClientPicker,
306
+ cancelMcpClientPicker,
307
+ selectWizardDate,
308
+ selectWizardProject,
309
+ previewDraft,
1224
310
  cancelCurrentLogwork,
1225
- executeCommand,
311
+ applyWizardPreview,
312
+ restoreTaskEditorPanel
313
+ } = useLogworkWizard({
314
+ cwd,
315
+ sessionRef,
316
+ wizard,
317
+ setWizard,
318
+ setActivePanel,
319
+ setLoading,
320
+ setInputValue,
321
+ setSelectedIndex,
322
+ inkWorkflows,
323
+ prompts,
324
+ runWithInlineAuth,
325
+ runWithJiraAuthInstruction,
326
+ executeCommand
327
+ });
328
+
329
+ const {
330
+ saveCurrentDraft,
1226
331
  openDraftPicker,
332
+ resumeManualDraft,
333
+ deleteSavedDraft,
334
+ returnFromDraftPicker
335
+ } = useDraftActions({
336
+ cwd,
337
+ sessionRef,
338
+ wizard,
339
+ setWizard,
340
+ setActivePanel,
341
+ setLoading,
342
+ inkWorkflows,
343
+ prompts,
344
+ runWithInlineAuth,
1227
345
  previewDraft,
346
+ startNewLogworkWizard
347
+ });
348
+
349
+ const {
350
+ cancelRemoveTasks,
351
+ confirmRemoveTasks,
352
+ submitWizardTaskInput,
353
+ selectTaskForEdit,
354
+ submitTaskReplacement
355
+ } = useTaskEditorActions({
356
+ sessionRef,
357
+ wizard,
358
+ setWizard,
359
+ setActivePanel,
360
+ setInputValue,
1228
361
  prompts,
362
+ executeCommand,
363
+ previewDraft,
364
+ applyWizardPreview,
365
+ cancelCurrentLogwork,
1229
366
  restoreTaskEditorPanel,
1230
367
  saveCurrentDraft,
1231
- wizard
1232
- ]);
1233
-
1234
- const selectTaskForEdit = useCallback((index) => {
1235
- const task = wizard.tasks[index];
1236
- setInputValue('');
1237
- setWizard((current) => ({
1238
- ...current,
1239
- step: 'edit_task_replace',
1240
- editIndex: index,
1241
- replacementError: ''
1242
- }));
1243
- setActivePanel({
1244
- kind: 'logwork',
1245
- title: 'Replace Task',
1246
- text: [
1247
- `Replacing ${index + 1}. +${formatTaskHours(task.hours)} ${task.taskName}`,
1248
- 'Enter replacement line using format: +2 Feature Development | check ui/ux',
1249
- 'Esc returns without changing the task.'
1250
- ].join('\n')
1251
- });
1252
- }, [wizard]);
1253
-
1254
- const submitTaskReplacement = useCallback(async (value) => {
1255
- const text = String(value || '').trim();
1256
- setInputValue('');
1257
- if (!text) {
1258
- setActivePanel({
1259
- kind: 'error',
1260
- title: 'Replace Task',
1261
- text: 'Replacement task is empty. Use format: +2 Feature Development | check ui/ux'
1262
- });
1263
- return;
1264
- }
1265
- if (text === '/cancel') {
1266
- setWizard((current) => ({
1267
- ...current,
1268
- step: 'edit_tasks',
1269
- replacementError: ''
1270
- }));
1271
- restoreTaskEditorPanel();
1272
- return;
1273
- }
1274
- try {
1275
- const task = parseTaskLine(text);
1276
- await previewDraft({
1277
- ...wizard,
1278
- step: 'edit_tasks',
1279
- tasks: replaceDraftTask(wizard.tasks, wizard.editIndex, task),
1280
- preview: null,
1281
- replacementError: ''
1282
- });
1283
- } catch (error) {
1284
- setWizard((current) => ({
1285
- ...current,
1286
- replacementError: error.message
1287
- }));
1288
- setActivePanel({
1289
- kind: 'error',
1290
- title: 'Replace Task',
1291
- text: error.message
1292
- });
1293
- }
1294
- }, [previewDraft, restoreTaskEditorPanel, wizard]);
368
+ openDraftPicker
369
+ });
1295
370
 
1296
371
  const submitInput = useCallback((value) => {
1297
372
  const text = String(value || '');
1298
373
  const trimmed = text.trim();
1299
374
 
1300
- if (pendingConfirm) {
375
+ if (pendingConfirm || pendingSelect) {
1301
376
  return;
1302
377
  }
1303
378
 
@@ -1375,6 +450,7 @@ export function ManualApp({
1375
450
  executeCommand,
1376
451
  inputValue,
1377
452
  pendingConfirm,
453
+ pendingSelect,
1378
454
  authActive,
1379
455
  authPrompt,
1380
456
  selectedIndex,
@@ -1396,259 +472,43 @@ export function ManualApp({
1396
472
  loading ? h(LoadingLine, { message: loading }) : null,
1397
473
  h(StatusBar, {
1398
474
  preview: activeSessionPreview(sessionRef.current),
1399
- dryRun: process.env.LOGWORK_DRY_RUN === '1',
475
+ dryRun: isDryRun(),
1400
476
  wizard,
1401
477
  authPrompt: authPrompt || (authActive ? { step: 'working' } : null)
1402
478
  }),
1403
- authPrompt
1404
- ? h(AuthPrompt, {
1405
- prompt: authPrompt,
1406
- inputValue,
1407
- onInputChange(value) {
1408
- setInputValue(value);
1409
- },
1410
- onPromptChange(nextPrompt) {
1411
- setAuthPrompt(nextPrompt);
1412
- },
1413
- onResolve: resolveAuthPrompt,
1414
- onReject: rejectAuthPrompt
1415
- })
1416
- : authActive
1417
- ? h(AuthPrompt, {
1418
- prompt: { step: 'working' },
1419
- inputValue,
1420
- onInputChange(value) {
1421
- setInputValue(value);
1422
- },
1423
- onPromptChange(nextPrompt) {
1424
- setAuthPrompt(nextPrompt);
1425
- },
1426
- onResolve: resolveAuthPrompt,
1427
- onReject: rejectAuthPrompt
1428
- })
1429
- : pendingConfirm
1430
- ? h(ConfirmDialog, {
1431
- message: pendingConfirm.message,
1432
- initialValue: pendingConfirm.initialValue,
1433
- onResolve: resolveConfirm
1434
- })
1435
- : wizard?.step === 'select_logwork_target'
1436
- ? h(TargetPicker, {
1437
- title: 'Pick logwork target',
1438
- targets: LOGWORK_TARGETS,
1439
- selectedIndex: wizard.targetIndex || 0,
1440
- onChange(index) {
1441
- setWizard((current) => ({
1442
- ...current,
1443
- targetIndex: index
1444
- }));
1445
- },
1446
- onSelect(target) {
1447
- startLogworkWizard(target.key);
1448
- },
1449
- onCancel() {
1450
- setWizard(null);
1451
- setActivePanel({
1452
- kind: 'idle',
1453
- title: 'Ready',
1454
- text: 'Type / for commands.'
1455
- });
1456
- }
1457
- })
1458
- : wizard?.step === 'select_auth_target'
1459
- ? h(TargetPicker, {
1460
- title: 'Pick auth target',
1461
- targets: AUTH_TARGETS,
1462
- selectedIndex: wizard.targetIndex || 0,
1463
- onChange(index) {
1464
- setWizard((current) => ({
1465
- ...current,
1466
- targetIndex: index
1467
- }));
1468
- },
1469
- onSelect(target) {
1470
- executeCommand({
1471
- type: 'auth',
1472
- target: target.key
1473
- });
1474
- },
1475
- onCancel() {
1476
- setWizard(null);
1477
- setActivePanel({
1478
- kind: 'idle',
1479
- title: 'Ready',
1480
- text: 'Type / for commands.'
1481
- });
1482
- }
1483
- })
1484
- : wizard?.step === 'select_reconciliation_period'
1485
- ? h(TargetPicker, {
1486
- title: 'Pick reconciliation period',
1487
- targets: RECONCILIATION_PERIOD_OPTIONS,
1488
- selectedIndex: wizard.periodIndex || 0,
1489
- onChange(index) {
1490
- setWizard((current) => ({
1491
- ...current,
1492
- periodIndex: index
1493
- }));
1494
- },
1495
- onSelect(period) {
1496
- executeCommand({
1497
- type: 'reconcile',
1498
- period: period.key
1499
- });
1500
- },
1501
- onCancel() {
1502
- setWizard(null);
1503
- setActivePanel({
1504
- kind: 'idle',
1505
- title: 'Ready',
1506
- text: 'Type / for commands.'
1507
- });
1508
- }
1509
- })
1510
- : wizard?.step === 'select_draft'
1511
- ? h(DraftPicker, {
1512
- drafts: wizard.drafts || [],
1513
- selectedIndex: wizard.draftIndex || 0,
1514
- includeStartNew: wizard.draftSource === 'start',
1515
- onChange(index) {
1516
- setWizard((current) => ({
1517
- ...current,
1518
- draftIndex: index
1519
- }));
1520
- },
1521
- onResume(draft) {
1522
- resumeManualDraft(draft);
1523
- },
1524
- onDelete(draft) {
1525
- deleteSavedDraft(draft);
1526
- },
1527
- onStartNew() {
1528
- startNewLogworkWizard(RO_TARGET);
1529
- },
1530
- onCancel() {
1531
- returnFromDraftPicker();
1532
- }
1533
- })
1534
- : wizard?.step === 'select_date'
1535
- ? h(DatePicker, {
1536
- options: wizard.dateOptions,
1537
- selectedIndex: wizard.dateIndex,
1538
- onChange(index) {
1539
- setWizard((current) => ({
1540
- ...current,
1541
- dateIndex: index
1542
- }));
1543
- },
1544
- onSelect(option) {
1545
- selectWizardDate(option);
1546
- }
1547
- })
1548
- : wizard?.step === 'select_project'
1549
- ? h(ProjectPicker, {
1550
- options: wizard.projectOptions,
1551
- selectedIndex: wizard.projectIndex,
1552
- onChange(index) {
1553
- setWizard((current) => ({
1554
- ...current,
1555
- projectIndex: index
1556
- }));
1557
- },
1558
- onSelect(project) {
1559
- selectWizardProject(project);
1560
- }
1561
- })
1562
- : wizard?.step === 'select_mcp_client'
1563
- ? h(McpClientPicker, {
1564
- selectedIndex: wizard.mcpClientIndex || 0,
1565
- onChange(index) {
1566
- setWizard((current) => ({
1567
- ...current,
1568
- mcpClientIndex: index
1569
- }));
1570
- },
1571
- onSelect(client) {
1572
- executeCommand({
1573
- type: 'mcp',
1574
- client: client.key
1575
- });
1576
- },
1577
- onCancel() {
1578
- cancelMcpClientPicker();
1579
- }
1580
- })
1581
- : wizard?.step === 'remove_tasks'
1582
- ? h(TaskRemovePicker, {
1583
- tasks: wizard.tasks,
1584
- selectedIndex: wizard.removeIndex || 0,
1585
- selectedIndexes: wizard.removeSelectedIndexes || [],
1586
- onChange(index) {
1587
- setWizard((current) => ({
1588
- ...current,
1589
- removeIndex: index
1590
- }));
1591
- },
1592
- onToggle(index) {
1593
- setWizard((current) => ({
1594
- ...current,
1595
- removeSelectedIndexes: toggleTaskSelection(current.removeSelectedIndexes || [], index)
1596
- }));
1597
- },
1598
- onSubmit(indexes) {
1599
- confirmRemoveTasks(indexes);
1600
- },
1601
- onCancel() {
1602
- cancelRemoveTasks();
1603
- }
1604
- })
1605
- : wizard?.step === 'edit_task_select'
1606
- ? h(TaskEditPicker, {
1607
- tasks: wizard.tasks,
1608
- selectedIndex: wizard.editIndex || 0,
1609
- onChange(index) {
1610
- setWizard((current) => ({
1611
- ...current,
1612
- editIndex: index
1613
- }));
1614
- },
1615
- onSelect(index) {
1616
- selectTaskForEdit(index);
1617
- },
1618
- onCancel() {
1619
- setWizard((current) => ({
1620
- ...current,
1621
- step: 'edit_tasks'
1622
- }));
1623
- restoreTaskEditorPanel();
1624
- }
1625
- })
1626
- : h(CommandInput, {
1627
- value: inputValue,
1628
- onChange(value) {
1629
- setInputValue(value);
1630
- setSelectedIndex(0);
1631
- },
1632
- onSubmit: submitInput,
1633
- selectedIndex,
1634
- setSelectedIndex,
1635
- mode: wizard?.step === 'edit_tasks' ? 'task' : wizard?.step === 'edit_task_replace' ? 'task-edit' : 'command',
1636
- onEscape() {
1637
- if (!wizard) {
1638
- exit();
1639
- return;
1640
- }
1641
- if (wizard?.step === 'edit_tasks') {
1642
- cancelCurrentLogwork();
1643
- return;
1644
- }
1645
- if (wizard?.step === 'edit_task_replace') {
1646
- setWizard((current) => ({
1647
- ...current,
1648
- step: 'edit_tasks'
1649
- }));
1650
- restoreTaskEditorPanel();
1651
- }
1652
- }
1653
- }));
479
+ renderManualInteraction({
480
+ authPrompt,
481
+ authActive,
482
+ pendingConfirm,
483
+ pendingSelect,
484
+ wizard,
485
+ inputValue,
486
+ selectedIndex,
487
+ setInputValue,
488
+ setSelectedIndex,
489
+ setAuthPrompt,
490
+ setWizard,
491
+ setActivePanel,
492
+ resolveAuthPrompt,
493
+ rejectAuthPrompt,
494
+ resolveConfirm,
495
+ changeSelect,
496
+ resolveSelect,
497
+ exit,
498
+ executeCommand,
499
+ submitInput,
500
+ startLogworkWizard,
501
+ startNewLogworkWizard,
502
+ selectWizardDate,
503
+ selectWizardProject,
504
+ cancelMcpClientPicker,
505
+ cancelCurrentLogwork,
506
+ restoreTaskEditorPanel,
507
+ resumeManualDraft,
508
+ deleteSavedDraft,
509
+ returnFromDraftPicker,
510
+ confirmRemoveTasks,
511
+ cancelRemoveTasks,
512
+ selectTaskForEdit
513
+ }));
1654
514
  }