loop-task 2.1.4 → 2.1.7

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 (148) hide show
  1. package/dist/app/App.js +119 -0
  2. package/dist/{tui → app}/index.js +4 -5
  3. package/dist/app/providers/index.js +1 -0
  4. package/dist/cli/import-writer.js +1 -1
  5. package/dist/cli.js +9 -9
  6. package/dist/client/cli-format.js +7 -0
  7. package/dist/client/commands.js +2 -164
  8. package/dist/client/ipc.js +3 -3
  9. package/dist/client/project-commands.js +159 -0
  10. package/dist/core/{command-runner.js → command/command-runner.js} +3 -3
  11. package/dist/core/{foreground-loop.js → foreground/index.js} +4 -4
  12. package/dist/core/{log-rotator.js → logging/log-rotator.js} +1 -1
  13. package/dist/core/loop/chain-executor.js +67 -0
  14. package/dist/core/loop/delay-utils.js +65 -0
  15. package/dist/core/loop/loop-controller.js +191 -0
  16. package/dist/core/loop/loop-runner.js +132 -0
  17. package/dist/core/loop/run-executor.js +90 -0
  18. package/dist/core/loop/types.js +1 -0
  19. package/dist/daemon/daemon-log.js +1 -1
  20. package/dist/daemon/http/helpers.js +81 -0
  21. package/dist/daemon/http/openapi.js +92 -0
  22. package/dist/daemon/http/route-loops.js +218 -0
  23. package/dist/daemon/http/route-misc.js +36 -0
  24. package/dist/daemon/http/route-projects.js +55 -0
  25. package/dist/daemon/http/route-tasks.js +53 -0
  26. package/dist/daemon/http/routes.js +17 -0
  27. package/dist/daemon/http/server.js +64 -0
  28. package/dist/daemon/http/sse.js +36 -0
  29. package/dist/daemon/index.js +7 -9
  30. package/dist/{ipc/handlers → daemon/ipc}/logs-stream.js +1 -1
  31. package/dist/daemon/managers/loop-entry.js +24 -0
  32. package/dist/daemon/{manager.js → managers/loop-manager.js} +32 -152
  33. package/dist/daemon/managers/loop-options.js +16 -0
  34. package/dist/daemon/managers/loop-serialization.js +43 -0
  35. package/dist/daemon/{projects.js → managers/project-manager.js} +6 -4
  36. package/dist/daemon/{task-manager.js → managers/task-manager.js} +5 -3
  37. package/dist/daemon/server/handlers/index.js +86 -0
  38. package/dist/daemon/server/handlers/log-handlers.js +130 -0
  39. package/dist/daemon/server/handlers/loop-handlers.js +54 -0
  40. package/dist/daemon/server/handlers/project-handlers.js +37 -0
  41. package/dist/daemon/server/handlers/task-handlers.js +20 -0
  42. package/dist/daemon/server/index.js +113 -0
  43. package/dist/daemon/{spawner.js → spawner/index.js} +4 -4
  44. package/dist/daemon/{state.js → state/index.js} +4 -4
  45. package/dist/daemon/{file-watcher.js → watcher/index.js} +8 -6
  46. package/dist/duration.js +1 -1
  47. package/dist/{tui/state.js → entities/loops/filters.js} +0 -76
  48. package/dist/entities/loops/index.js +1 -0
  49. package/dist/entities/projects/filters.js +69 -0
  50. package/dist/entities/projects/index.js +1 -0
  51. package/dist/entities/tasks/filters.js +41 -0
  52. package/dist/entities/tasks/index.js +1 -0
  53. package/dist/{tui/components → features/chain-editor}/ChainEditor.js +7 -5
  54. package/dist/features/code-editor/CodeEditorModal.js +84 -0
  55. package/dist/{tui/components → features/code-editor}/CodeEditorPreview.js +3 -3
  56. package/dist/features/code-editor/useEditorKeyboard.js +147 -0
  57. package/dist/features/code-editor/useModalDimensions.js +9 -0
  58. package/dist/{tui → features/commands}/commands.js +2 -2
  59. package/dist/features/commands/useCommandHandlers.js +147 -0
  60. package/dist/features/commands/useContextualActions.js +75 -0
  61. package/dist/features/commands/useGlobalShortcuts.js +152 -0
  62. package/dist/features/forms/FormRouter.js +44 -0
  63. package/dist/{tui/components → features/overlays}/ContextHelpModal.js +3 -3
  64. package/dist/{tui/components → features/overlays}/ExportModal.js +3 -3
  65. package/dist/{tui/components → features/overlays}/HelpGuideModal.js +3 -3
  66. package/dist/{tui/components → features/overlays}/HelpModal.js +3 -3
  67. package/dist/features/overlays/OverlayStack.js +9 -0
  68. package/dist/{tui/components → features/overlays}/ProjectsModal.js +2 -2
  69. package/dist/{tui/components → features/overlays}/TaskPickerModal.js +2 -2
  70. package/dist/{tui/components → features/overlays}/WelcomeScreen.js +5 -3
  71. package/dist/features/overlays/useOverlayStack.js +51 -0
  72. package/dist/features/state/useAppState.js +150 -0
  73. package/dist/logger.js +0 -1
  74. package/dist/loop-config.js +9 -4
  75. package/dist/{config → shared/config}/constants.js +1 -1
  76. package/dist/shared/container/index.js +22 -0
  77. package/dist/{tui → shared}/hooks/useBreakpoint.js +1 -1
  78. package/dist/shared/hooks/useInject.js +6 -0
  79. package/dist/{tui → shared}/hooks/useLogStream.js +7 -5
  80. package/dist/{hooks → shared/hooks}/useLoopFormValidation.js +2 -2
  81. package/dist/{tui → shared}/hooks/useLoopPolling.js +6 -4
  82. package/dist/shared/{useUndoRedo.js → hooks/useUndoRedo.js} +2 -4
  83. package/dist/shared/providers/InversifyProvider.js +6 -0
  84. package/dist/shared/services/export-service.js +43 -0
  85. package/dist/shared/services/log-service.js +28 -0
  86. package/dist/shared/services/loop-service.js +78 -0
  87. package/dist/shared/services/project-service.js +39 -0
  88. package/dist/shared/services/task-service.js +41 -0
  89. package/dist/shared/services/types.js +7 -0
  90. package/dist/{tui/components → shared/ui}/Button.js +1 -1
  91. package/dist/{tui/components → shared/ui}/DebugPanel.js +1 -1
  92. package/dist/{tui/components → shared/ui}/FocusableButton.js +1 -1
  93. package/dist/{tui/components → shared/ui}/FocusableInput.js +1 -1
  94. package/dist/{tui/components → shared/ui}/FocusableList.js +1 -1
  95. package/dist/{tui/components → shared/ui}/Modal.js +1 -1
  96. package/dist/{tui/components → shared/ui}/SelectModal.js +2 -2
  97. package/dist/{tui/components → shared/ui}/Toast.js +1 -1
  98. package/dist/shared/ui/state.js +9 -0
  99. package/dist/{tui → shared}/utils/paste.js +2 -2
  100. package/dist/{tui → shared}/utils/validation.js +1 -1
  101. package/dist/widgets/command-input/CommandDropdown.js +69 -0
  102. package/dist/widgets/command-input/CommandInput.js +129 -0
  103. package/dist/widgets/command-input/HintBar.js +9 -0
  104. package/dist/widgets/command-input/InputModes.js +68 -0
  105. package/dist/{tui/components → widgets/commands-browser}/CommandsBrowserModal.js +3 -3
  106. package/dist/{tui/components → widgets/header}/Header.js +3 -3
  107. package/dist/{tui/components → widgets/header}/TabBar.js +1 -1
  108. package/dist/{tui/components → widgets/left-panel}/LeftPanel.js +3 -3
  109. package/dist/{tui/components → widgets/left-panel}/Navigator.js +3 -3
  110. package/dist/{tui/components → widgets/left-panel}/ProjectsPage.js +9 -34
  111. package/dist/widgets/left-panel/ProjectsPageParts.js +34 -0
  112. package/dist/{tui/components → widgets/left-panel}/TaskBrowser.js +4 -4
  113. package/dist/{tui/components → widgets/log-modal}/LogModal.js +8 -6
  114. package/dist/widgets/loop-form/CreateForm.js +75 -0
  115. package/dist/widgets/loop-form/TextField.js +9 -0
  116. package/dist/{tui/components → widgets/loop-form}/WizardForm.js +5 -10
  117. package/dist/widgets/loop-form/useCreateSteps.js +107 -0
  118. package/dist/widgets/loop-form/useHandleComplete.js +74 -0
  119. package/dist/{tui/components → widgets/project-form}/ProjectForm.js +9 -7
  120. package/dist/{tui/components → widgets/right-panel}/Inspector.js +4 -4
  121. package/dist/{tui/components → widgets/right-panel}/RightPanel.js +4 -4
  122. package/dist/{tui/components → widgets/right-panel}/RunHistory.js +52 -4
  123. package/dist/{tui/components → widgets/task-form}/TaskForm.js +11 -9
  124. package/package.json +2 -1
  125. package/dist/core/loop-controller.js +0 -511
  126. package/dist/daemon/http-server.js +0 -599
  127. package/dist/daemon/server.js +0 -374
  128. package/dist/tui/App.js +0 -680
  129. package/dist/tui/components/CodeEditorModal.js +0 -244
  130. package/dist/tui/components/CommandInput.js +0 -273
  131. package/dist/tui/components/CreateForm.js +0 -224
  132. package/dist/tui/daemon.js +0 -142
  133. package/dist/tui/format.js +0 -111
  134. /package/dist/{tui/router.js → app/router/index.js} +0 -0
  135. /package/dist/{tui → app}/types.js +0 -0
  136. /package/dist/core/{resolve-cwd.js → command/resolve-cwd.js} +0 -0
  137. /package/dist/core/{context-parser.js → context/context-parser.js} +0 -0
  138. /package/dist/core/{template.js → context/template.js} +0 -0
  139. /package/dist/core/{log-parser.js → logging/log-parser.js} +0 -0
  140. /package/dist/core/{scheduling.js → scheduling/index.js} +0 -0
  141. /package/dist/{ipc → daemon/ipc}/send.js +0 -0
  142. /package/dist/{config → shared/config}/paths.js +0 -0
  143. /package/dist/{i18n → shared/i18n}/en.json +0 -0
  144. /package/dist/{i18n → shared/i18n}/index.js +0 -0
  145. /package/dist/{board → shared/ui}/format.js +0 -0
  146. /package/dist/{tui → shared/ui}/hooks/useHoverState.js +0 -0
  147. /package/dist/{tui → shared/ui}/theme.js +0 -0
  148. /package/dist/{tui → shared}/utils/syntax.js +0 -0
