brainfile 0.18.0 → 0.19.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 (245) hide show
  1. package/README.md +4 -4
  2. package/dist/__tests__/helpers/mcp.d.ts +34 -0
  3. package/dist/__tests__/helpers/mcp.d.ts.map +1 -0
  4. package/dist/__tests__/helpers/mcp.js +37 -0
  5. package/dist/__tests__/helpers/mcp.js.map +1 -0
  6. package/dist/chunk-I5SJHUZK.mjs +52 -0
  7. package/dist/chunk-YTFLC673.mjs +12089 -0
  8. package/dist/cli.js +8 -479
  9. package/dist/cli.mjs +41194 -0
  10. package/dist/commands/add.d.ts.map +1 -1
  11. package/dist/commands/add.js +28 -37
  12. package/dist/commands/add.js.map +1 -1
  13. package/dist/commands/archive.d.ts +3 -2
  14. package/dist/commands/archive.d.ts.map +1 -1
  15. package/dist/commands/archive.js +16 -205
  16. package/dist/commands/archive.js.map +1 -1
  17. package/dist/commands/complete.d.ts +0 -4
  18. package/dist/commands/complete.d.ts.map +1 -1
  19. package/dist/commands/complete.js +14 -168
  20. package/dist/commands/complete.js.map +1 -1
  21. package/dist/commands/contract.d.ts.map +1 -1
  22. package/dist/commands/contract.js +66 -331
  23. package/dist/commands/contract.js.map +1 -1
  24. package/dist/commands/delete.d.ts.map +1 -1
  25. package/dist/commands/delete.js +13 -55
  26. package/dist/commands/delete.js.map +1 -1
  27. package/dist/commands/init.d.ts.map +1 -1
  28. package/dist/commands/init.js +3 -2
  29. package/dist/commands/init.js.map +1 -1
  30. package/dist/commands/list.d.ts.map +1 -1
  31. package/dist/commands/list.js +3 -54
  32. package/dist/commands/list.js.map +1 -1
  33. package/dist/commands/log.js +2 -8
  34. package/dist/commands/log.js.map +1 -1
  35. package/dist/commands/mcp.d.ts.map +1 -1
  36. package/dist/commands/mcp.js +22 -17
  37. package/dist/commands/mcp.js.map +1 -1
  38. package/dist/commands/migrate.d.ts.map +1 -1
  39. package/dist/commands/migrate.js +4 -3
  40. package/dist/commands/migrate.js.map +1 -1
  41. package/dist/commands/move.d.ts.map +1 -1
  42. package/dist/commands/move.js +31 -126
  43. package/dist/commands/move.js.map +1 -1
  44. package/dist/commands/patch.d.ts.map +1 -1
  45. package/dist/commands/patch.js +22 -92
  46. package/dist/commands/patch.js.map +1 -1
  47. package/dist/commands/plan.d.ts +61 -0
  48. package/dist/commands/plan.d.ts.map +1 -0
  49. package/dist/commands/plan.js +205 -0
  50. package/dist/commands/plan.js.map +1 -0
  51. package/dist/commands/search.d.ts.map +1 -1
  52. package/dist/commands/search.js +20 -146
  53. package/dist/commands/search.js.map +1 -1
  54. package/dist/commands/show.d.ts.map +1 -1
  55. package/dist/commands/show.js +5 -126
  56. package/dist/commands/show.js.map +1 -1
  57. package/dist/commands/subtask.d.ts.map +1 -1
  58. package/dist/commands/subtask.js +19 -135
  59. package/dist/commands/subtask.js.map +1 -1
  60. package/dist/commands/types.d.ts +2 -2
  61. package/dist/commands/types.d.ts.map +1 -1
  62. package/dist/commands/types.js +8 -30
  63. package/dist/commands/types.js.map +1 -1
  64. package/dist/devtools-NE5M3KPM.mjs +92 -0
  65. package/dist/lib/contractRunner.d.ts.map +1 -1
  66. package/dist/lib/contractRunner.js +19 -205
  67. package/dist/lib/contractRunner.js.map +1 -1
  68. package/dist/mcp/helpers.d.ts +11 -16
  69. package/dist/mcp/helpers.d.ts.map +1 -1
  70. package/dist/mcp/helpers.js +9 -46
  71. package/dist/mcp/helpers.js.map +1 -1
  72. package/dist/mcp/tools/contract_tool.d.ts +1 -1
  73. package/dist/mcp/tools/contract_tool.d.ts.map +1 -1
  74. package/dist/mcp/tools/contract_tool.js +23 -175
  75. package/dist/mcp/tools/contract_tool.js.map +1 -1
  76. package/dist/mcp/tools/get_task_tool.d.ts +1 -1
  77. package/dist/mcp/tools/get_task_tool.d.ts.map +1 -1
  78. package/dist/mcp/tools/get_task_tool.js +15 -34
  79. package/dist/mcp/tools/get_task_tool.js.map +1 -1
  80. package/dist/mcp/tools/list_tasks_tool.d.ts +1 -1
  81. package/dist/mcp/tools/list_tasks_tool.d.ts.map +1 -1
  82. package/dist/mcp/tools/list_tasks_tool.js +11 -48
  83. package/dist/mcp/tools/list_tasks_tool.js.map +1 -1
  84. package/dist/mcp/tools/search_tool.d.ts +1 -1
  85. package/dist/mcp/tools/search_tool.d.ts.map +1 -1
  86. package/dist/mcp/tools/search_tool.js +17 -98
  87. package/dist/mcp/tools/search_tool.js.map +1 -1
  88. package/dist/mcp/tools/subtask_tool.d.ts +1 -1
  89. package/dist/mcp/tools/subtask_tool.d.ts.map +1 -1
  90. package/dist/mcp/tools/subtask_tool.js +49 -224
  91. package/dist/mcp/tools/subtask_tool.js.map +1 -1
  92. package/dist/mcp/tools/task_add_tool.d.ts +1 -1
  93. package/dist/mcp/tools/task_add_tool.d.ts.map +1 -1
  94. package/dist/mcp/tools/task_add_tool.js +58 -154
  95. package/dist/mcp/tools/task_add_tool.js.map +1 -1
  96. package/dist/mcp/tools/task_complete_tool.d.ts +1 -1
  97. package/dist/mcp/tools/task_complete_tool.d.ts.map +1 -1
  98. package/dist/mcp/tools/task_complete_tool.js +28 -124
  99. package/dist/mcp/tools/task_complete_tool.js.map +1 -1
  100. package/dist/mcp/tools/task_delete_tool.d.ts +1 -1
  101. package/dist/mcp/tools/task_delete_tool.d.ts.map +1 -1
  102. package/dist/mcp/tools/task_delete_tool.js +14 -33
  103. package/dist/mcp/tools/task_delete_tool.js.map +1 -1
  104. package/dist/mcp/tools/task_move_tool.d.ts +1 -1
  105. package/dist/mcp/tools/task_move_tool.d.ts.map +1 -1
  106. package/dist/mcp/tools/task_move_tool.js +44 -150
  107. package/dist/mcp/tools/task_move_tool.js.map +1 -1
  108. package/dist/mcp/tools/task_patch_tool.d.ts +1 -1
  109. package/dist/mcp/tools/task_patch_tool.d.ts.map +1 -1
  110. package/dist/mcp/tools/task_patch_tool.js +37 -113
  111. package/dist/mcp/tools/task_patch_tool.js.map +1 -1
  112. package/dist/open-ZGSQIGEH.mjs +542 -0
  113. package/dist/tui-P56H4X7N.mjs +23715 -0
  114. package/dist/utils/dot-brainfile.d.ts +0 -4
  115. package/dist/utils/dot-brainfile.d.ts.map +1 -1
  116. package/dist/utils/dot-brainfile.js +0 -17
  117. package/dist/utils/dot-brainfile.js.map +1 -1
  118. package/dist/utils/errorHandler.d.ts +7 -11
  119. package/dist/utils/errorHandler.d.ts.map +1 -1
  120. package/dist/utils/errorHandler.js +8 -19
  121. package/dist/utils/errorHandler.js.map +1 -1
  122. package/package.json +24 -16
  123. package/dist/cli.d.ts +0 -3
  124. package/dist/cli.d.ts.map +0 -1
  125. package/dist/cli.js.map +0 -1
  126. package/dist/commands/tui.d.ts +0 -6
  127. package/dist/commands/tui.d.ts.map +0 -1
  128. package/dist/commands/tui.js +0 -79
  129. package/dist/commands/tui.js.map +0 -1
  130. package/dist/tui/BrainfileTUI.d.ts +0 -4
  131. package/dist/tui/BrainfileTUI.d.ts.map +0 -1
  132. package/dist/tui/BrainfileTUI.js +0 -272
  133. package/dist/tui/BrainfileTUI.js.map +0 -1
  134. package/dist/tui/actions.d.ts +0 -84
  135. package/dist/tui/actions.d.ts.map +0 -1
  136. package/dist/tui/actions.js +0 -1251
  137. package/dist/tui/actions.js.map +0 -1
  138. package/dist/tui/components/ColumnTabs.d.ts +0 -9
  139. package/dist/tui/components/ColumnTabs.d.ts.map +0 -1
  140. package/dist/tui/components/ColumnTabs.js +0 -34
  141. package/dist/tui/components/ColumnTabs.js.map +0 -1
  142. package/dist/tui/components/Header.d.ts +0 -15
  143. package/dist/tui/components/Header.d.ts.map +0 -1
  144. package/dist/tui/components/Header.js +0 -48
  145. package/dist/tui/components/Header.js.map +0 -1
  146. package/dist/tui/components/HelpOverlay.d.ts +0 -9
  147. package/dist/tui/components/HelpOverlay.d.ts.map +0 -1
  148. package/dist/tui/components/HelpOverlay.js +0 -116
  149. package/dist/tui/components/HelpOverlay.js.map +0 -1
  150. package/dist/tui/components/LogsPanel.d.ts +0 -16
  151. package/dist/tui/components/LogsPanel.d.ts.map +0 -1
  152. package/dist/tui/components/LogsPanel.js +0 -115
  153. package/dist/tui/components/LogsPanel.js.map +0 -1
  154. package/dist/tui/components/MainPanelTabs.d.ts +0 -10
  155. package/dist/tui/components/MainPanelTabs.d.ts.map +0 -1
  156. package/dist/tui/components/MainPanelTabs.js +0 -56
  157. package/dist/tui/components/MainPanelTabs.js.map +0 -1
  158. package/dist/tui/components/ManualBorder.d.ts +0 -42
  159. package/dist/tui/components/ManualBorder.d.ts.map +0 -1
  160. package/dist/tui/components/ManualBorder.js +0 -56
  161. package/dist/tui/components/ManualBorder.js.map +0 -1
  162. package/dist/tui/components/Overlays.d.ts +0 -41
  163. package/dist/tui/components/Overlays.d.ts.map +0 -1
  164. package/dist/tui/components/Overlays.js +0 -127
  165. package/dist/tui/components/Overlays.js.map +0 -1
  166. package/dist/tui/components/ProgressBar.d.ts +0 -8
  167. package/dist/tui/components/ProgressBar.d.ts.map +0 -1
  168. package/dist/tui/components/ProgressBar.js +0 -35
  169. package/dist/tui/components/ProgressBar.js.map +0 -1
  170. package/dist/tui/components/RulesPanel.d.ts +0 -25
  171. package/dist/tui/components/RulesPanel.d.ts.map +0 -1
  172. package/dist/tui/components/RulesPanel.js +0 -139
  173. package/dist/tui/components/RulesPanel.js.map +0 -1
  174. package/dist/tui/components/SearchBar.d.ts +0 -8
  175. package/dist/tui/components/SearchBar.d.ts.map +0 -1
  176. package/dist/tui/components/SearchBar.js +0 -38
  177. package/dist/tui/components/SearchBar.js.map +0 -1
  178. package/dist/tui/components/StackedTaskList.d.ts +0 -27
  179. package/dist/tui/components/StackedTaskList.d.ts.map +0 -1
  180. package/dist/tui/components/StackedTaskList.js +0 -120
  181. package/dist/tui/components/StackedTaskList.js.map +0 -1
  182. package/dist/tui/components/StatusBar.d.ts +0 -14
  183. package/dist/tui/components/StatusBar.d.ts.map +0 -1
  184. package/dist/tui/components/StatusBar.js +0 -42
  185. package/dist/tui/components/StatusBar.js.map +0 -1
  186. package/dist/tui/components/TaskCard.d.ts +0 -25
  187. package/dist/tui/components/TaskCard.d.ts.map +0 -1
  188. package/dist/tui/components/TaskCard.js +0 -221
  189. package/dist/tui/components/TaskCard.js.map +0 -1
  190. package/dist/tui/components/TaskCardMeasure.d.ts +0 -31
  191. package/dist/tui/components/TaskCardMeasure.d.ts.map +0 -1
  192. package/dist/tui/components/TaskCardMeasure.js +0 -133
  193. package/dist/tui/components/TaskCardMeasure.js.map +0 -1
  194. package/dist/tui/components/TaskDetail.d.ts +0 -13
  195. package/dist/tui/components/TaskDetail.d.ts.map +0 -1
  196. package/dist/tui/components/TaskDetail.js +0 -64
  197. package/dist/tui/components/TaskDetail.js.map +0 -1
  198. package/dist/tui/components/TaskList.d.ts +0 -12
  199. package/dist/tui/components/TaskList.d.ts.map +0 -1
  200. package/dist/tui/components/TaskList.js +0 -75
  201. package/dist/tui/components/TaskList.js.map +0 -1
  202. package/dist/tui/components/index.d.ts +0 -31
  203. package/dist/tui/components/index.d.ts.map +0 -1
  204. package/dist/tui/components/index.js +0 -43
  205. package/dist/tui/components/index.js.map +0 -1
  206. package/dist/tui/hooks/index.d.ts +0 -3
  207. package/dist/tui/hooks/index.d.ts.map +0 -1
  208. package/dist/tui/hooks/index.js +0 -8
  209. package/dist/tui/hooks/index.js.map +0 -1
  210. package/dist/tui/hooks/useBrainfileLoader.d.ts +0 -5
  211. package/dist/tui/hooks/useBrainfileLoader.d.ts.map +0 -1
  212. package/dist/tui/hooks/useBrainfileLoader.js +0 -237
  213. package/dist/tui/hooks/useBrainfileLoader.js.map +0 -1
  214. package/dist/tui/hooks/useKeyboardNavigation.d.ts +0 -20
  215. package/dist/tui/hooks/useKeyboardNavigation.d.ts.map +0 -1
  216. package/dist/tui/hooks/useKeyboardNavigation.js +0 -806
  217. package/dist/tui/hooks/useKeyboardNavigation.js.map +0 -1
  218. package/dist/tui/index.d.ts +0 -10
  219. package/dist/tui/index.d.ts.map +0 -1
  220. package/dist/tui/index.js +0 -38
  221. package/dist/tui/index.js.map +0 -1
  222. package/dist/tui/theme.d.ts +0 -119
  223. package/dist/tui/theme.d.ts.map +0 -1
  224. package/dist/tui/theme.js +0 -138
  225. package/dist/tui/theme.js.map +0 -1
  226. package/dist/tui/types.d.ts +0 -53
  227. package/dist/tui/types.d.ts.map +0 -1
  228. package/dist/tui/types.js +0 -12
  229. package/dist/tui/types.js.map +0 -1
  230. package/dist/tui/utils.d.ts +0 -24
  231. package/dist/tui/utils.d.ts.map +0 -1
  232. package/dist/tui/utils.js +0 -161
  233. package/dist/tui/utils.js.map +0 -1
  234. package/dist/utils/board-types.d.ts +0 -13
  235. package/dist/utils/board-types.d.ts.map +0 -1
  236. package/dist/utils/board-types.js +0 -7
  237. package/dist/utils/board-types.js.map +0 -1
  238. package/dist/utils/contractSpec.d.ts +0 -13
  239. package/dist/utils/contractSpec.d.ts.map +0 -1
  240. package/dist/utils/contractSpec.js +0 -61
  241. package/dist/utils/contractSpec.js.map +0 -1
  242. package/dist/utils/strict-validation.d.ts +0 -8
  243. package/dist/utils/strict-validation.d.ts.map +0 -1
  244. package/dist/utils/strict-validation.js +0 -41
  245. package/dist/utils/strict-validation.js.map +0 -1