@@ -0,0 +1,119 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useCallback } from "react";
3
+ import { Box, useApp } from "ink";
4
+ import { useLoopPolling } from "../shared/hooks/useLoopPolling.js";
5
+ import { useLogStream } from "../shared/hooks/useLogStream.js";
6
+ import { useBreakpoint } from "../shared/hooks/useBreakpoint.js";
7
+ import { useToasts } from "../shared/ui/Toast.js";
8
+ import { Header } from "../widgets/header/Header.js";
9
+ import { LeftPanel } from "../widgets/left-panel/LeftPanel.js";
10
+ import { RightPanel } from "../widgets/right-panel/RightPanel.js";
11
+ import { CommandInput } from "../widgets/command-input/CommandInput.js";
12
+ import { DebugPanel } from "../shared/ui/DebugPanel.js";
13
+ import { useInject } from "../shared/hooks/useInject.js";
14
+ import { TYPES } from "../shared/services/types.js";
15
+ import { cycleSortMode, cycleStatusFilter } from "../entities/loops/filters.js";
16
+ import { t } from "../shared/i18n/index.js";
17
+ import { darkTheme as theme } from "../shared/ui/theme.js";
18
+ import { useRouter } from "./router/index.js";
19
+ import { useAppState } from "../features/state/useAppState.js";
20
+ import { useCommandHandlers } from "../features/commands/useCommandHandlers.js";
21
+ import { useContextualActions } from "../features/commands/useContextualActions.js";
22
+ import { useOverlayStack } from "../features/overlays/useOverlayStack.js";
23
+ import { useGlobalShortcuts } from "../features/commands/useGlobalShortcuts.js";
24
+ import { FormRouter } from "../features/forms/FormRouter.js";
25
+ import { OverlayStack } from "../features/overlays/OverlayStack.js";
26
+ function viewKey(view, editTarget, editTask) {
27
+ if (view === "create")
28
+ return `${view}:${editTarget?.id ?? "new"}`;
29
+ if (view === "task-edit")
30
+ return `${view}:${editTask?.id ?? "new"}`;
31
+ return view;
32
+ }
33
+ function isBoardView(view) {
34
+ return view === "board";
35
+ }
36
+ export function App(props) {
37
+ const { onQuit } = props;
38
+ const { exit } = useApp();
39
+ const loopService = useInject(TYPES.LoopService);
40
+ const taskService = useInject(TYPES.TaskService);
41
+ const projectService = useInject(TYPES.ProjectService);
42
+ const logService = useInject(TYPES.LogService);
43
+ const exportService = useInject(TYPES.ExportService);
44
+ const { loops, daemonStatus, refresh } = useLoopPolling();
45
+ const { view, push, pop } = useRouter("board");
46
+ const { toasts, push: pushToast } = useToasts();
47
+ const breakpoint = useBreakpoint();
48
+ const s = useAppState(loops, pushToast, refresh, loopService, taskService, projectService, logService, view, push, pop);
49
+ useLogStream(s.selectedId, view, (error) => pushToast("error", error.message));
50
+ const { handleCommand } = useCommandHandlers({
51
+ activeTab: s.activeTab, selected: s.selected, selectedTask: s.selectedTask, selectedProjectEntity: s.selectedProjectEntity,
52
+ tasks: s.tasks, projects: s.projects, currentProjectId: s.currentProjectId, filters: s.filters, projectFilters: s.projectFilters,
53
+ setCloneMode: s.setCloneMode, setEditTarget: s.setEditTarget, setPendingTaskSelection: s.setPendingTaskSelection,
54
+ setEditTask: s.setEditTask, setEditProject: s.setEditProject, setActiveTab: s.setActiveTab,
55
+ setConfirmState: s.setConfirmState, setCommandsBrowserOpen: s.setCommandsBrowserOpen, setSearchValue: s.setSearchValue, setSearchState: s.setSearchState,
56
+ setFilters: s.setFilters, setSort: s.setSort, setCurrentProjectId: s.setCurrentProjectId,
57
+ setProjectFilters: s.setProjectFilters, setProjectSelectedIndex: s.setProjectSelectedIndex,
58
+ setDebugMode: s.setDebugMode, setExportModal: s.setExportModal,
59
+ push, pop, refresh, refreshTasks: s.refreshTasks, refreshProjects: s.refreshProjects, pushToast,
60
+ loopService, taskService, projectService, exportService, logService,
61
+ runAction: s.runAction, handleOpenRunLog: s.handleOpenRunLog,
62
+ });
63
+ const { handleContextualCopy, triggerContextualAction } = useContextualActions({
64
+ activeTab: s.activeTab, focusedPanel: s.focusedPanel, selected: s.selected, selectedTask: s.selectedTask, selectedProjectEntity: s.selectedProjectEntity,
65
+ tasks: s.tasks, push, setCloneMode: s.setCloneMode, setEditTarget: s.setEditTarget, setPendingTaskSelection: s.setPendingTaskSelection,
66
+ handleCommand, handleOpenRunLog: s.handleOpenRunLog, pushToast, isBoardView,
67
+ view, logModalRun: s.logModalRun, commandsBrowserOpen: s.commandsBrowserOpen, confirmState: s.confirmState, searchState: s.searchState,
68
+ setChordState: s.setChordState, chordState: s.chordState,
69
+ });
70
+ const { popLayer, anyModalOpen, commandInputDisabled, inputOwner } = useOverlayStack({
71
+ confirmState: s.confirmState, setConfirmState: s.setConfirmState, searchState: s.searchState, setSearchState: s.setSearchState, setSearchValue: s.setSearchValue,
72
+ logModalRun: s.logModalRun, setLogModalRun: s.setLogModalRun, logModalLoopId: s.logModalLoopId, setLogModalLoopId: s.setLogModalLoopId,
73
+ commandsBrowserOpen: s.commandsBrowserOpen, setCommandsBrowserOpen: s.setCommandsBrowserOpen, exportModal: s.exportModal, setExportModal: s.setExportModal,
74
+ contextHelpOpen: s.contextHelpOpen, setContextHelpOpen: s.setContextHelpOpen, view, pop, onQuit, exit,
75
+ commandBarHasText: s.commandBarHasText, commandBarDropdownOpen: s.commandBarDropdownOpen,
76
+ });
77
+ useGlobalShortcuts({
78
+ activeTab: s.activeTab, focusedPanel: s.focusedPanel, setFocusedPanel: s.setFocusedPanel, setActiveTab: s.setActiveTab,
79
+ view, handleCommand, triggerContextualAction, popLayer,
80
+ anyModalOpen, debugMode: s.debugMode, setDebugEntries: s.setDebugEntries, inputOwner,
81
+ confirmState: s.confirmState, searchState: s.searchState, logModalRun: s.logModalRun, commandsBrowserOpen: s.commandsBrowserOpen, exportModal: s.exportModal,
82
+ contextHelpOpen: s.contextHelpOpen, setContextHelpOpen: s.setContextHelpOpen, onQuit, exit, setConfirmState: s.setConfirmState,
83
+ setLogModalRun: s.setLogModalRun, setLogModalLoopId: s.setLogModalLoopId, setCommandsBrowserOpen: s.setCommandsBrowserOpen,
84
+ chordState: s.chordState, setChordState: s.setChordState,
85
+ });
86
+ const handleConfirmYes = useCallback(() => { if (s.confirmState) {
87
+ s.confirmState.onConfirm();
88
+ s.setConfirmState(null);
89
+ } }, [s.confirmState]);
90
+ const handleConfirmCancel = useCallback(() => { s.setConfirmState(null); }, []);
91
+ const cancelCreate = () => { s.setEditTarget(null); s.setCloneMode(false); s.setPendingTaskSelection(null); pop(); };
92
+ const cancelTask = () => { s.setEditTask(null); pop(); };
93
+ const cancelProject = () => { s.setEditProject(null); pop(); };
94
+ const handleChooseTask = (task) => { s.setPendingTaskSelection(task); };
95
+ const onCreateDone = (updated, _id, desc) => {
96
+ s.setEditTarget(null);
97
+ s.setCloneMode(false);
98
+ s.setPendingTaskSelection(null);
99
+ pop();
100
+ pushToast("success", updated ? t("board.toastUpdated", { desc }) : t("board.toastStarted", { desc }));
101
+ void refresh();
102
+ };
103
+ const onTaskDone = (updated, id) => {
104
+ s.setEditTask(null);
105
+ void s.refreshTasks();
106
+ pop();
107
+ pushToast("success", updated ? t("board.toastTaskUpdated", { id }) : t("board.toastTaskCreated", { id }));
108
+ };
109
+ const onProjectDone = (updated, name) => {
110
+ s.setEditProject(null);
111
+ void s.refreshProjects();
112
+ pop();
113
+ pushToast("success", updated ? t("project.toastUpdated", { name }) : t("project.toastCreated", { name }));
114
+ };
115
+ const commandContext = useMemo(() => ({ activeTab: s.activeTab, selectedLoop: s.selected, selectedTask: s.selectedTask, selectedProject: s.selectedProjectEntity }), [s.activeTab, s.selected, s.selectedTask]);
116
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", height: process.stdout.rows || 24, backgroundColor: theme.bg.base, children: [_jsx(Header, { daemonStatus: daemonStatus, counts: s.counts, activeTab: s.activeTab, onTabChange: s.setActiveTab, tabCounts: s.tabCounts }), _jsx(Box, { flexGrow: 1, children: isBoardView(view) ? (_jsxs(Box, { flexDirection: breakpoint === "narrow" ? "column" : "row", flexGrow: 1, children: [_jsx(LeftPanel, { isFocused: s.focusedPanel === "left" && !anyModalOpen, navActive: inputOwner === "panel", activeTab: s.activeTab, query: s.leftPanelQuery, loops: s.visible, selectedIndex: s.clampedIndex, filters: s.filters, sort: s.sort, breakpoint: breakpoint, projects: s.projects, onSelect: (i) => s.setSelectedIndex(i), onActivate: (i) => { s.setSelectedIndex(i); }, tasks: s.filteredTasks, taskSelectedIndex: s.taskClampedIndex, onTaskSelect: (i) => s.setTaskSelectedIndex(i), onTaskActivate: (i) => { s.setTaskSelectedIndex(i); s.setEditTask(s.filteredTasks[i] ?? null); push("task-edit"); }, onStatusCycle: () => s.setFilters((prev) => ({ ...prev, status: cycleStatusFilter(prev.status) })), onSortCycle: () => s.setSort(cycleSortMode(s.sort)), onSelectProject: () => s.setActiveTab("projects"), currentProjectName: s.currentProjectId === "all" ? t("project.showAll") : (s.projects.find(p => p.id === s.currentProjectId)?.name ?? "Default"), projectFilters: s.projectFilters, projectSelectedIndex: s.projectClampedIndex, onProjectSelect: (i) => s.setProjectSelectedIndex(i), onProjectActivate: (i) => { s.setProjectSelectedIndex(i); }, projectLoops: loops }), _jsx(RightPanel, { isFocused: s.focusedPanel === "right" && !anyModalOpen, navActive: inputOwner === "panel", activeTab: s.activeTab, loop: s.selected, selectedRunIndex: s.selectedRunIndex, onSelectRun: (i) => s.setSelectedRunIndex(i), onOpenRun: s.handleOpenRunLog, selectedTask: s.selectedTask, allTasks: s.tasks, selectedProject: s.selectedProjectEntity, projectLoopCount: s.projectLoopCount, projects: s.projects, onProjectEdit: () => { if (s.selectedProjectEntity && !s.selectedProjectEntity.isSystem)
117
+ handleCommand("edit"); }, onProjectDelete: () => { if (s.selectedProjectEntity && !s.selectedProjectEntity.isSystem)
118
+ handleCommand("delete"); } }), s.debugMode ? _jsx(DebugPanel, { entries: s.debugEntries }) : null] })) : (_jsx(FormRouter, { view: view, editTarget: s.editTarget, cloneMode: s.cloneMode, editTask: s.editTask, editProject: s.editProject, pendingTaskSelection: s.pendingTaskSelection, tasks: s.tasks, projects: s.projects, currentProjectId: s.currentProjectId, cancelCreate: cancelCreate, onCreateDone: onCreateDone, handleChooseTask: handleChooseTask, cancelTask: cancelTask, onTaskDone: onTaskDone, cancelProject: cancelProject, onProjectDone: onProjectDone })) }, viewKey(view, s.editTarget, s.editTask)), isBoardView(view) ? (_jsx(CommandInput, { context: commandContext, onCommand: handleCommand, confirmState: s.confirmState, searchState: s.searchState, searchValue: s.searchValue, onSearchChange: s.handleSearchChange, onSearchSubmit: s.handleSearchSubmit, onSearchCancel: s.handleSearchCancel, onConfirmYes: handleConfirmYes, onConfirmCancel: handleConfirmCancel, onCopy: handleContextualCopy, onPanelAction: triggerContextualAction, disabled: commandInputDisabled, navOwner: inputOwner, onInputStateChange: (hasText, dropdownOpen) => { s.setCommandBarHasText(hasText); s.setCommandBarDropdownOpen(dropdownOpen); } })) : null, _jsx(OverlayStack, { commandsBrowserOpen: s.commandsBrowserOpen, commandContext: commandContext, onCommandsBrowserClose: () => s.setCommandsBrowserOpen(false), onCommandsBrowserExecute: (v) => { s.setCommandsBrowserOpen(false); handleCommand(v); }, contextHelpOpen: s.contextHelpOpen, onContextHelpClose: () => s.setContextHelpOpen(false), exportModal: s.exportModal, onExportModalClose: () => s.setExportModal(null), onExportCopy: () => pushToast("success", t("board.toastCopied")), logModalRun: s.logModalRun, logModalLoopId: s.logModalLoopId, logModalLines: s.logModalLines, logModalLoading: s.logModalLoading, onLogModalClose: () => { s.setLogModalRun(null); s.setLogModalLoopId(null); }, onLogCopy: () => pushToast("success", t("board.toastCopied")), toasts: toasts })] }));
119
+ }
@@ -1,18 +1,17 @@
1
1
  import { render } from "ink";