@@ -1,1251 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.moveTaskAction = moveTaskAction;
37
- exports.deleteTaskAction = deleteTaskAction;
38
- exports.archiveTaskAction = archiveTaskAction;
39
- exports.archiveTaskActionAsync = archiveTaskActionAsync;
40
- exports.patchTaskAction = patchTaskAction;
41
- exports.cyclePriorityAction = cyclePriorityAction;
42
- exports.toggleSubtaskAction = toggleSubtaskAction;
43
- exports.addTaskAction = addTaskAction;
44
- exports.newTaskInEditor = newTaskInEditor;
45
- exports.editTaskInEditor = editTaskInEditor;
46
- exports.copyToClipboard = copyToClipboard;
47
- exports.addRuleAction = addRuleAction;
48
- exports.updateRuleAction = updateRuleAction;
49
- exports.deleteRuleAction = deleteRuleAction;
50
- exports.loadLogs = loadLogs;
51
- exports.restoreTaskAction = restoreTaskAction;
52
- exports.deleteArchivedTaskAction = deleteArchivedTaskAction;
53
- /**
54
- * TUI Actions - Wrappers around @brainfile/core operations
55
- *
56
- * All actions read/write the brainfile directly. The file watcher
57
- * in useBrainfileLoader will detect changes and refresh the UI.
58
- */
59
- const fs = __importStar(require("fs"));
60
- const path = __importStar(require("path"));
61
- const os = __importStar(require("os"));
62
- const child_process_1 = require("child_process");
63
- const core_1 = require("@brainfile/core");
64
- const config_1 = require("../utils/config");
65
- const v2_detect_1 = require("../utils/v2-detect");
66
- const github_auth_1 = require("../utils/github-auth");
67
- const linear_auth_1 = require("../utils/linear-auth");
68
- /**
69
- * Read and parse the brainfile
70
- */
71
- function readBrainfile(filePath) {
72
- try {
73
- const content = fs.readFileSync(filePath, 'utf-8');
74
- const result = core_1.Brainfile.parseWithErrors(content);
75
- if (!result.board) {
76
- return { board: null, content, error: result.error || 'Failed to parse brainfile' };
77
- }
78
- return { board: result.board, content };
79
- }
80
- catch (err) {
81
- return { board: null, content: '', error: err instanceof Error ? err.message : String(err) };
82
- }
83
- }
84
- /**
85
- * Write the board back to the brainfile
86
- */
87
- function writeBrainfile(filePath, board) {
88
- try {
89
- const content = core_1.Brainfile.serialize(board);
90
- fs.writeFileSync(filePath, content, 'utf-8');
91
- return { success: true };
92
- }
93
- catch (err) {
94
- return { success: false, error: err instanceof Error ? err.message : String(err) };
95
- }
96
- }
97
- /**
98
- * Read board with tasks populated (v1: embedded, v2: reconstructed from board/ files).
99
- */
100
- function readBoardState(filePath) {
101
- if ((0, v2_detect_1.isV2)(filePath)) {
102
- try {
103
- const content = fs.readFileSync(filePath, 'utf-8');
104
- const board = (0, v2_detect_1.buildBoardFromV2)(filePath);
105
- return { board, content };
106
- }
107
- catch (err) {
108
- return { board: null, content: '', error: err instanceof Error ? err.message : String(err) };
109
- }
110
- }
111
- return readBrainfile(filePath);
112
- }
113
- /**
114
- * Read board config only (for rules/settings changes that should not touch task files).
115
- */
116
- function readBoardConfigOnly(filePath) {
117
- if ((0, v2_detect_1.isV2)(filePath)) {
118
- try {
119
- const content = fs.readFileSync(filePath, 'utf-8');
120
- const board = (0, v2_detect_1.readV2BoardConfig)(filePath);
121
- return { board, content };
122
- }
123
- catch (err) {
124
- return { board: null, content: '', error: err instanceof Error ? err.message : String(err) };
125
- }
126
- }
127
- return readBrainfile(filePath);
128
- }
129
- /**
130
- * Resolve a task file path in v2 board directory.
131
- */
132
- function resolveV2TaskPath(filePath, taskId) {
133
- const dirs = (0, v2_detect_1.getV2Dirs)(filePath);
134
- const doc = (0, core_1.findTask)(dirs.boardDir, taskId);
135
- if (!doc || !doc.filePath)
136
- return null;
137
- return { taskPath: doc.filePath, task: doc.task };
138
- }
139
- /**
140
- * Move a task to a different column
141
- */
142
- function moveTaskAction(filePath, taskId, targetColumnId) {
143
- const { board, error } = readBoardState(filePath);
144
- if (!board)
145
- return { success: false, error };
146
- const taskInfo = (0, core_1.findTaskById)(board, taskId);
147
- if (!taskInfo)
148
- return { success: false, error: `Task ${taskId} not found` };
149
- const targetColumn = (0, core_1.findColumnById)(board, targetColumnId) || (0, core_1.findColumnByName)(board, targetColumnId);
150
- if (!targetColumn)
151
- return { success: false, error: `Column ${targetColumnId} not found` };
152
- if (taskInfo.column.id === targetColumn.id) {
153
- return { success: true, message: 'Task already in this column' };
154
- }
155
- if ((0, v2_detect_1.isV2)(filePath)) {
156
- const located = resolveV2TaskPath(filePath, taskId);
157
- if (!located)
158
- return { success: false, error: `Task ${taskId} not found` };
159
- const moveResult = (0, core_1.moveTaskFile)(located.taskPath, targetColumn.id, targetColumn.tasks.length);
160
- if (!moveResult.success)
161
- return { success: false, error: moveResult.error };
162
- return { success: true, message: `Moved to ${targetColumn.title}` };
163
- }
164
- const result = (0, core_1.moveTask)(board, taskId, taskInfo.column.id, targetColumn.id, targetColumn.tasks.length);
165
- if (!result.success)
166
- return { success: false, error: result.error };
167
- const writeResult = writeBrainfile(filePath, result.board);
168
- if (!writeResult.success)
169
- return writeResult;
170
- return { success: true, message: `Moved to ${targetColumn.title}` };
171
- }
172
- /**
173
- * Delete a task permanently
174
- */
175
- function deleteTaskAction(filePath, taskId) {
176
- const { board, error } = readBoardState(filePath);
177
- if (!board)
178
- return { success: false, error };
179
- const taskInfo = (0, core_1.findTaskById)(board, taskId);
180
- if (!taskInfo)
181
- return { success: false, error: `Task ${taskId} not found` };
182
- if ((0, v2_detect_1.isV2)(filePath)) {
183
- const located = resolveV2TaskPath(filePath, taskId);
184
- if (!located)
185
- return { success: false, error: `Task ${taskId} not found` };
186
- const result = (0, core_1.deleteTaskFile)(located.taskPath);
187
- if (!result.success)
188
- return { success: false, error: result.error };
189
- return { success: true, message: `Deleted ${taskId}` };
190
- }
191
- const result = (0, core_1.deleteTask)(board, taskInfo.column.id, taskId);
192
- if (!result.success)
193
- return { success: false, error: result.error };
194
- const writeResult = writeBrainfile(filePath, result.board);
195
- if (!writeResult.success)
196
- return writeResult;
197
- return { success: true, message: `Deleted ${taskId}` };
198
- }
199
- /**
200
- * Get the archive file path for a brainfile
201
- * Archive lives in same directory: brainfile.md -> brainfile-archive.md
202
- */
203
- function getArchivePath(filePath) {
204
- const dir = path.dirname(filePath);
205
- const filename = path.basename(filePath);
206
- const archiveFilename = filename.replace(/\.md$/, '-archive.md');
207
- return path.join(dir, archiveFilename);
208
- }
209
- /**
210
- * Create an empty archive board structure
211
- */
212
- function createEmptyArchiveBoard() {
213
- return {
214
- title: 'Logs',
215
- columns: [],
216
- archive: [],
217
- };
218
- }
219
- /**
220
- * Archive a task to a separate brainfile-archive.md file
221
- * Per protocol spec, archived tasks go to a separate file, not inline archive array
222
- */
223
- function archiveTaskAction(filePath, taskId) {
224
- if ((0, v2_detect_1.isV2)(filePath)) {
225
- const dirs = (0, v2_detect_1.getV2Dirs)(filePath);
226
- const located = resolveV2TaskPath(filePath, taskId);
227
- if (!located)
228
- return { success: false, error: `Task ${taskId} not found` };
229
- const result = (0, core_1.completeTaskFile)(located.taskPath, dirs.logsDir);
230
- if (!result.success)
231
- return { success: false, error: result.error };
232
- return { success: true, message: `Moved ${taskId} to logs` };
233
- }
234
- const { board, error } = readBrainfile(filePath);
235
- if (!board)
236
- return { success: false, error };
237
- const taskInfo = (0, core_1.findTaskById)(board, taskId);
238
- if (!taskInfo)
239
- return { success: false, error: `Task ${taskId} not found` };
240
- const task = taskInfo.task;
241
- const columnId = taskInfo.column.id;
242
- // Remove task from the main board (don't use archiveTask - it puts task in inline archive)
243
- const result = (0, core_1.deleteTask)(board, columnId, taskId);
244
- if (!result.success)
245
- return { success: false, error: result.error };
246
- // Save the main board without the task
247
- const writeResult = writeBrainfile(filePath, result.board);
248
- if (!writeResult.success)
249
- return writeResult;
250
- // Now add the task to the separate archive file
251
- const archivePath = getArchivePath(filePath);
252
- // Read or create archive file
253
- let archiveBoard;
254
- if (fs.existsSync(archivePath)) {
255
- try {
256
- const archiveContent = fs.readFileSync(archivePath, 'utf-8');
257
- const parseResult = core_1.Brainfile.parseWithErrors(archiveContent);
258
- if (parseResult.board) {
259
- archiveBoard = parseResult.board;
260
- }
261
- else {
262
- archiveBoard = createEmptyArchiveBoard();
263
- }
264
- }
265
- catch {
266
- archiveBoard = createEmptyArchiveBoard();
267
- }
268
- }
269
- else {
270
- archiveBoard = createEmptyArchiveBoard();
271
- }
272
- // Add task to archive (at beginning)
273
- if (!archiveBoard.archive) {
274
- archiveBoard.archive = [];
275
- }
276
- archiveBoard.archive.unshift(task);
277
- // Save archive file
278
- try {
279
- const archiveContent = core_1.Brainfile.serialize(archiveBoard);
280
- fs.writeFileSync(archivePath, archiveContent, 'utf-8');
281
- }
282
- catch (err) {
283
- // Task was removed from main board but archive write failed
284
- // This is a partial failure state - log and report
285
- return {
286
- success: false,
287
- error: `Task removed from board but failed to write logs: ${err instanceof Error ? err.message : String(err)}`,
288
- };
289
- }
290
- return { success: true, message: `Moved ${taskId} to logs` };
291
- }
292
- /**
293
- * Archive a task with support for external destinations (GitHub, Linear)
294
- * Respects archive.destination from brainfile.md or global config
295
- */
296
- async function archiveTaskActionAsync(filePath, taskId) {
297
- const { board, error } = readBoardState(filePath);
298
- if (!board)
299
- return { success: false, error };
300
- const taskInfo = (0, core_1.findTaskById)(board, taskId);
301
- if (!taskInfo)
302
- return { success: false, error: `Task ${taskId} not found` };
303
- const task = taskInfo.task;
304
- const columnId = taskInfo.column.id;
305
- const columnTitle = taskInfo.column.title;
306
- // Determine destination from brainfile or config
307
- const brainfileDestination = board.archive?.destination;
308
- const parsedDest = (0, config_1.getEffectiveDestination)(brainfileDestination);
309
- const destination = parsedDest.type;
310
- // Handle local archive (default behavior)
311
- if (destination === 'local') {
312
- return archiveTaskAction(filePath, taskId);
313
- }
314
- // Handle GitHub archive
315
- if (destination === 'github') {
316
- if (!(await (0, github_auth_1.isGitHubAuthenticated)())) {
317
- return { success: false, error: 'Not authenticated with GitHub. Run: brainfile auth github' };
318
- }
319
- const config = (0, config_1.getArchiveConfig)();
320
- const owner = parsedDest.owner || config.github?.owner;
321
- const repo = parsedDest.repo || config.github?.repo;
322
- if (!owner || !repo) {
323
- return { success: false, error: 'GitHub owner/repo not configured. Set archive.destination: github:owner/repo' };
324
- }
325
- const payload = (0, core_1.formatTaskForGitHub)(task, {
326
- includeMeta: true,
327
- includeSubtasks: true,
328
- includeRelatedFiles: true,
329
- boardTitle: board.title,
330
- fromColumn: columnTitle,
331
- extraLabels: config.github?.labels,
332
- });
333
- const ghResult = await (0, github_auth_1.createGitHubIssue)({
334
- owner,
335
- repo,
336
- title: payload.title,
337
- body: payload.body,
338
- labels: payload.labels,
339
- state: 'closed',
340
- });
341
- if (!ghResult.success) {
342
- return { success: false, error: `GitHub: ${ghResult.error}` };
343
- }
344
- // Remove task from board
345
- if ((0, v2_detect_1.isV2)(filePath)) {
346
- const located = resolveV2TaskPath(filePath, taskId);
347
- if (located) {
348
- (0, core_1.deleteTaskFile)(located.taskPath);
349
- }
350
- }
351
- else {
352
- const deleteResult = (0, core_1.deleteTask)(board, columnId, taskId);
353
- if (deleteResult.success) {
354
- writeBrainfile(filePath, deleteResult.board);
355
- }
356
- }
357
- return { success: true, message: `Moved to logs via GitHub #${ghResult.issueNumber}` };
358
- }
359
- // Handle Linear archive
360
- if (destination === 'linear') {
361
- if (!(await (0, linear_auth_1.isLinearAuthenticated)())) {
362
- return { success: false, error: 'Not authenticated with Linear. Run: brainfile auth linear --token <key>' };
363
- }
364
- const config = (0, config_1.getArchiveConfig)();
365
- let teamId = config.linear?.teamId;
366
- // Resolve teamKey to teamId if provided in destination
367
- if (parsedDest.teamKey) {
368
- const teams = await (0, linear_auth_1.getLinearTeams)();
369
- const matchingTeam = teams.find((t) => t.key.toLowerCase() === parsedDest.teamKey.toLowerCase());
370
- if (matchingTeam) {
371
- teamId = matchingTeam.id;
372
- }
373
- else {
374
- return { success: false, error: `Linear team "${parsedDest.teamKey}" not found` };
375
- }
376
- }
377
- if (!teamId) {
378
- // Try to auto-select if only one team
379
- const teams = await (0, linear_auth_1.getLinearTeams)();
380
- if (teams.length === 1) {
381
- teamId = teams[0].id;
382
- }
383
- else if (teams.length > 1) {
384
- return { success: false, error: 'Multiple Linear teams. Set archive.destination: linear:TEAM_KEY' };
385
- }
386
- else {
387
- return { success: false, error: 'No Linear teams found' };
388
- }
389
- }
390
- const payload = (0, core_1.formatTaskForLinear)(task, {
391
- includeMeta: true,
392
- includeSubtasks: true,
393
- includeRelatedFiles: true,
394
- boardTitle: board.title,
395
- fromColumn: columnTitle,
396
- stateName: 'Done',
397
- });
398
- const linearResult = await (0, linear_auth_1.createLinearIssue)({
399
- teamId,
400
- title: payload.title,
401
- description: payload.description,
402
- priority: payload.priority,
403
- labelNames: payload.labelNames,
404
- stateName: 'Done',
405
- });
406
- if (!linearResult.success) {
407
- return { success: false, error: `Linear: ${linearResult.error}` };
408
- }
409
- // Remove task from board
410
- if ((0, v2_detect_1.isV2)(filePath)) {
411
- const located = resolveV2TaskPath(filePath, taskId);
412
- if (located) {
413
- (0, core_1.deleteTaskFile)(located.taskPath);
414
- }
415
- }
416
- else {
417
- const deleteResult = (0, core_1.deleteTask)(board, columnId, taskId);
418
- if (deleteResult.success) {
419
- writeBrainfile(filePath, deleteResult.board);
420
- }
421
- }
422
- return { success: true, message: `Moved to logs via Linear ${linearResult.issueId}` };
423
- }
424
- return { success: false, error: `Unknown destination: ${destination}` };
425
- }
426
- /**
427
- * Patch/update task fields
428
- */
429
- function patchTaskAction(filePath, taskId, patch) {
430
- if ((0, v2_detect_1.isV2)(filePath)) {
431
- const located = resolveV2TaskPath(filePath, taskId);
432
- if (!located)
433
- return { success: false, error: `Task ${taskId} not found` };
434
- const doc = (0, core_1.readTaskFile)(located.taskPath);
435
- if (!doc)
436
- return { success: false, error: `Failed to read task ${taskId}` };
437
- const updatedTask = { ...doc.task };
438
- if (patch.title !== undefined)
439
- updatedTask.title = patch.title;
440
- if (patch.description !== undefined) {
441
- if (patch.description === null) {
442
- delete updatedTask.description;
443
- }
444
- else {
445
- updatedTask.description = patch.description;
446
- }
447
- }
448
- if (patch.priority !== undefined) {
449
- if (patch.priority === null) {
450
- delete updatedTask.priority;
451
- }
452
- else {
453
- updatedTask.priority = patch.priority;
454
- }
455
- }
456
- if (patch.tags !== undefined) {
457
- if (patch.tags === null) {
458
- delete updatedTask.tags;
459
- }
460
- else {
461
- updatedTask.tags = patch.tags;
462
- }
463
- }
464
- if (patch.assignee !== undefined) {
465
- if (patch.assignee === null) {
466
- delete updatedTask.assignee;
467
- }
468
- else {
469
- updatedTask.assignee = patch.assignee;
470
- }
471
- }
472
- if (patch.dueDate !== undefined) {
473
- if (patch.dueDate === null) {
474
- delete updatedTask.dueDate;
475
- }
476
- else {
477
- updatedTask.dueDate = patch.dueDate;
478
- }
479
- }
480
- if (patch.relatedFiles !== undefined) {
481
- if (patch.relatedFiles === null) {
482
- delete updatedTask.relatedFiles;
483
- }
484
- else {
485
- updatedTask.relatedFiles = patch.relatedFiles;
486
- }
487
- }
488
- updatedTask.updatedAt = new Date().toISOString();
489
- try {
490
- (0, core_1.writeTaskFile)(located.taskPath, updatedTask, doc.body);
491
- return { success: true, message: `Updated ${taskId}` };
492
- }
493
- catch (err) {
494
- return { success: false, error: err instanceof Error ? err.message : String(err) };
495
- }
496
- }
497
- const { board, error } = readBrainfile(filePath);
498
- if (!board)
499
- return { success: false, error };
500
- const result = (0, core_1.patchTask)(board, taskId, patch);
501
- if (!result.success)
502
- return { success: false, error: result.error };
503
- const writeResult = writeBrainfile(filePath, result.board);
504
- if (!writeResult.success)
505
- return writeResult;
506
- return { success: true, message: `Updated ${taskId}` };
507
- }
508
- /**
509
- * Cycle task priority: none -> low -> medium -> high -> critical -> none
510
- */
511
- function cyclePriorityAction(filePath, taskId) {
512
- const priorities = [undefined, 'low', 'medium', 'high', 'critical'];
513
- if ((0, v2_detect_1.isV2)(filePath)) {
514
- const located = resolveV2TaskPath(filePath, taskId);
515
- if (!located)
516
- return { success: false, error: `Task ${taskId} not found` };
517
- const doc = (0, core_1.readTaskFile)(located.taskPath);
518
- if (!doc)
519
- return { success: false, error: `Failed to read task ${taskId}` };
520
- const currentIndex = priorities.indexOf(doc.task.priority);
521
- const nextIndex = (currentIndex + 1) % priorities.length;
522
- const nextPriority = priorities[nextIndex];
523
- const updatedTask = {
524
- ...doc.task,
525
- ...(nextPriority ? { priority: nextPriority } : {}),
526
- updatedAt: new Date().toISOString(),
527
- };
528
- if (!nextPriority) {
529
- delete updatedTask.priority;
530
- }
531
- try {
532
- (0, core_1.writeTaskFile)(located.taskPath, updatedTask, doc.body);
533
- return { success: true, message: `Priority: ${nextPriority || 'none'}` };
534
- }
535
- catch (err) {
536
- return { success: false, error: err instanceof Error ? err.message : String(err) };
537
- }
538
- }
539
- const { board, error } = readBrainfile(filePath);
540
- if (!board)
541
- return { success: false, error };
542
- const taskInfo = (0, core_1.findTaskById)(board, taskId);
543
- if (!taskInfo)
544
- return { success: false, error: `Task ${taskId} not found` };
545
- const currentIndex = priorities.indexOf(taskInfo.task.priority);
546
- const nextIndex = (currentIndex + 1) % priorities.length;
547
- const nextPriority = priorities[nextIndex];
548
- const patch = { priority: nextPriority || null };
549
- const result = (0, core_1.patchTask)(board, taskId, patch);
550
- if (!result.success)
551
- return { success: false, error: result.error };
552
- const writeResult = writeBrainfile(filePath, result.board);
553
- if (!writeResult.success)
554
- return writeResult;
555
- return { success: true, message: `Priority: ${nextPriority || 'none'}` };
556
- }
557
- /**
558
- * Toggle a subtask's completion status
559
- */
560
- function toggleSubtaskAction(filePath, taskId, subtaskId) {
561
- if ((0, v2_detect_1.isV2)(filePath)) {
562
- const located = resolveV2TaskPath(filePath, taskId);
563
- if (!located)
564
- return { success: false, error: `Task ${taskId} not found` };
565
- const doc = (0, core_1.readTaskFile)(located.taskPath);
566
- if (!doc)
567
- return { success: false, error: `Failed to read task ${taskId}` };
568
- if (!doc.task.subtasks || doc.task.subtasks.length === 0) {
569
- return { success: false, error: `Task ${taskId} has no subtasks` };
570
- }
571
- const updatedSubtasks = doc.task.subtasks.map((subtask) => {
572
- if (subtask.id !== subtaskId)
573
- return subtask;
574
- return { ...subtask, completed: !subtask.completed };
575
- });
576
- const found = updatedSubtasks.some((subtask) => subtask.id === subtaskId);
577
- if (!found)
578
- return { success: false, error: `Subtask ${subtaskId} not found` };
579
- const updatedTask = {
580
- ...doc.task,
581
- subtasks: updatedSubtasks,
582
- updatedAt: new Date().toISOString(),
583
- };
584
- try {
585
- (0, core_1.writeTaskFile)(located.taskPath, updatedTask, doc.body);
586
- return { success: true, message: `Toggled ${subtaskId}` };
587
- }
588
- catch (err) {
589
- return { success: false, error: err instanceof Error ? err.message : String(err) };
590
- }
591
- }
592
- const { board, error } = readBrainfile(filePath);
593
- if (!board)
594
- return { success: false, error };
595
- const result = (0, core_1.toggleSubtask)(board, taskId, subtaskId);
596
- if (!result.success)
597
- return { success: false, error: result.error };
598
- const writeResult = writeBrainfile(filePath, result.board);
599
- if (!writeResult.success)
600
- return writeResult;
601
- return { success: true, message: `Toggled ${subtaskId}` };
602
- }
603
- /**
604
- * Add a new task to a column (quick add - title only)
605
- */
606
- function addTaskAction(filePath, columnId, taskInput) {
607
- const { board, error } = readBoardState(filePath);
608
- if (!board)
609
- return { success: false, error };
610
- const column = (0, core_1.findColumnById)(board, columnId) || (0, core_1.findColumnByName)(board, columnId);
611
- if (!column)
612
- return { success: false, error: `Column ${columnId} not found` };
613
- if ((0, v2_detect_1.isV2)(filePath)) {
614
- const dirs = (0, v2_detect_1.getV2Dirs)(filePath);
615
- const result = (0, core_1.addTaskFile)(dirs.boardDir, {
616
- title: taskInput.title,
617
- column: column.id,
618
- description: taskInput.description,
619
- priority: taskInput.priority,
620
- tags: taskInput.tags,
621
- assignee: taskInput.assignee,
622
- dueDate: taskInput.dueDate,
623
- relatedFiles: taskInput.relatedFiles,
624
- }, taskInput.description ? `## Description\n${taskInput.description.trim()}\n` : '', dirs.logsDir);
625
- if (!result.success)
626
- return { success: false, error: result.error };
627
- return { success: true, message: `Added ${result.task?.id || 'task'}` };
628
- }
629
- const result = (0, core_1.addTask)(board, column.id, taskInput);
630
- if (!result.success)
631
- return { success: false, error: result.error };
632
- const writeResult = writeBrainfile(filePath, result.board);
633
- if (!writeResult.success)
634
- return writeResult;
635
- // Get the new task ID
636
- const newColumn = result.board.columns.find(c => c.id === column.id);
637
- const newTask = newColumn?.tasks[newColumn.tasks.length - 1];
638
- return { success: true, message: `Added ${newTask?.id || 'task'}` };
639
- }
640
- /**
641
- * Create a new task using $EDITOR with full template
642
- */
643
- function newTaskInEditor(filePath, columnId) {
644
- const { board, error } = readBoardState(filePath);
645
- if (!board)
646
- return { success: false, error };
647
- const column = (0, core_1.findColumnById)(board, columnId) || (0, core_1.findColumnByName)(board, columnId);
648
- if (!column)
649
- return { success: false, error: `Column ${columnId} not found` };
650
- // Build template for new task
651
- const template = buildNewTaskTemplate();
652
- // Write to temp file
653
- const tempDir = os.tmpdir();
654
- const tempFile = path.join(tempDir, `brainfile-new-task.yaml`);
655
- try {
656
- fs.writeFileSync(tempFile, template, 'utf-8');
657
- }
658
- catch (err) {
659
- return { success: false, error: `Failed to create temp file: ${err}` };
660
- }
661
- // Get editor from environment
662
- const editor = process.env.EDITOR || process.env.VISUAL || 'nano';
663
- // Use spawnSync to block and give full terminal control to editor
664
- // 5 minute timeout to prevent indefinite hangs
665
- const result = (0, child_process_1.spawnSync)(editor, [tempFile], {
666
- stdio: 'inherit',
667
- shell: true,
668
- timeout: 300000,
669
- });
670
- // Reset terminal state after editor exits - hide cursor and clear line
671
- // This fixes cursor blink artifact when returning to Ink TUI
672
- process.stdout.write('\x1b[?25l'); // Hide cursor
673
- process.stdout.write('\x1b[2K'); // Clear current line
674
- if (result.error) {
675
- cleanupTempFile(tempFile);
676
- return { success: false, error: `Failed to open editor: ${result.error.message}` };
677
- }
678
- if (result.signal === 'SIGTERM') {
679
- cleanupTempFile(tempFile);
680
- return { success: false, error: 'Editor timed out (5 minute limit)' };
681
- }
682
- if (result.status !== 0) {
683
- cleanupTempFile(tempFile);
684
- return { success: false, error: `Editor exited with code ${result.status}` };
685
- }
686
- // Read edited content
687
- let editedYaml;
688
- try {
689
- editedYaml = fs.readFileSync(tempFile, 'utf-8');
690
- }
691
- catch (err) {
692
- cleanupTempFile(tempFile);
693
- return { success: false, error: `Failed to read edited file: ${err}` };
694
- }
695
- cleanupTempFile(tempFile);
696
- // Parse the new task
697
- const parseResult = parseNewTaskYaml(editedYaml);
698
- if (!parseResult.success) {
699
- return { success: false, error: parseResult.error };
700
- }
701
- if (!parseResult.taskInput || !parseResult.taskInput.title) {
702
- return { success: false, error: 'Task title is required' };
703
- }
704
- // Add the task
705
- return addTaskAction(filePath, columnId, parseResult.taskInput);
706
- }
707
- /**
708
- * Build template for a new task
709
- */
710
- function buildNewTaskTemplate() {
711
- return `# New Task
712
- # Fill in the fields below. Save and close to create the task.
713
- # Lines starting with # are ignored. Title is required.
714
-
715
- title:
716
- description:
717
- priority:
718
- tags:
719
- assignee:
720
- dueDate:
721
- `;
722
- }
723
- /**
724
- * Parse new task YAML into TaskInput
725
- */
726
- function parseNewTaskYaml(yaml) {
727
- try {
728
- const lines = yaml.split('\n').filter(line => !line.startsWith('#') && line.trim());
729
- const taskInput = { title: '' };
730
- for (const line of lines) {
731
- const colonIndex = line.indexOf(':');
732
- if (colonIndex === -1)
733
- continue;
734
- const key = line.substring(0, colonIndex).trim();
735
- const value = line.substring(colonIndex + 1).trim();
736
- switch (key) {
737
- case 'title':
738
- if (value)
739
- taskInput.title = value;
740
- break;
741
- case 'description':
742
- if (value)
743
- taskInput.description = value;
744
- break;
745
- case 'priority':
746
- const validPriorities = ['low', 'medium', 'high', 'critical'];
747
- if (validPriorities.includes(value)) {
748
- taskInput.priority = value;
749
- }
750
- break;
751
- case 'tags':
752
- if (value) {
753
- taskInput.tags = value.split(',').map(t => t.trim()).filter(Boolean);
754
- }
755
- break;
756
- case 'assignee':
757
- if (value)
758
- taskInput.assignee = value;
759
- break;
760
- case 'dueDate':
761
- if (value)
762
- taskInput.dueDate = value;
763
- break;
764
- }
765
- }
766
- return { success: true, taskInput };
767
- }
768
- catch (err) {
769
- return { success: false, error: `Failed to parse task: ${err}` };
770
- }
771
- }
772
- /**
773
- * Edit a task in the user's preferred editor
774
- * Uses spawnSync to fully block and give terminal control to the editor
775
- */
776
- function editTaskInEditor(filePath, taskId) {
777
- const { board, error } = readBoardState(filePath);
778
- if (!board) {
779
- return { success: false, error };
780
- }
781
- const taskInfo = (0, core_1.findTaskById)(board, taskId);
782
- if (!taskInfo) {
783
- return { success: false, error: `Task ${taskId} not found` };
784
- }
785
- const task = taskInfo.task;
786
- // Build YAML representation of just this task
787
- const taskYaml = buildTaskYaml(task);
788
- // Write to temp file
789
- const tempDir = os.tmpdir();
790
- const tempFile = path.join(tempDir, `brainfile-edit-${taskId}.yaml`);
791
- try {
792
- fs.writeFileSync(tempFile, taskYaml, 'utf-8');
793
- }
794
- catch (err) {
795
- return { success: false, error: `Failed to create temp file: ${err}` };
796
- }
797
- // Get editor from environment
798
- const editor = process.env.EDITOR || process.env.VISUAL || 'nano';
799
- // Use spawnSync to block and give full terminal control to editor
800
- // 5 minute timeout to prevent indefinite hangs
801
- const result = (0, child_process_1.spawnSync)(editor, [tempFile], {
802
- stdio: 'inherit',
803
- shell: true,
804
- timeout: 300000,
805
- });
806
- // Reset terminal state after editor exits - hide cursor and clear line
807
- // This fixes cursor blink artifact when returning to Ink TUI
808
- process.stdout.write('\x1b[?25l'); // Hide cursor
809
- process.stdout.write('\x1b[2K'); // Clear current line
810
- if (result.error) {
811
- cleanupTempFile(tempFile);
812
- return { success: false, error: `Failed to open editor: ${result.error.message}` };
813
- }
814
- if (result.signal === 'SIGTERM') {
815
- cleanupTempFile(tempFile);
816
- return { success: false, error: 'Editor timed out (5 minute limit)' };
817
- }
818
- if (result.status !== 0) {
819
- cleanupTempFile(tempFile);
820
- return { success: false, error: `Editor exited with code ${result.status}` };
821
- }
822
- // Read edited content
823
- let editedYaml;
824
- try {
825
- editedYaml = fs.readFileSync(tempFile, 'utf-8');
826
- }
827
- catch (err) {
828
- cleanupTempFile(tempFile);
829
- return { success: false, error: `Failed to read edited file: ${err}` };
830
- }
831
- cleanupTempFile(tempFile);
832
- // Parse edited YAML and apply as patch
833
- const patchResult = parseEditedTaskYaml(editedYaml, task);
834
- if (!patchResult.success) {
835
- return { success: false, error: patchResult.error };
836
- }
837
- if (!patchResult.patch || Object.keys(patchResult.patch).length === 0) {
838
- return { success: true, message: 'No changes made' };
839
- }
840
- // Apply the patch
841
- return patchTaskAction(filePath, taskId, patchResult.patch);
842
- }
843
- /**
844
- * Build YAML representation of a task for editing
845
- */
846
- function buildTaskYaml(task) {
847
- const lines = [
848
- `# Edit task: ${task.id}`,
849
- `# Save and close to apply changes. Delete a field to clear it.`,
850
- `# Lines starting with # are ignored.`,
851
- ``,
852
- `title: ${task.title}`,
853
- ];
854
- if (task.description) {
855
- // Handle multi-line descriptions
856
- if (task.description.includes('\n')) {
857
- lines.push(`description: |`);
858
- task.description.split('\n').forEach(line => {
859
- lines.push(` ${line}`);
860
- });
861
- }
862
- else {
863
- lines.push(`description: ${task.description}`);
864
- }
865
- }
866
- else {
867
- lines.push(`# description: `);
868
- }
869
- lines.push(`priority: ${task.priority || ''}`);
870
- lines.push(`tags: ${task.tags?.join(', ') || ''}`);
871
- lines.push(`assignee: ${task.assignee || ''}`);
872
- lines.push(`dueDate: ${task.dueDate || ''}`);
873
- if (task.subtasks && task.subtasks.length > 0) {
874
- lines.push(`subtasks:`);
875
- task.subtasks.forEach(st => {
876
- lines.push(` - [${st.completed ? 'x' : ' '}] ${st.title}`);
877
- });
878
- }
879
- if (task.relatedFiles && task.relatedFiles.length > 0) {
880
- lines.push(`relatedFiles: ${task.relatedFiles.join(', ')}`);
881
- }
882
- return lines.join('\n') + '\n';
883
- }
884
- /**
885
- * Parse edited YAML and return a TaskPatch
886
- */
887
- function parseEditedTaskYaml(yaml, originalTask) {
888
- const patch = {};
889
- try {
890
- const lines = yaml.split('\n').filter(line => !line.startsWith('#') && line.trim());
891
- for (const line of lines) {
892
- const colonIndex = line.indexOf(':');
893
- if (colonIndex === -1)
894
- continue;
895
- const key = line.substring(0, colonIndex).trim();
896
- const value = line.substring(colonIndex + 1).trim();
897
- switch (key) {
898
- case 'title':
899
- if (value && value !== originalTask.title) {
900
- patch.title = value;
901
- }
902
- break;
903
- case 'description':
904
- if (value !== (originalTask.description || '')) {
905
- patch.description = value || undefined;
906
- }
907
- break;
908
- case 'priority':
909
- const validPriorities = ['low', 'medium', 'high', 'critical', ''];
910
- if (validPriorities.includes(value) && value !== (originalTask.priority || '')) {
911
- patch.priority = value || null;
912
- }
913
- break;
914
- case 'tags':
915
- const newTags = value ? value.split(',').map(t => t.trim()).filter(Boolean) : [];
916
- const oldTags = originalTask.tags || [];
917
- if (JSON.stringify(newTags) !== JSON.stringify(oldTags)) {
918
- patch.tags = newTags.length > 0 ? newTags : null;
919
- }
920
- break;
921
- case 'assignee':
922
- if (value !== (originalTask.assignee || '')) {
923
- patch.assignee = value || null;
924
- }
925
- break;
926
- case 'dueDate':
927
- if (value !== (originalTask.dueDate || '')) {
928
- patch.dueDate = value || null;
929
- }
930
- break;
931
- case 'relatedFiles':
932
- const newFiles = value ? value.split(',').map(f => f.trim()).filter(Boolean) : [];
933
- const oldFiles = originalTask.relatedFiles || [];
934
- if (JSON.stringify(newFiles) !== JSON.stringify(oldFiles)) {
935
- patch.relatedFiles = newFiles.length > 0 ? newFiles : null;
936
- }
937
- break;
938
- }
939
- }
940
- return { success: true, patch };
941
- }
942
- catch (err) {
943
- return { success: false, error: `Failed to parse edited YAML: ${err}` };
944
- }
945
- }
946
- /**
947
- * Clean up temp file
948
- */
949
- function cleanupTempFile(filePath) {
950
- try {
951
- if (fs.existsSync(filePath)) {
952
- fs.unlinkSync(filePath);
953
- }
954
- }
955
- catch {
956
- // Ignore cleanup errors
957
- }
958
- }
959
- /**
960
- * Copy text to clipboard (cross-platform)
961
- * Uses spawnSync with input option to pipe text to clipboard command
962
- */
963
- function copyToClipboard(text) {
964
- try {
965
- const platform = process.platform;
966
- let command;
967
- let args;
968
- if (platform === 'darwin') {
969
- command = 'pbcopy';
970
- args = [];
971
- }
972
- else if (platform === 'linux') {
973
- // Try xclip first, fall back to xsel
974
- command = 'xclip';
975
- args = ['-selection', 'clipboard'];
976
- }
977
- else if (platform === 'win32') {
978
- command = 'clip';
979
- args = [];
980
- }
981
- else {
982
- return { success: false, error: 'Clipboard not supported on this platform' };
983
- }
984
- const result = (0, child_process_1.spawnSync)(command, args, {
985
- input: text,
986
- stdio: ['pipe', 'ignore', 'ignore'],
987
- });
988
- if (result.error) {
989
- return { success: false, error: `Clipboard error: ${result.error.message}` };
990
- }
991
- return { success: true, message: 'Copied to clipboard' };
992
- }
993
- catch (err) {
994
- return { success: false, error: `Failed to copy: ${err}` };
995
- }
996
- }
997
- /**
998
- * Add a new rule to a rule category
999
- */
1000
- function addRuleAction(filePath, ruleType, ruleText) {
1001
- const { board, error } = readBoardConfigOnly(filePath);
1002
- if (!board)
1003
- return { success: false, error };
1004
- // Initialize rules if not present
1005
- if (!board.rules) {
1006
- board.rules = {
1007
- always: [],
1008
- never: [],
1009
- prefer: [],
1010
- context: [],
1011
- };
1012
- }
1013
- // Ensure the rule type array exists
1014
- if (!board.rules[ruleType]) {
1015
- board.rules[ruleType] = [];
1016
- }
1017
- // Generate next rule ID
1018
- const existingIds = board.rules[ruleType].map((r) => r.id);
1019
- const maxId = existingIds.length > 0 ? Math.max(...existingIds) : 0;
1020
- const newRuleId = maxId + 1;
1021
- // Add the rule
1022
- board.rules[ruleType].push({
1023
- id: newRuleId,
1024
- rule: ruleText.trim(),
1025
- });
1026
- const writeResult = writeBrainfile(filePath, board);
1027
- if (!writeResult.success)
1028
- return writeResult;
1029
- return { success: true, message: `Added ${ruleType} rule #${newRuleId}` };
1030
- }
1031
- /**
1032
- * Update an existing rule
1033
- */
1034
- function updateRuleAction(filePath, ruleType, ruleId, ruleText) {
1035
- const { board, error } = readBoardConfigOnly(filePath);
1036
- if (!board)
1037
- return { success: false, error };
1038
- if (!board.rules || !board.rules[ruleType]) {
1039
- return { success: false, error: `No ${ruleType} rules found` };
1040
- }
1041
- const rule = board.rules[ruleType].find((r) => r.id === ruleId);
1042
- if (!rule) {
1043
- return { success: false, error: `Rule #${ruleId} not found` };
1044
- }
1045
- rule.rule = ruleText.trim();
1046
- const writeResult = writeBrainfile(filePath, board);
1047
- if (!writeResult.success)
1048
- return writeResult;
1049
- return { success: true, message: `Updated ${ruleType} rule #${ruleId}` };
1050
- }
1051
- /**
1052
- * Delete a rule
1053
- */
1054
- function deleteRuleAction(filePath, ruleType, ruleId) {
1055
- const { board, error } = readBoardConfigOnly(filePath);
1056
- if (!board)
1057
- return { success: false, error };
1058
- if (!board.rules || !board.rules[ruleType]) {
1059
- return { success: false, error: `No ${ruleType} rules found` };
1060
- }
1061
- const ruleIndex = board.rules[ruleType].findIndex((r) => r.id === ruleId);
1062
- if (ruleIndex === -1) {
1063
- return { success: false, error: `Rule #${ruleId} not found` };
1064
- }
1065
- board.rules[ruleType].splice(ruleIndex, 1);
1066
- const writeResult = writeBrainfile(filePath, board);
1067
- if (!writeResult.success)
1068
- return writeResult;
1069
- return { success: true, message: `Deleted ${ruleType} rule #${ruleId}` };
1070
- }
1071
- // =============================================================================
1072
- // ARCHIVE OPERATIONS
1073
- // =============================================================================
1074
- /**
1075
- * Load logs from the logs file
1076
- */
1077
- function loadLogs(filePath) {
1078
- if ((0, v2_detect_1.isV2)(filePath)) {
1079
- try {
1080
- const dirs = (0, v2_detect_1.getV2Dirs)(filePath);
1081
- const docs = (0, core_1.readTasksDir)(dirs.logsDir);
1082
- const logs = docs
1083
- .map((doc) => ({ ...doc.task }))
1084
- .sort((a, b) => {
1085
- const aTime = a.completedAt ? Date.parse(a.completedAt) : 0;
1086
- const bTime = b.completedAt ? Date.parse(b.completedAt) : 0;
1087
- return bTime - aTime;
1088
- });
1089
- return { logs };
1090
- }
1091
- catch (err) {
1092
- return { logs: [], error: err instanceof Error ? err.message : String(err) };
1093
- }
1094
- }
1095
- const archivePath = getArchivePath(filePath);
1096
- if (!fs.existsSync(archivePath)) {
1097
- return { logs: [] };
1098
- }
1099
- try {
1100
- const archiveContent = fs.readFileSync(archivePath, 'utf-8');
1101
- const parseResult = core_1.Brainfile.parseWithErrors(archiveContent);
1102
- if (parseResult.board && parseResult.board.archive) {
1103
- return { logs: parseResult.board.archive };
1104
- }
1105
- return { logs: [] };
1106
- }
1107
- catch (err) {
1108
- return { logs: [], error: err instanceof Error ? err.message : String(err) };
1109
- }
1110
- }
1111
- /**
1112
- * Restore a task from logs to a column
1113
- */
1114
- function restoreTaskAction(filePath, taskId, toColumnId) {
1115
- if ((0, v2_detect_1.isV2)(filePath)) {
1116
- const dirs = (0, v2_detect_1.getV2Dirs)(filePath);
1117
- const { board, error } = readBoardState(filePath);
1118
- if (!board)
1119
- return { success: false, error };
1120
- const column = (0, core_1.findColumnById)(board, toColumnId) || (0, core_1.findColumnByName)(board, toColumnId);
1121
- if (!column) {
1122
- return { success: false, error: `Column ${toColumnId} not found` };
1123
- }
1124
- if ((0, core_1.findTask)(dirs.boardDir, taskId)) {
1125
- return { success: false, error: `Task ${taskId} already exists in board` };
1126
- }
1127
- const logDoc = (0, core_1.findTask)(dirs.logsDir, taskId);
1128
- if (!logDoc || !logDoc.filePath) {
1129
- return { success: false, error: `Task ${taskId} not found in logs` };
1130
- }
1131
- const restoredTask = {
1132
- ...logDoc.task,
1133
- column: column.id,
1134
- updatedAt: new Date().toISOString(),
1135
- };
1136
- delete restoredTask.completedAt;
1137
- const targetPath = path.join(dirs.boardDir, `${taskId}.md`);
1138
- try {
1139
- (0, core_1.writeTaskFile)(targetPath, restoredTask, logDoc.body);
1140
- fs.unlinkSync(logDoc.filePath);
1141
- return { success: true, message: `Restored ${taskId} to ${column.title}` };
1142
- }
1143
- catch (err) {
1144
- return { success: false, error: `Failed to restore task: ${err instanceof Error ? err.message : String(err)}` };
1145
- }
1146
- }
1147
- const archivePath = getArchivePath(filePath);
1148
- // Read archive file
1149
- if (!fs.existsSync(archivePath)) {
1150
- return { success: false, error: 'Logs file not found' };
1151
- }
1152
- let archiveBoard;
1153
- try {
1154
- const archiveContent = fs.readFileSync(archivePath, 'utf-8');
1155
- const parseResult = core_1.Brainfile.parseWithErrors(archiveContent);
1156
- if (!parseResult.board) {
1157
- return { success: false, error: 'Failed to parse logs file' };
1158
- }
1159
- archiveBoard = parseResult.board;
1160
- }
1161
- catch (err) {
1162
- return { success: false, error: `Failed to read logs: ${err instanceof Error ? err.message : String(err)}` };
1163
- }
1164
- // Find task in archive
1165
- if (!archiveBoard.archive || archiveBoard.archive.length === 0) {
1166
- return { success: false, error: 'Logs are empty' };
1167
- }
1168
- const taskIndex = archiveBoard.archive.findIndex(t => t.id === taskId);
1169
- if (taskIndex === -1) {
1170
- return { success: false, error: `Task ${taskId} not found in logs` };
1171
- }
1172
- const task = archiveBoard.archive[taskIndex];
1173
- // Read main board
1174
- const { board: mainBoard, error: mainBoardError } = readBrainfile(filePath);
1175
- if (!mainBoard)
1176
- return { success: false, error: mainBoardError };
1177
- // Find target column
1178
- const column = (0, core_1.findColumnById)(mainBoard, toColumnId) || (0, core_1.findColumnByName)(mainBoard, toColumnId);
1179
- if (!column) {
1180
- return { success: false, error: `Column ${toColumnId} not found` };
1181
- }
1182
- // Add task to column (at beginning)
1183
- column.tasks.unshift(task);
1184
- // Remove from archive
1185
- archiveBoard.archive.splice(taskIndex, 1);
1186
- // Save both files
1187
- const writeResult = writeBrainfile(filePath, mainBoard);
1188
- if (!writeResult.success)
1189
- return writeResult;
1190
- try {
1191
- const archiveContent = core_1.Brainfile.serialize(archiveBoard);
1192
- fs.writeFileSync(archivePath, archiveContent, 'utf-8');
1193
- }
1194
- catch (err) {
1195
- return { success: false, error: `Task restored but failed to update logs: ${err}` };
1196
- }
1197
- return { success: true, message: `Restored ${taskId} to ${column.title}` };
1198
- }
1199
- /**
1200
- * Permanently delete a task from logs
1201
- */
1202
- function deleteArchivedTaskAction(filePath, taskId) {
1203
- if ((0, v2_detect_1.isV2)(filePath)) {
1204
- const dirs = (0, v2_detect_1.getV2Dirs)(filePath);
1205
- const logDoc = (0, core_1.findTask)(dirs.logsDir, taskId);
1206
- if (!logDoc || !logDoc.filePath) {
1207
- return { success: false, error: `Task ${taskId} not found in logs` };
1208
- }
1209
- const result = (0, core_1.deleteTaskFile)(logDoc.filePath);
1210
- if (!result.success) {
1211
- return { success: false, error: result.error || `Failed to delete ${taskId}` };
1212
- }
1213
- return { success: true, message: `Permanently deleted ${taskId}` };
1214
- }
1215
- const archivePath = getArchivePath(filePath);
1216
- // Read archive file
1217
- if (!fs.existsSync(archivePath)) {
1218
- return { success: false, error: 'Logs file not found' };
1219
- }
1220
- let archiveBoard;
1221
- try {
1222
- const archiveContent = fs.readFileSync(archivePath, 'utf-8');
1223
- const parseResult = core_1.Brainfile.parseWithErrors(archiveContent);
1224
- if (!parseResult.board) {
1225
- return { success: false, error: 'Failed to parse logs file' };
1226
- }
1227
- archiveBoard = parseResult.board;
1228
- }
1229
- catch (err) {
1230
- return { success: false, error: `Failed to read logs: ${err instanceof Error ? err.message : String(err)}` };
1231
- }
1232
- // Find and remove task from archive
1233
- if (!archiveBoard.archive || archiveBoard.archive.length === 0) {
1234
- return { success: false, error: 'Logs are empty' };
1235
- }
1236
- const taskIndex = archiveBoard.archive.findIndex(t => t.id === taskId);
1237
- if (taskIndex === -1) {
1238
- return { success: false, error: `Task ${taskId} not found in logs` };
1239
- }
1240
- archiveBoard.archive.splice(taskIndex, 1);
1241
- // Save archive file
1242
- try {
1243
- const archiveContent = core_1.Brainfile.serialize(archiveBoard);
1244
- fs.writeFileSync(archivePath, archiveContent, 'utf-8');
1245
- }
1246
- catch (err) {
1247
- return { success: false, error: `Failed to update logs: ${err}` };
1248
- }
1249
- return { success: true, message: `Permanently deleted ${taskId}` };
1250
- }
1251
- //# sourceMappingURL=actions.js.map