2
2
  import React from "react";
3
3
  import { App } from "./App.js";
4
- import { BRACKETED_PASTE_ENABLE, BRACKETED_PASTE_DISABLE } from "../config/constants.js";
4
+ import { BRACKETED_PASTE_ENABLE, BRACKETED_PASTE_DISABLE } from "../shared/config/constants.js";
5
+ import { InversifyProvider } from "../shared/providers/InversifyProvider.js";
5
6
  export async function launchBoard() {
6
- // Enable bracketed paste so a native paste (Ctrl+Shift+V / Cmd+V / right-click)
7
- // arrives as one delimited chunk instead of a stream of keypresses.
8
7
  process.stdout.write(BRACKETED_PASTE_ENABLE);
9
8
  const disableBracketedPaste = () => process.stdout.write(BRACKETED_PASTE_DISABLE);
10
- const instance = render(React.createElement(App, {
9
+ const instance = render(React.createElement(InversifyProvider, null, React.createElement(App, {
11
10
  onQuit: () => {
12
11
  disableBracketedPaste();
13
12
  instance.unmount();
14
13
  }
15
- }));
14
+ })));
16
15
  process.on("exit", disableBracketedPaste);
17
16
  process.on("uncaughtException", (error) => {
18
17
  console.error("Uncaught exception:", error);
@@ -0,0 +1 @@
1
+ export { InversifyProvider, InversifyContext } from "../../shared/providers/InversifyProvider.js";
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { writeFileAtomic } from "../shared/fs-utils.js";
4
- import { getDataDir } from "../config/paths.js";
4
+ import { getDataDir } from "../shared/config/paths.js";
5
5
  const STORE_FILES = ["loops.json", "tasks.json", "projects.json"];
6
6
  function backupFilePath(filePath) {
7
7
  return `${filePath}.bak`;
package/dist/cli.js CHANGED
@@ -2,13 +2,13 @@
2
2
  import { Command } from "commander";
3
3
  import { createRequire } from "node:module";
4
4
  import { Logger } from "./logger.js";
5
- import { runLoop } from "./core/foreground-loop.js";
5
+ import { runLoop } from "./core/foreground/index.js";
6
6
  import { buildLoopOptions } from "./loop-config.js";
7
7
  import { parseDuration } from "./duration.js";
8
8
  import { startLoop } from "./client/commands.js";
9
- import { listProjectsCli, createProjectCli, renameProjectCli, setProjectColorCli, deleteProjectCli, resolveProjectId, } from "./client/commands.js";
10
- import { t } from "./i18n/index.js";
11
- import { HTTP_API_PORT, HTTP_API_HOST } from "./config/constants.js";
9
+ import { listProjectsCli, createProjectCli, renameProjectCli, setProjectColorCli, deleteProjectCli, resolveProjectId, } from "./client/project-commands.js";
10
+ import { t } from "./shared/i18n/index.js";
11
+ import { HTTP_API_PORT, HTTP_API_HOST } from "./shared/config/constants.js";
12
12
  const require = createRequire(import.meta.url);
13
13
  const packageJson = require("../package.json");
14
14
  const program = new Command();
@@ -20,7 +20,7 @@ program
20
20
  .command("start")
21
21
  .description(t("cli.startDescription"))
22
22
  .action(async () => {
23
- const { ensureDaemon } = await import("./daemon/spawner.js");
23
+ const { ensureDaemon } = await import("./daemon/spawner/index.js");
24
24
  ensureDaemon();
25
25
  console.log(t("cli.daemonStarted"));
26
26
  });
@@ -54,8 +54,8 @@ program
54
54
  .command("restart")
55
55
  .description("Kill the daemon and all running loops, then restart fresh")
56
56
  .action(async () => {
57
- const { stopDaemon, ensureDaemon } = await import("./daemon/spawner.js");
58
- const { readDaemonPid, removeDaemonPid, removeDaemonSignature } = await import("./daemon/state.js");
57
+ const { stopDaemon, ensureDaemon } = await import("./daemon/spawner/index.js");
58
+ const { readDaemonPid, removeDaemonPid, removeDaemonSignature } = await import("./daemon/state/index.js");
59
59
  const pid = readDaemonPid();
60
60
  if (pid !== null) {
61
61
  console.log("Stopping daemon...");
@@ -170,7 +170,7 @@ projectCmd
170
170
  await deleteProjectCli(idOrName);
171
171
  });
172
172
  program.action(async () => {
173
- const { launchBoard } = await import("./tui/index.js");
173
+ const { launchBoard } = await import("./app/index.js");
174
174
  await launchBoard();
175
175
  });
176
176
  program
@@ -190,7 +190,7 @@ program
190
190
  }
191
191
  else {
192
192
  for (const loop of loops) {
193
- const { describeLoop, statusLabel } = await import("./board/format.js");
193
+ const { describeLoop, statusLabel } = await import("./shared/ui/format.js");
194
194
  console.log(`${loop.id} ${statusLabel(loop.status)} ${describeLoop(loop)}`);
195
195
  }
196
196
  }
@@ -0,0 +1,7 @@
1
+ export function formatCmd(command, args) {
2
+ const full = `${command} ${args.join(" ")}`.trim();
3
+ return full.length > 30 ? full.slice(0, 27) + "..." : full;
4
+ }
5
+ export function pad(str, width) {
6
+ return str.length >= width ? str + " " : str + " ".repeat(width - str.length);
7
+ }
@@ -1,7 +1,7 @@
1
1
  import { formatDuration } from "../duration.js";
2
- import { PROJECT_COLORS } from "../config/constants.js";
3
- import { t } from "../i18n/index.js";
2
+ import { t } from "../shared/i18n/index.js";
4
3
  import { sendRequest, streamRequest } from "./ipc.js";
4
+ import { formatCmd, pad } from "./cli-format.js";
5
5
  export async function createBackgroundLoop(options, intervalHuman) {
6
6
  const response = await sendRequest({
7
7
  type: "start",
@@ -153,165 +153,3 @@ export async function attachLoop(id) {
153
153
  process.exit(0);
154
154
  });
155
155
  }
156
- function formatCmd(command, args) {
157
- const full = `${command} ${args.join(" ")}`.trim();
158
- return full.length > 30 ? full.slice(0, 27) + "..." : full;
159
- }
160
- function pad(str, width) {
161
- return str.length >= width ? str + " " : str + " ".repeat(width - str.length);
162
- }
163
- async function fetchProjects() {
164
- const response = await sendRequest({ type: "project-list" });
165
- if (response.type !== "ok") {
166
- throw new Error(response.message);
167
- }
168
- return response.data;
169
- }
170
- export function resolveColor(input) {
171
- if (PROJECT_COLORS[input])
172
- return PROJECT_COLORS[input];
173
- if (/^#[0-9a-fA-F]{6}$/.test(input))
174
- return input;
175
- throw new Error(t("cli.projectInvalidColor", {
176
- color: input,
177
- valid: Object.keys(PROJECT_COLORS).join(", "),
178
- }));
179
- }
180
- export async function resolveProjectId(idOrName) {
181
- const projects = await fetchProjects();
182
- const byId = projects.find((p) => p.id === idOrName);
183
- if (byId)
184
- return byId.id;
185
- const matches = projects.filter((p) => p.name.toLowerCase() === idOrName.toLowerCase());
186
- if (matches.length === 1)
187
- return matches[0].id;
188
- if (matches.length === 0)
189
- throw new Error(t("cli.projectNotFound", { name: idOrName }));
190
- throw new Error(t("cli.projectAmbiguous", { name: idOrName }));
191
- }
192
- export async function listProjectsCli() {
193
- try {
194
- const projects = await fetchProjects();
195
- if (projects.length === 0) {
196
- console.log(t("cli.noProjects"));
197
- process.exit(0);
198
- }
199
- const loopResponse = await sendRequest({ type: "list" });
200
- const loops = loopResponse.type === "ok" ? loopResponse.data : [];
201
- const counts = new Map();
202
- for (const loop of loops) {
203
- counts.set(loop.projectId, (counts.get(loop.projectId) ?? 0) + 1);
204
- }
205
- const nameW = Math.max(t("cli.projectHeaderName").length, ...projects.map((p) => p.name.length)) + 2;
206
- const idW = Math.max(t("cli.projectHeaderId").length, ...projects.map((p) => p.id.length)) + 2;
207
- const colorW = Math.max(t("cli.projectHeaderColor").length, ...projects.map((p) => p.color.length)) + 2;
208
- const loopsW = t("cli.projectHeaderLoops").length + 2;
209
- const header = pad(t("cli.projectHeaderName"), nameW) +
210
- pad(t("cli.projectHeaderId"), idW) +
211
- pad(t("cli.projectHeaderColor"), colorW) +
212
- pad(t("cli.projectHeaderLoops"), loopsW) +
213
- t("cli.projectHeaderSystem");
214
- console.log(header);
215
- console.log("-".repeat(header.length));
216
- for (const project of projects) {
217
- console.log(pad(project.name, nameW) +
218
- pad(project.id, idW) +
219
- pad(project.color, colorW) +
220
- pad(String(counts.get(project.id) ?? 0), loopsW) +
221
- (project.isSystem ? "yes" : ""));
222
- }
223
- process.exit(0);
224
- }
225
- catch (error) {
226
- const message = error instanceof Error ? error.message : String(error);
227
- console.error(t("cli.error", { message }));
228
- process.exit(1);
229
- }
230
- }
231
- export async function createProjectCli(name, colorInput) {
232
- try {
233
- const color = colorInput ? resolveColor(colorInput) : PROJECT_COLORS.cyan;
234
- const response = await sendRequest({
235
- type: "project-create",
236
- payload: { name, color },
237
- });
238
- if (response.type !== "ok") {
239
- throw new Error(response.message);
240
- }
241
- const id = response.data.id;
242
- console.log(t("cli.projectCreated", { name, id }));
243
- process.exit(0);
244
- }
245
- catch (error) {
246
- const message = error instanceof Error ? error.message : String(error);
247
- console.error(t("cli.error", { message }));
248
- process.exit(1);
249
- }
250
- }
251
- export async function renameProjectCli(idOrName, newName) {
252
- try {
253
- const id = await resolveProjectId(idOrName);
254
- const response = await sendRequest({
255
- type: "project-update",
256
- payload: { id, name: newName },
257
- });
258
- if (response.type !== "ok") {
259
- throw new Error(response.message);
260
- }
261
- console.log(t("cli.projectRenamed", { name: newName }));
262
- process.exit(0);
263
- }
264
- catch (error) {
265
- const message = error instanceof Error ? error.message : String(error);
266
- console.error(t("cli.error", { message }));
267
- process.exit(1);
268
- }
269
- }
270
- export async function setProjectColorCli(idOrName, colorInput) {
271
- try {
272
- const projects = await fetchProjects();
273
- const byId = projects.find((p) => p.id === idOrName);
274
- const matches = byId
275
- ? [byId]
276
- : projects.filter((p) => p.name.toLowerCase() === idOrName.toLowerCase());
277
- if (matches.length === 0)
278
- throw new Error(t("cli.projectNotFound", { name: idOrName }));
279
- if (matches.length > 1)
280
- throw new Error(t("cli.projectAmbiguous", { name: idOrName }));
281
- const project = matches[0];
282
- const color = resolveColor(colorInput);
283
- const response = await sendRequest({
284
- type: "project-update",
285
- payload: { id: project.id, name: project.name, color },
286
- });
287
- if (response.type !== "ok") {
288
- throw new Error(response.message);
289
- }
290
- console.log(t("cli.projectColorSet", { name: project.name, color }));
291
- process.exit(0);
292
- }
293
- catch (error) {
294
- const message = error instanceof Error ? error.message : String(error);
295
- console.error(t("cli.error", { message }));
296
- process.exit(1);
297
- }
298
- }
299
- export async function deleteProjectCli(idOrName) {
300
- try {
301
- const id = await resolveProjectId(idOrName);
302
- const response = await sendRequest({
303
- type: "project-delete",
304
- payload: { id },
305
- });
306
- if (response.type !== "ok") {
307
- throw new Error(response.message);
308
- }
309
- console.log(t("cli.projectDeleted", { name: idOrName }));
310
- process.exit(0);
311
- }
312
- catch (error) {
313
- const message = error instanceof Error ? error.message : String(error);
314
- console.error(t("cli.error", { message }));
315
- process.exit(1);
316
- }
317
- }
@@ -1,7 +1,7 @@
1
1
  import net from "node:net";
2
- import { ensureDaemon, getSocket } from "../daemon/spawner.js";
3
- import { t } from "../i18n/index.js";
4
- import { IPC_TIMEOUT_MS } from "../config/constants.js";
2
+ import { ensureDaemon, getSocket } from "../daemon/spawner/index.js";
3
+ import { t } from "../shared/i18n/index.js";
4
+ import { IPC_TIMEOUT_MS } from "../shared/config/constants.js";
5
5
  export async function sendRequest(request) {
6
6
  ensureDaemon();
7
7
  const socketPath = getSocket();
@@ -0,0 +1,159 @@
1
+ import { PROJECT_COLORS } from "../shared/config/constants.js";
2
+ import { t } from "../shared/i18n/index.js";
3
+ import { sendRequest } from "./ipc.js";
4
+ import { pad } from "./cli-format.js";
5
+ async function fetchProjects() {
6
+ const response = await sendRequest({ type: "project-list" });
7
+ if (response.type !== "ok") {
8
+ throw new Error(response.message);
9
+ }
10
+ return response.data;
11
+ }
12
+ export function resolveColor(input) {
13
+ if (PROJECT_COLORS[input])
14
+ return PROJECT_COLORS[input];
15
+ if (/^#[0-9a-fA-F]{6}$/.test(input))
16
+ return input;
17
+ throw new Error(t("cli.projectInvalidColor", {
18
+ color: input,
19
+ valid: Object.keys(PROJECT_COLORS).join(", "),
20
+ }));
21
+ }
22
+ export async function resolveProjectId(idOrName) {
23
+ const projects = await fetchProjects();
24
+ const byId = projects.find((p) => p.id === idOrName);
25
+ if (byId)
26
+ return byId.id;
27
+ const matches = projects.filter((p) => p.name.toLowerCase() === idOrName.toLowerCase());
28
+ if (matches.length === 1)
29
+ return matches[0].id;
30
+ if (matches.length === 0)
31
+ throw new Error(t("cli.projectNotFound", { name: idOrName }));
32
+ throw new Error(t("cli.projectAmbiguous", { name: idOrName }));
33
+ }
34
+ export async function listProjectsCli() {
35
+ try {
36
+ const projects = await fetchProjects();
37
+ if (projects.length === 0) {
38
+ console.log(t("cli.noProjects"));
39
+ process.exit(0);
40
+ }
41
+ const loopResponse = await sendRequest({ type: "list" });
42
+ const loops = loopResponse.type === "ok" ? loopResponse.data : [];
43
+ const counts = new Map();
44
+ for (const loop of loops) {
45
+ counts.set(loop.projectId, (counts.get(loop.projectId) ?? 0) + 1);
46
+ }
47
+ const nameW = Math.max(t("cli.projectHeaderName").length, ...projects.map((p) => p.name.length)) + 2;
48
+ const idW = Math.max(t("cli.projectHeaderId").length, ...projects.map((p) => p.id.length)) + 2;
49
+ const colorW = Math.max(t("cli.projectHeaderColor").length, ...projects.map((p) => p.color.length)) + 2;
50
+ const loopsW = t("cli.projectHeaderLoops").length + 2;
51
+ const header = pad(t("cli.projectHeaderName"), nameW) +
52
+ pad(t("cli.projectHeaderId"), idW) +
53
+ pad(t("cli.projectHeaderColor"), colorW) +
54
+ pad(t("cli.projectHeaderLoops"), loopsW) +
55
+ t("cli.projectHeaderSystem");
56
+ console.log(header);
57
+ console.log("-".repeat(header.length));
58
+ for (const project of projects) {
59
+ console.log(pad(project.name, nameW) +
60
+ pad(project.id, idW) +
61
+ pad(project.color, colorW) +
62
+ pad(String(counts.get(project.id) ?? 0), loopsW) +
63
+ (project.isSystem ? "yes" : ""));
64
+ }
65
+ process.exit(0);
66
+ }
67
+ catch (error) {
68
+ const message = error instanceof Error ? error.message : String(error);
69
+ console.error(t("cli.error", { message }));
70
+ process.exit(1);
71
+ }
72
+ }
73
+ export async function createProjectCli(name, colorInput) {
74
+ try {
75
+ const color = colorInput ? resolveColor(colorInput) : PROJECT_COLORS.cyan;
76
+ const response = await sendRequest({
77
+ type: "project-create",
78
+ payload: { name, color },
79
+ });
80
+ if (response.type !== "ok") {
81
+ throw new Error(response.message);
82
+ }
83
+ const id = response.data.id;
84
+ console.log(t("cli.projectCreated", { name, id }));
85
+ process.exit(0);
86
+ }
87
+ catch (error) {
88
+ const message = error instanceof Error ? error.message : String(error);
89
+ console.error(t("cli.error", { message }));
90
+ process.exit(1);
91
+ }
92
+ }
93
+ export async function renameProjectCli(idOrName, newName) {
94
+ try {
95
+ const id = await resolveProjectId(idOrName);
96
+ const response = await sendRequest({
97
+ type: "project-update",
98
+ payload: { id, name: newName },
99
+ });
100
+ if (response.type !== "ok") {
101
+ throw new Error(response.message);
102
+ }
103
+ console.log(t("cli.projectRenamed", { name: newName }));
104
+ process.exit(0);
105
+ }
106
+ catch (error) {
107
+ const message = error instanceof Error ? error.message : String(error);
108
+ console.error(t("cli.error", { message }));
109
+ process.exit(1);
110
+ }
111
+ }
112
+ export async function setProjectColorCli(idOrName, colorInput) {
113
+ try {
114
+ const projects = await fetchProjects();
115
+ const byId = projects.find((p) => p.id === idOrName);
116
+ const matches = byId
117
+ ? [byId]
118
+ : projects.filter((p) => p.name.toLowerCase() === idOrName.toLowerCase());
119
+ if (matches.length === 0)
120
+ throw new Error(t("cli.projectNotFound", { name: idOrName }));
121
+ if (matches.length > 1)
122
+ throw new Error(t("cli.projectAmbiguous", { name: idOrName }));
123
+ const project = matches[0];
124
+ const color = resolveColor(colorInput);
125
+ const response = await sendRequest({
126
+ type: "project-update",
127
+ payload: { id: project.id, name: project.name, color },
128
+ });
129
+ if (response.type !== "ok") {
130
+ throw new Error(response.message);
131
+ }
132
+ console.log(t("cli.projectColorSet", { name: project.name, color }));
133
+ process.exit(0);
134
+ }
135
+ catch (error) {
136
+ const message = error instanceof Error ? error.message : String(error);
137
+ console.error(t("cli.error", { message }));
138
+ process.exit(1);
139
+ }
140
+ }
141
+ export async function deleteProjectCli(idOrName) {
142
+ try {
143
+ const id = await resolveProjectId(idOrName);
144
+ const response = await sendRequest({
145
+ type: "project-delete",
146
+ payload: { id },
147
+ });
148
+ if (response.type !== "ok") {
149
+ throw new Error(response.message);
150
+ }
151
+ console.log(t("cli.projectDeleted", { name: idOrName }));
152
+ process.exit(0);
153
+ }
154
+ catch (error) {
155
+ const message = error instanceof Error ? error.message : String(error);
156
+ console.error(t("cli.error", { message }));
157
+ process.exit(1);
158
+ }
159
+ }
@@ -1,8 +1,8 @@
1
1
  import fs from "node:fs";
2
2
  import { execa } from "execa";
3
- import { formatDuration } from "../duration.js";
4
- import { t } from "../i18n/index.js";
5
- import { MAX_CONTEXT_STDOUT_BYTES } from "../config/constants.js";
3
+ import { formatDuration } from "../../duration.js";
4
+ import { t } from "../../shared/i18n/index.js";
5
+ import { MAX_CONTEXT_STDOUT_BYTES } from "../../shared/config/constants.js";
6
6
  function quoteArg(arg) {
7
7
  if (arg.length === 0)
8
8
  return "''";
@@ -1,7 +1,7 @@
1
- import { formatDuration } from "../duration.js";
2
- import { t } from "../i18n/index.js";
3
- import { sleep } from "../shared/sleep.js";
4
- import { executeCommandForeground } from "./command-runner.js";
1
+ import { formatDuration } from "../../duration.js";
2
+ import { t } from "../../shared/i18n/index.js";
3
+ import { sleep } from "../../shared/sleep.js";
4
+ import { executeCommandForeground } from "../command/command-runner.js";
5
5
  export async function runLoop(options, logger, signal) {
6
6
  const state = {
7
7
  running: false,
@@ -1,5 +1,5 @@
1
1
  import fs from "node:fs";
2
- import { MAX_LOG_BYTES, MAX_LOG_GENERATIONS } from "../config/constants.js";
2
+ import { MAX_LOG_BYTES, MAX_LOG_GENERATIONS } from "../../shared/config/constants.js";
3
3
  export function rotateLogIfNeeded(logPath, currentStream) {
4
4
  if (!fs.existsSync(logPath)) {
5
5
  return currentStream;