@waymakeros/cli 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -0
- package/dist/cli/cli.d.ts +7 -0
- package/dist/cli/cli.d.ts.map +1 -0
- package/dist/cli/cli.js +703 -0
- package/dist/cli/cli.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +14 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +507 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/init.d.ts +13 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +297 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/kanban.d.ts +12 -0
- package/dist/cli/commands/kanban.d.ts.map +1 -0
- package/dist/cli/commands/kanban.js +71 -0
- package/dist/cli/commands/kanban.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +12 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +170 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +12 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +222 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/daemon.d.ts +106 -0
- package/dist/cli/daemon.d.ts.map +1 -0
- package/dist/cli/daemon.js +393 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +90 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +238 -0
- package/dist/index.js.map +1 -0
- package/dist/simple-server.d.ts +7 -0
- package/dist/simple-server.d.ts.map +1 -0
- package/dist/simple-server.js +157 -0
- package/dist/simple-server.js.map +1 -0
- package/dist/sync/commander-client.d.ts +161 -0
- package/dist/sync/commander-client.d.ts.map +1 -0
- package/dist/sync/commander-client.js +405 -0
- package/dist/sync/commander-client.js.map +1 -0
- package/dist/sync/config-manager.d.ts +48 -0
- package/dist/sync/config-manager.d.ts.map +1 -0
- package/dist/sync/config-manager.js +169 -0
- package/dist/sync/config-manager.js.map +1 -0
- package/dist/sync/file-watcher.d.ts +53 -0
- package/dist/sync/file-watcher.d.ts.map +1 -0
- package/dist/sync/file-watcher.js +228 -0
- package/dist/sync/file-watcher.js.map +1 -0
- package/dist/sync/folder-service.d.ts +110 -0
- package/dist/sync/folder-service.d.ts.map +1 -0
- package/dist/sync/folder-service.js +298 -0
- package/dist/sync/folder-service.js.map +1 -0
- package/dist/sync/folder-status-mapper.d.ts +106 -0
- package/dist/sync/folder-status-mapper.d.ts.map +1 -0
- package/dist/sync/folder-status-mapper.js +235 -0
- package/dist/sync/folder-status-mapper.js.map +1 -0
- package/dist/sync/layer-resolver.d.ts +54 -0
- package/dist/sync/layer-resolver.d.ts.map +1 -0
- package/dist/sync/layer-resolver.js +206 -0
- package/dist/sync/layer-resolver.js.map +1 -0
- package/dist/sync/markdown-parser.d.ts +49 -0
- package/dist/sync/markdown-parser.d.ts.map +1 -0
- package/dist/sync/markdown-parser.js +202 -0
- package/dist/sync/markdown-parser.js.map +1 -0
- package/dist/sync/reverse-sync.d.ts +139 -0
- package/dist/sync/reverse-sync.d.ts.map +1 -0
- package/dist/sync/reverse-sync.js +773 -0
- package/dist/sync/reverse-sync.js.map +1 -0
- package/dist/sync/sync-engine.d.ts +157 -0
- package/dist/sync/sync-engine.d.ts.map +1 -0
- package/dist/sync/sync-engine.js +875 -0
- package/dist/sync/sync-engine.js.map +1 -0
- package/dist/sync/sync-index.d.ts +150 -0
- package/dist/sync/sync-index.d.ts.map +1 -0
- package/dist/sync/sync-index.js +287 -0
- package/dist/sync/sync-index.js.map +1 -0
- package/dist/sync/trinity-mapper.d.ts +62 -0
- package/dist/sync/trinity-mapper.d.ts.map +1 -0
- package/dist/sync/trinity-mapper.js +548 -0
- package/dist/sync/trinity-mapper.js.map +1 -0
- package/dist/sync/types.d.ts +176 -0
- package/dist/sync/types.d.ts.map +1 -0
- package/dist/sync/types.js +6 -0
- package/dist/sync/types.js.map +1 -0
- package/dist/tools/apply-framework.d.ts +7 -0
- package/dist/tools/apply-framework.d.ts.map +1 -0
- package/dist/tools/apply-framework.js +30 -0
- package/dist/tools/apply-framework.js.map +1 -0
- package/dist/tools/commander-comment-create.d.ts +7 -0
- package/dist/tools/commander-comment-create.d.ts.map +1 -0
- package/dist/tools/commander-comment-create.js +100 -0
- package/dist/tools/commander-comment-create.js.map +1 -0
- package/dist/tools/commander-comment-list.d.ts +7 -0
- package/dist/tools/commander-comment-list.d.ts.map +1 -0
- package/dist/tools/commander-comment-list.js +110 -0
- package/dist/tools/commander-comment-list.js.map +1 -0
- package/dist/tools/commander-document-create.d.ts +7 -0
- package/dist/tools/commander-document-create.d.ts.map +1 -0
- package/dist/tools/commander-document-create.js +168 -0
- package/dist/tools/commander-document-create.js.map +1 -0
- package/dist/tools/commander-document-get.d.ts +7 -0
- package/dist/tools/commander-document-get.d.ts.map +1 -0
- package/dist/tools/commander-document-get.js +104 -0
- package/dist/tools/commander-document-get.js.map +1 -0
- package/dist/tools/commander-document-list.d.ts +7 -0
- package/dist/tools/commander-document-list.d.ts.map +1 -0
- package/dist/tools/commander-document-list.js +117 -0
- package/dist/tools/commander-document-list.js.map +1 -0
- package/dist/tools/commander-document-update.d.ts +7 -0
- package/dist/tools/commander-document-update.d.ts.map +1 -0
- package/dist/tools/commander-document-update.js +133 -0
- package/dist/tools/commander-document-update.js.map +1 -0
- package/dist/tools/commander-folder-create.d.ts +7 -0
- package/dist/tools/commander-folder-create.d.ts.map +1 -0
- package/dist/tools/commander-folder-create.js +157 -0
- package/dist/tools/commander-folder-create.js.map +1 -0
- package/dist/tools/commander-folder-delete.d.ts +7 -0
- package/dist/tools/commander-folder-delete.d.ts.map +1 -0
- package/dist/tools/commander-folder-delete.js +85 -0
- package/dist/tools/commander-folder-delete.js.map +1 -0
- package/dist/tools/commander-folder-get.d.ts +7 -0
- package/dist/tools/commander-folder-get.d.ts.map +1 -0
- package/dist/tools/commander-folder-get.js +94 -0
- package/dist/tools/commander-folder-get.js.map +1 -0
- package/dist/tools/commander-folder-list.d.ts +7 -0
- package/dist/tools/commander-folder-list.d.ts.map +1 -0
- package/dist/tools/commander-folder-list.js +96 -0
- package/dist/tools/commander-folder-list.js.map +1 -0
- package/dist/tools/commander-folder-update.d.ts +7 -0
- package/dist/tools/commander-folder-update.d.ts.map +1 -0
- package/dist/tools/commander-folder-update.js +120 -0
- package/dist/tools/commander-folder-update.js.map +1 -0
- package/dist/tools/commander-framework-apply.d.ts +7 -0
- package/dist/tools/commander-framework-apply.d.ts.map +1 -0
- package/dist/tools/commander-framework-apply.js +110 -0
- package/dist/tools/commander-framework-apply.js.map +1 -0
- package/dist/tools/commander-framework-categories.d.ts +7 -0
- package/dist/tools/commander-framework-categories.d.ts.map +1 -0
- package/dist/tools/commander-framework-categories.js +90 -0
- package/dist/tools/commander-framework-categories.js.map +1 -0
- package/dist/tools/commander-framework-get.d.ts +7 -0
- package/dist/tools/commander-framework-get.d.ts.map +1 -0
- package/dist/tools/commander-framework-get.js +124 -0
- package/dist/tools/commander-framework-get.js.map +1 -0
- package/dist/tools/commander-framework-list.d.ts +7 -0
- package/dist/tools/commander-framework-list.d.ts.map +1 -0
- package/dist/tools/commander-framework-list.js +103 -0
- package/dist/tools/commander-framework-list.js.map +1 -0
- package/dist/tools/commander-goal-create.d.ts +7 -0
- package/dist/tools/commander-goal-create.d.ts.map +1 -0
- package/dist/tools/commander-goal-create.js +130 -0
- package/dist/tools/commander-goal-create.js.map +1 -0
- package/dist/tools/commander-goal-get.d.ts +7 -0
- package/dist/tools/commander-goal-get.d.ts.map +1 -0
- package/dist/tools/commander-goal-get.js +83 -0
- package/dist/tools/commander-goal-get.js.map +1 -0
- package/dist/tools/commander-goal-list.d.ts +7 -0
- package/dist/tools/commander-goal-list.d.ts.map +1 -0
- package/dist/tools/commander-goal-list.js +111 -0
- package/dist/tools/commander-goal-list.js.map +1 -0
- package/dist/tools/commander-goal-update.d.ts +7 -0
- package/dist/tools/commander-goal-update.d.ts.map +1 -0
- package/dist/tools/commander-goal-update.js +110 -0
- package/dist/tools/commander-goal-update.js.map +1 -0
- package/dist/tools/commander-key-result-create.d.ts +7 -0
- package/dist/tools/commander-key-result-create.d.ts.map +1 -0
- package/dist/tools/commander-key-result-create.js +134 -0
- package/dist/tools/commander-key-result-create.js.map +1 -0
- package/dist/tools/commander-key-result-update.d.ts +7 -0
- package/dist/tools/commander-key-result-update.d.ts.map +1 -0
- package/dist/tools/commander-key-result-update.js +119 -0
- package/dist/tools/commander-key-result-update.js.map +1 -0
- package/dist/tools/commander-layer-create.d.ts +7 -0
- package/dist/tools/commander-layer-create.d.ts.map +1 -0
- package/dist/tools/commander-layer-create.js +167 -0
- package/dist/tools/commander-layer-create.js.map +1 -0
- package/dist/tools/commander-layer-delete.d.ts +7 -0
- package/dist/tools/commander-layer-delete.d.ts.map +1 -0
- package/dist/tools/commander-layer-delete.js +141 -0
- package/dist/tools/commander-layer-delete.js.map +1 -0
- package/dist/tools/commander-layer-list.d.ts +7 -0
- package/dist/tools/commander-layer-list.d.ts.map +1 -0
- package/dist/tools/commander-layer-list.js +102 -0
- package/dist/tools/commander-layer-list.js.map +1 -0
- package/dist/tools/commander-presentation.d.ts +7 -0
- package/dist/tools/commander-presentation.d.ts.map +1 -0
- package/dist/tools/commander-presentation.js +185 -0
- package/dist/tools/commander-presentation.js.map +1 -0
- package/dist/tools/commander-project-create.d.ts +7 -0
- package/dist/tools/commander-project-create.d.ts.map +1 -0
- package/dist/tools/commander-project-create.js +130 -0
- package/dist/tools/commander-project-create.js.map +1 -0
- package/dist/tools/commander-project-get.d.ts +7 -0
- package/dist/tools/commander-project-get.d.ts.map +1 -0
- package/dist/tools/commander-project-get.js +107 -0
- package/dist/tools/commander-project-get.js.map +1 -0
- package/dist/tools/commander-project-list.d.ts +7 -0
- package/dist/tools/commander-project-list.d.ts.map +1 -0
- package/dist/tools/commander-project-list.js +104 -0
- package/dist/tools/commander-project-list.js.map +1 -0
- package/dist/tools/commander-project-update.d.ts +7 -0
- package/dist/tools/commander-project-update.d.ts.map +1 -0
- package/dist/tools/commander-project-update.js +126 -0
- package/dist/tools/commander-project-update.js.map +1 -0
- package/dist/tools/commander-project.d.ts +7 -0
- package/dist/tools/commander-project.d.ts.map +1 -0
- package/dist/tools/commander-project.js +144 -0
- package/dist/tools/commander-project.js.map +1 -0
- package/dist/tools/commander-role-assign.d.ts +7 -0
- package/dist/tools/commander-role-assign.d.ts.map +1 -0
- package/dist/tools/commander-role-assign.js +115 -0
- package/dist/tools/commander-role-assign.js.map +1 -0
- package/dist/tools/commander-role-create.d.ts +7 -0
- package/dist/tools/commander-role-create.d.ts.map +1 -0
- package/dist/tools/commander-role-create.js +130 -0
- package/dist/tools/commander-role-create.js.map +1 -0
- package/dist/tools/commander-role-get.d.ts +7 -0
- package/dist/tools/commander-role-get.d.ts.map +1 -0
- package/dist/tools/commander-role-get.js +108 -0
- package/dist/tools/commander-role-get.js.map +1 -0
- package/dist/tools/commander-role-list.d.ts +7 -0
- package/dist/tools/commander-role-list.d.ts.map +1 -0
- package/dist/tools/commander-role-list.js +106 -0
- package/dist/tools/commander-role-list.js.map +1 -0
- package/dist/tools/commander-role-update.d.ts +7 -0
- package/dist/tools/commander-role-update.d.ts.map +1 -0
- package/dist/tools/commander-role-update.js +126 -0
- package/dist/tools/commander-role-update.js.map +1 -0
- package/dist/tools/commander-search.d.ts +7 -0
- package/dist/tools/commander-search.d.ts.map +1 -0
- package/dist/tools/commander-search.js +146 -0
- package/dist/tools/commander-search.js.map +1 -0
- package/dist/tools/commander-sheet-create.d.ts +7 -0
- package/dist/tools/commander-sheet-create.d.ts.map +1 -0
- package/dist/tools/commander-sheet-create.js +160 -0
- package/dist/tools/commander-sheet-create.js.map +1 -0
- package/dist/tools/commander-sheet-get.d.ts +7 -0
- package/dist/tools/commander-sheet-get.d.ts.map +1 -0
- package/dist/tools/commander-sheet-get.js +128 -0
- package/dist/tools/commander-sheet-get.js.map +1 -0
- package/dist/tools/commander-sheet-list.d.ts +7 -0
- package/dist/tools/commander-sheet-list.d.ts.map +1 -0
- package/dist/tools/commander-sheet-list.js +108 -0
- package/dist/tools/commander-sheet-list.js.map +1 -0
- package/dist/tools/commander-sheet-update.d.ts +7 -0
- package/dist/tools/commander-sheet-update.d.ts.map +1 -0
- package/dist/tools/commander-sheet-update.js +163 -0
- package/dist/tools/commander-sheet-update.js.map +1 -0
- package/dist/tools/commander-status-list.d.ts +7 -0
- package/dist/tools/commander-status-list.d.ts.map +1 -0
- package/dist/tools/commander-status-list.js +103 -0
- package/dist/tools/commander-status-list.js.map +1 -0
- package/dist/tools/commander-task-assign.d.ts +7 -0
- package/dist/tools/commander-task-assign.d.ts.map +1 -0
- package/dist/tools/commander-task-assign.js +91 -0
- package/dist/tools/commander-task-assign.js.map +1 -0
- package/dist/tools/commander-task-create.d.ts +7 -0
- package/dist/tools/commander-task-create.d.ts.map +1 -0
- package/dist/tools/commander-task-create.js +142 -0
- package/dist/tools/commander-task-create.js.map +1 -0
- package/dist/tools/commander-task-delete.d.ts +7 -0
- package/dist/tools/commander-task-delete.d.ts.map +1 -0
- package/dist/tools/commander-task-delete.js +88 -0
- package/dist/tools/commander-task-delete.js.map +1 -0
- package/dist/tools/commander-task-list-mine.d.ts +7 -0
- package/dist/tools/commander-task-list-mine.d.ts.map +1 -0
- package/dist/tools/commander-task-list-mine.js +109 -0
- package/dist/tools/commander-task-list-mine.js.map +1 -0
- package/dist/tools/commander-task-read.d.ts +10 -0
- package/dist/tools/commander-task-read.d.ts.map +1 -0
- package/dist/tools/commander-task-read.js +96 -0
- package/dist/tools/commander-task-read.js.map +1 -0
- package/dist/tools/commander-task-update.d.ts +7 -0
- package/dist/tools/commander-task-update.d.ts.map +1 -0
- package/dist/tools/commander-task-update.js +159 -0
- package/dist/tools/commander-task-update.js.map +1 -0
- package/dist/tools/commander-taskboard-tasks.d.ts +7 -0
- package/dist/tools/commander-taskboard-tasks.d.ts.map +1 -0
- package/dist/tools/commander-taskboard-tasks.js +97 -0
- package/dist/tools/commander-taskboard-tasks.js.map +1 -0
- package/dist/tools/commander-team-add-member.d.ts +7 -0
- package/dist/tools/commander-team-add-member.d.ts.map +1 -0
- package/dist/tools/commander-team-add-member.js +104 -0
- package/dist/tools/commander-team-add-member.js.map +1 -0
- package/dist/tools/commander-team-create.d.ts +7 -0
- package/dist/tools/commander-team-create.d.ts.map +1 -0
- package/dist/tools/commander-team-create.js +117 -0
- package/dist/tools/commander-team-create.js.map +1 -0
- package/dist/tools/commander-team-list.d.ts +7 -0
- package/dist/tools/commander-team-list.d.ts.map +1 -0
- package/dist/tools/commander-team-list.js +94 -0
- package/dist/tools/commander-team-list.js.map +1 -0
- package/dist/tools/commander-team-remove-member.d.ts +7 -0
- package/dist/tools/commander-team-remove-member.d.ts.map +1 -0
- package/dist/tools/commander-team-remove-member.js +98 -0
- package/dist/tools/commander-team-remove-member.js.map +1 -0
- package/dist/tools/commander-user-get.d.ts +7 -0
- package/dist/tools/commander-user-get.d.ts.map +1 -0
- package/dist/tools/commander-user-get.js +101 -0
- package/dist/tools/commander-user-get.js.map +1 -0
- package/dist/tools/commander-user-invite.d.ts +7 -0
- package/dist/tools/commander-user-invite.d.ts.map +1 -0
- package/dist/tools/commander-user-invite.js +119 -0
- package/dist/tools/commander-user-invite.js.map +1 -0
- package/dist/tools/commander-user-list.d.ts +7 -0
- package/dist/tools/commander-user-list.d.ts.map +1 -0
- package/dist/tools/commander-user-list.js +108 -0
- package/dist/tools/commander-user-list.js.map +1 -0
- package/dist/tools/commander-workspace-create.d.ts +7 -0
- package/dist/tools/commander-workspace-create.d.ts.map +1 -0
- package/dist/tools/commander-workspace-create.js +124 -0
- package/dist/tools/commander-workspace-create.js.map +1 -0
- package/dist/tools/commander-workspace-list.d.ts +7 -0
- package/dist/tools/commander-workspace-list.d.ts.map +1 -0
- package/dist/tools/commander-workspace-list.js +95 -0
- package/dist/tools/commander-workspace-list.js.map +1 -0
- package/dist/tools/commander-workspace-update.d.ts +7 -0
- package/dist/tools/commander-workspace-update.d.ts.map +1 -0
- package/dist/tools/commander-workspace-update.js +118 -0
- package/dist/tools/commander-workspace-update.js.map +1 -0
- package/dist/tools/commander-workspace.d.ts +7 -0
- package/dist/tools/commander-workspace.d.ts.map +1 -0
- package/dist/tools/commander-workspace.js +131 -0
- package/dist/tools/commander-workspace.js.map +1 -0
- package/dist/tools/create-kanban.d.ts +7 -0
- package/dist/tools/create-kanban.d.ts.map +1 -0
- package/dist/tools/create-kanban.js +32 -0
- package/dist/tools/create-kanban.js.map +1 -0
- package/dist/tools/create-table.d.ts +7 -0
- package/dist/tools/create-table.d.ts.map +1 -0
- package/dist/tools/create-table.js +188 -0
- package/dist/tools/create-table.js.map +1 -0
- package/dist/tools/search-knowledge.d.ts +7 -0
- package/dist/tools/search-knowledge.d.ts.map +1 -0
- package/dist/tools/search-knowledge.js +33 -0
- package/dist/tools/search-knowledge.js.map +1 -0
- package/dist/tools/waymaker-kanban-view.d.ts +7 -0
- package/dist/tools/waymaker-kanban-view.d.ts.map +1 -0
- package/dist/tools/waymaker-kanban-view.js +255 -0
- package/dist/tools/waymaker-kanban-view.js.map +1 -0
- package/dist/tools/waymaker-sync-configure.d.ts +13 -0
- package/dist/tools/waymaker-sync-configure.d.ts.map +1 -0
- package/dist/tools/waymaker-sync-configure.js +291 -0
- package/dist/tools/waymaker-sync-configure.js.map +1 -0
- package/dist/tools/waymaker-sync-file.d.ts +7 -0
- package/dist/tools/waymaker-sync-file.d.ts.map +1 -0
- package/dist/tools/waymaker-sync-file.js +79 -0
- package/dist/tools/waymaker-sync-file.js.map +1 -0
- package/dist/tools/waymaker-sync-poll.d.ts +8 -0
- package/dist/tools/waymaker-sync-poll.d.ts.map +1 -0
- package/dist/tools/waymaker-sync-poll.js +398 -0
- package/dist/tools/waymaker-sync-poll.js.map +1 -0
- package/dist/tools/waymaker-sync-start.d.ts +7 -0
- package/dist/tools/waymaker-sync-start.d.ts.map +1 -0
- package/dist/tools/waymaker-sync-start.js +56 -0
- package/dist/tools/waymaker-sync-start.js.map +1 -0
- package/dist/tools/waymaker-sync-status.d.ts +7 -0
- package/dist/tools/waymaker-sync-status.d.ts.map +1 -0
- package/dist/tools/waymaker-sync-status.js +45 -0
- package/dist/tools/waymaker-sync-status.js.map +1 -0
- package/dist/tools/waymaker-sync-stop.d.ts +7 -0
- package/dist/tools/waymaker-sync-stop.d.ts.map +1 -0
- package/dist/tools/waymaker-sync-stop.js +56 -0
- package/dist/tools/waymaker-sync-stop.js.map +1 -0
- package/dist/tools/waymaker-sync-workspace.d.ts +26 -0
- package/dist/tools/waymaker-sync-workspace.d.ts.map +1 -0
- package/dist/tools/waymaker-sync-workspace.js +148 -0
- package/dist/tools/waymaker-sync-workspace.js.map +1 -0
- package/dist/tools/waymaker-sync-workspace.tool.d.ts +9 -0
- package/dist/tools/waymaker-sync-workspace.tool.d.ts.map +1 -0
- package/dist/tools/waymaker-sync-workspace.tool.js +68 -0
- package/dist/tools/waymaker-sync-workspace.tool.js.map +1 -0
- package/dist/types/tool.d.ts +13 -0
- package/dist/types/tool.d.ts.map +1 -0
- package/dist/types/tool.js +2 -0
- package/dist/types/tool.js.map +1 -0
- package/dist/utils/daemon-health.d.ts +21 -0
- package/dist/utils/daemon-health.d.ts.map +1 -0
- package/dist/utils/daemon-health.js +40 -0
- package/dist/utils/daemon-health.js.map +1 -0
- package/dist/utils/fetch-ipv4.d.ts +36 -0
- package/dist/utils/fetch-ipv4.d.ts.map +1 -0
- package/dist/utils/fetch-ipv4.js +91 -0
- package/dist/utils/fetch-ipv4.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReverseSync: Commander → IDE Synchronization
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to Commander taskboard changes and syncs them to IDE filesystem.
|
|
5
|
+
* Part of bidirectional Waymaker Sync.
|
|
6
|
+
*/
|
|
7
|
+
// Polyfill WebSocket for Node.js (required by Supabase Realtime)
|
|
8
|
+
import WebSocket from 'ws';
|
|
9
|
+
if (typeof globalThis.WebSocket === 'undefined') {
|
|
10
|
+
globalThis.WebSocket = WebSocket;
|
|
11
|
+
}
|
|
12
|
+
import { createClient } from '@supabase/supabase-js';
|
|
13
|
+
import * as path from 'path';
|
|
14
|
+
import * as fs from 'fs/promises';
|
|
15
|
+
import { EventEmitter } from 'events';
|
|
16
|
+
export class ReverseSync extends EventEmitter {
|
|
17
|
+
config;
|
|
18
|
+
supabase;
|
|
19
|
+
channel = null;
|
|
20
|
+
running = false;
|
|
21
|
+
fileMap = new Map(); // task_id → file_path
|
|
22
|
+
sectionMap = new Map(); // status_section_id → folder_name
|
|
23
|
+
pendingChanges = new Map();
|
|
24
|
+
syncTimer = null;
|
|
25
|
+
reconnectAttempts = 0;
|
|
26
|
+
maxReconnectAttempts = 10;
|
|
27
|
+
reconnectTimer = null;
|
|
28
|
+
supabaseToken = null;
|
|
29
|
+
tokenExpiresAt = null;
|
|
30
|
+
tokenRefreshTimer = null;
|
|
31
|
+
recentEvents = new Map(); // "taskId:eventType" → timestamp
|
|
32
|
+
dedupeWindowMs = 2000; // Ignore duplicate events within 2s
|
|
33
|
+
constructor(config) {
|
|
34
|
+
super();
|
|
35
|
+
this.config = config;
|
|
36
|
+
// Initialize Supabase client with anon key
|
|
37
|
+
// After token exchange, we'll call setAuth() with the Supabase JWT
|
|
38
|
+
this.supabase = createClient(config.supabaseUrl, config.supabaseKey);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Exchange OAuth token (wm_at_*) for a Supabase-compatible JWT
|
|
42
|
+
* This allows Realtime subscriptions to pass RLS policies
|
|
43
|
+
*/
|
|
44
|
+
async exchangeTokenForSupabaseJWT() {
|
|
45
|
+
if (!this.config.apiKey?.startsWith('wm_at_')) {
|
|
46
|
+
console.error('[ReverseSync] No OAuth token available for exchange');
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
console.error('[ReverseSync] Exchanging OAuth token for Supabase JWT...');
|
|
50
|
+
try {
|
|
51
|
+
const response = await fetch(`${this.config.supabaseUrl}/functions/v1/auth-token-exchange`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: {
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const errorText = await response.text();
|
|
60
|
+
console.error(`[ReverseSync] Token exchange failed (${response.status}):`, errorText);
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const data = await response.json();
|
|
64
|
+
if (!data.success || !data.supabaseToken) {
|
|
65
|
+
console.error('[ReverseSync] Token exchange response invalid:', data);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
// Store the token and expiry
|
|
69
|
+
this.supabaseToken = data.supabaseToken;
|
|
70
|
+
this.tokenExpiresAt = new Date(data.expiresAt);
|
|
71
|
+
// Set the JWT for Realtime authentication
|
|
72
|
+
// The anon key remains as the API key, JWT is used for auth via setAuth()
|
|
73
|
+
this.supabase.realtime.setAuth(this.supabaseToken);
|
|
74
|
+
console.error('[ReverseSync] ✅ Token exchanged successfully');
|
|
75
|
+
console.error(`[ReverseSync] User: ${data.userId}`);
|
|
76
|
+
console.error(`[ReverseSync] Org: ${data.organizationId || 'none'}`);
|
|
77
|
+
console.error(`[ReverseSync] Expires: ${data.expiresAt}`);
|
|
78
|
+
// Schedule token refresh before expiry (5 minutes before)
|
|
79
|
+
this.scheduleTokenRefresh();
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
console.error('[ReverseSync] Token exchange error:', error);
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Schedule automatic token refresh before expiry
|
|
89
|
+
*/
|
|
90
|
+
scheduleTokenRefresh() {
|
|
91
|
+
// Clear any existing refresh timer
|
|
92
|
+
if (this.tokenRefreshTimer) {
|
|
93
|
+
clearTimeout(this.tokenRefreshTimer);
|
|
94
|
+
this.tokenRefreshTimer = null;
|
|
95
|
+
}
|
|
96
|
+
if (!this.tokenExpiresAt) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Refresh 5 minutes before expiry
|
|
100
|
+
const refreshTime = this.tokenExpiresAt.getTime() - Date.now() - 5 * 60 * 1000;
|
|
101
|
+
if (refreshTime <= 0) {
|
|
102
|
+
// Token expires soon, refresh immediately
|
|
103
|
+
console.error('[ReverseSync] Token expiring soon, refreshing immediately...');
|
|
104
|
+
this.exchangeTokenForSupabaseJWT();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
console.error(`[ReverseSync] Token refresh scheduled in ${Math.round(refreshTime / 60000)} minutes`);
|
|
108
|
+
this.tokenRefreshTimer = setTimeout(async () => {
|
|
109
|
+
console.error('[ReverseSync] Refreshing Supabase token...');
|
|
110
|
+
const success = await this.exchangeTokenForSupabaseJWT();
|
|
111
|
+
if (success) {
|
|
112
|
+
// Re-authenticate the realtime channel
|
|
113
|
+
this.supabase.realtime.setAuth(this.supabaseToken);
|
|
114
|
+
}
|
|
115
|
+
}, refreshTime);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Start reverse sync - subscribe to task changes
|
|
119
|
+
*/
|
|
120
|
+
async start() {
|
|
121
|
+
if (this.running) {
|
|
122
|
+
throw new Error('ReverseSync already running');
|
|
123
|
+
}
|
|
124
|
+
console.error('[ReverseSync] Starting...');
|
|
125
|
+
// Exchange OAuth token for Supabase JWT (required for Realtime RLS)
|
|
126
|
+
if (this.config.apiKey?.startsWith('wm_at_')) {
|
|
127
|
+
const tokenOk = await this.exchangeTokenForSupabaseJWT();
|
|
128
|
+
if (!tokenOk) {
|
|
129
|
+
console.error('[ReverseSync] ⚠️ Token exchange failed - Realtime may be blocked by RLS');
|
|
130
|
+
console.error('[ReverseSync] Initial sync will still work, but live updates may not');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
console.error('[ReverseSync] ⚠️ No OAuth token - using anon key (RLS may block)');
|
|
135
|
+
}
|
|
136
|
+
// Load section mappings from database
|
|
137
|
+
await this.loadSectionMappings();
|
|
138
|
+
// Load existing task files to build file map
|
|
139
|
+
await this.loadExistingFiles();
|
|
140
|
+
// Perform initial sync of existing tasks
|
|
141
|
+
await this.performInitialSync();
|
|
142
|
+
// Subscribe to task changes
|
|
143
|
+
await this.subscribeToTaskChanges();
|
|
144
|
+
this.running = true;
|
|
145
|
+
console.error('[ReverseSync] ✅ Started');
|
|
146
|
+
this.emit('started');
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Stop reverse sync
|
|
150
|
+
*/
|
|
151
|
+
async stop() {
|
|
152
|
+
if (!this.running) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
console.error('[ReverseSync] Stopping...');
|
|
156
|
+
// Unsubscribe from realtime
|
|
157
|
+
if (this.channel) {
|
|
158
|
+
await this.channel.unsubscribe();
|
|
159
|
+
this.channel = null;
|
|
160
|
+
}
|
|
161
|
+
// Clear pending changes
|
|
162
|
+
if (this.syncTimer) {
|
|
163
|
+
clearTimeout(this.syncTimer);
|
|
164
|
+
this.syncTimer = null;
|
|
165
|
+
}
|
|
166
|
+
// Clear reconnection timer
|
|
167
|
+
if (this.reconnectTimer) {
|
|
168
|
+
clearTimeout(this.reconnectTimer);
|
|
169
|
+
this.reconnectTimer = null;
|
|
170
|
+
}
|
|
171
|
+
// Clear token refresh timer
|
|
172
|
+
if (this.tokenRefreshTimer) {
|
|
173
|
+
clearTimeout(this.tokenRefreshTimer);
|
|
174
|
+
this.tokenRefreshTimer = null;
|
|
175
|
+
}
|
|
176
|
+
this.running = false;
|
|
177
|
+
this.reconnectAttempts = 0;
|
|
178
|
+
this.supabaseToken = null;
|
|
179
|
+
this.tokenExpiresAt = null;
|
|
180
|
+
console.error('[ReverseSync] ✅ Stopped');
|
|
181
|
+
this.emit('stopped');
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Subscribe to Supabase realtime for task changes
|
|
185
|
+
*/
|
|
186
|
+
async subscribeToTaskChanges() {
|
|
187
|
+
console.error(`[ReverseSync] Subscribing to Realtime for board: ${this.config.taskboardId}`);
|
|
188
|
+
this.channel = this.supabase
|
|
189
|
+
.channel('task-changes')
|
|
190
|
+
.on('postgres_changes', {
|
|
191
|
+
event: '*', // Listen to INSERT, UPDATE, DELETE
|
|
192
|
+
schema: 'public',
|
|
193
|
+
table: 'tasks',
|
|
194
|
+
filter: `board_id=eq.${this.config.taskboardId}`,
|
|
195
|
+
}, (payload) => {
|
|
196
|
+
this.handleRealtimeEvent(payload);
|
|
197
|
+
})
|
|
198
|
+
.subscribe(async (status, err) => {
|
|
199
|
+
// Log ALL status changes for debugging
|
|
200
|
+
console.error(`[ReverseSync] Realtime status: ${status}${err ? ` (error: ${JSON.stringify(err)})` : ''}`);
|
|
201
|
+
if (status === 'SUBSCRIBED') {
|
|
202
|
+
console.error('[ReverseSync] ✅ Subscribed to task changes');
|
|
203
|
+
// Reset reconnect attempts on successful connection
|
|
204
|
+
this.reconnectAttempts = 0;
|
|
205
|
+
// Update board settings to reflect sync is active
|
|
206
|
+
await this.updateBoardSyncStatus(true);
|
|
207
|
+
}
|
|
208
|
+
else if (status === 'CLOSED') {
|
|
209
|
+
console.error('[ReverseSync] ⚠️ Subscription closed, reconnecting...');
|
|
210
|
+
this.handleReconnect();
|
|
211
|
+
}
|
|
212
|
+
else if (status === 'CHANNEL_ERROR') {
|
|
213
|
+
console.error('[ReverseSync] ❌ Subscription error, reconnecting...');
|
|
214
|
+
this.handleReconnect();
|
|
215
|
+
}
|
|
216
|
+
else if (status === 'TIMED_OUT') {
|
|
217
|
+
console.error('[ReverseSync] ⏰ Subscription timed out, reconnecting...');
|
|
218
|
+
this.handleReconnect();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
// Log channel state after setup
|
|
222
|
+
console.error(`[ReverseSync] Channel created, waiting for connection...`);
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Handle reconnection with exponential backoff
|
|
226
|
+
*/
|
|
227
|
+
async handleReconnect() {
|
|
228
|
+
// Don't reconnect if we're not running
|
|
229
|
+
if (!this.running) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// Check if we've exceeded max attempts
|
|
233
|
+
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
234
|
+
console.error(`[ReverseSync] ❌ Max reconnection attempts (${this.maxReconnectAttempts}) reached. Giving up.`);
|
|
235
|
+
console.error('[ReverseSync] Please restart the sync daemon manually.');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
this.reconnectAttempts++;
|
|
239
|
+
// Calculate backoff: 2^attempts seconds, capped at 60s
|
|
240
|
+
const backoffSeconds = Math.min(Math.pow(2, this.reconnectAttempts), 60);
|
|
241
|
+
console.error(`[ReverseSync] Reconnection attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts} in ${backoffSeconds}s...`);
|
|
242
|
+
// Clear existing channel
|
|
243
|
+
if (this.channel) {
|
|
244
|
+
try {
|
|
245
|
+
await this.channel.unsubscribe();
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
// Ignore unsubscribe errors
|
|
249
|
+
}
|
|
250
|
+
this.channel = null;
|
|
251
|
+
}
|
|
252
|
+
// Schedule reconnection
|
|
253
|
+
this.reconnectTimer = setTimeout(async () => {
|
|
254
|
+
try {
|
|
255
|
+
await this.subscribeToTaskChanges();
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
console.error('[ReverseSync] Reconnection failed:', error);
|
|
259
|
+
// Try again
|
|
260
|
+
this.handleReconnect();
|
|
261
|
+
}
|
|
262
|
+
}, backoffSeconds * 1000);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Update board sync status via edge function
|
|
266
|
+
*/
|
|
267
|
+
async updateBoardSyncStatus(enabled) {
|
|
268
|
+
if (!this.config.apiKey) {
|
|
269
|
+
console.error('[ReverseSync] No API key configured - skipping board sync status update');
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
const edgeFunctionUrl = `${this.config.supabaseUrl}/functions/v1/commander-task-board-operations`;
|
|
274
|
+
const response = await fetch(edgeFunctionUrl, {
|
|
275
|
+
method: 'POST',
|
|
276
|
+
headers: {
|
|
277
|
+
'Content-Type': 'application/json',
|
|
278
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
279
|
+
},
|
|
280
|
+
body: JSON.stringify({
|
|
281
|
+
action: 'update_board',
|
|
282
|
+
id: this.config.taskboardId,
|
|
283
|
+
settings: {
|
|
284
|
+
sync_enabled: enabled,
|
|
285
|
+
last_sync_at: new Date().toISOString(),
|
|
286
|
+
},
|
|
287
|
+
}),
|
|
288
|
+
});
|
|
289
|
+
if (!response.ok) {
|
|
290
|
+
const error = await response.text();
|
|
291
|
+
console.error('[ReverseSync] Failed to update board sync status:', error);
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
console.error(`[ReverseSync] ✅ Board sync status updated: ${enabled ? 'enabled' : 'disabled'}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
console.error('[ReverseSync] Error updating board sync status:', error);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Handle realtime event from Supabase
|
|
303
|
+
*/
|
|
304
|
+
handleRealtimeEvent(payload) {
|
|
305
|
+
const { eventType, new: newRecord, old: oldRecord } = payload;
|
|
306
|
+
// Use newRecord if it has an id, otherwise use oldRecord (for DELETE events)
|
|
307
|
+
const taskData = (newRecord?.id) ? newRecord : oldRecord;
|
|
308
|
+
if (!taskData || !taskData.id) {
|
|
309
|
+
console.error(`[ReverseSync] Error: Missing task data in ${eventType} event`, { newRecord, oldRecord });
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
// Deduplicate: Supabase Realtime can fire 2 UPDATE events per change
|
|
313
|
+
const dedupeKey = `${taskData.id}:${eventType}`;
|
|
314
|
+
const now = Date.now();
|
|
315
|
+
const lastSeen = this.recentEvents.get(dedupeKey);
|
|
316
|
+
if (lastSeen && (now - lastSeen) < this.dedupeWindowMs) {
|
|
317
|
+
console.error(`[ReverseSync] Skipping duplicate ${eventType} for task ${taskData.id}`);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
this.recentEvents.set(dedupeKey, now);
|
|
321
|
+
// Clean up old entries periodically
|
|
322
|
+
if (this.recentEvents.size > 100) {
|
|
323
|
+
for (const [key, ts] of this.recentEvents) {
|
|
324
|
+
if (now - ts > this.dedupeWindowMs)
|
|
325
|
+
this.recentEvents.delete(key);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
console.error(`[ReverseSync] Received ${eventType} event for task:`, taskData.id);
|
|
329
|
+
const change = {
|
|
330
|
+
type: eventType,
|
|
331
|
+
task: taskData,
|
|
332
|
+
old: oldRecord,
|
|
333
|
+
};
|
|
334
|
+
// Queue change for processing (debounce rapid changes)
|
|
335
|
+
this.handleTaskChange(change);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Load section mappings from database for folder organization
|
|
339
|
+
*/
|
|
340
|
+
async loadSectionMappings() {
|
|
341
|
+
console.error(`[ReverseSync] Loading section mappings for board: ${this.config.taskboardId}`);
|
|
342
|
+
try {
|
|
343
|
+
const { data: sections, error } = await this.supabase
|
|
344
|
+
.from('task_status_sections')
|
|
345
|
+
.select('id, name, board_id, position')
|
|
346
|
+
.eq('board_id', this.config.taskboardId)
|
|
347
|
+
.order('position', { ascending: true });
|
|
348
|
+
if (error) {
|
|
349
|
+
console.error('[ReverseSync] Error loading sections:', error);
|
|
350
|
+
throw error;
|
|
351
|
+
}
|
|
352
|
+
if (!sections || sections.length === 0) {
|
|
353
|
+
console.error('[ReverseSync] WARNING: No sections found for board');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
// Build section_id → folder_name mapping
|
|
357
|
+
for (const section of sections) {
|
|
358
|
+
const folderName = this.slugifyName(section.name);
|
|
359
|
+
this.sectionMap.set(section.id, folderName);
|
|
360
|
+
console.error(`[ReverseSync] Section: ${section.name} → ${folderName}/`);
|
|
361
|
+
}
|
|
362
|
+
console.error(`[ReverseSync] ✅ Loaded ${this.sectionMap.size} section mappings`);
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
console.error('[ReverseSync] Error loading section mappings:', error);
|
|
366
|
+
// Non-fatal, continue without folder organization
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Load existing task markdown files to build task_id → file_path map
|
|
371
|
+
* Scans tasks folder and all subdirectories (section folders)
|
|
372
|
+
*/
|
|
373
|
+
async loadExistingFiles() {
|
|
374
|
+
const tasksDir = path.join(this.config.projectPath, this.config.tasksFolder);
|
|
375
|
+
try {
|
|
376
|
+
// Ensure directory exists
|
|
377
|
+
await fs.mkdir(tasksDir, { recursive: true });
|
|
378
|
+
// Read all items (files + directories)
|
|
379
|
+
const items = await fs.readdir(tasksDir, { withFileTypes: true });
|
|
380
|
+
// Process root-level .md files
|
|
381
|
+
const rootMdFiles = items.filter(item => item.isFile() && item.name.endsWith('.md'));
|
|
382
|
+
console.error(`[ReverseSync] Loading existing task files...`);
|
|
383
|
+
console.error(`[ReverseSync] Found ${rootMdFiles.length} files in root`);
|
|
384
|
+
for (const file of rootMdFiles) {
|
|
385
|
+
await this.loadTaskFile(file.name, tasksDir);
|
|
386
|
+
}
|
|
387
|
+
// Process subdirectories (section folders)
|
|
388
|
+
const subdirs = items.filter(item => item.isDirectory());
|
|
389
|
+
console.error(`[ReverseSync] Found ${subdirs.length} section folders`);
|
|
390
|
+
for (const dir of subdirs) {
|
|
391
|
+
const sectionPath = path.join(tasksDir, dir.name);
|
|
392
|
+
const sectionFiles = await fs.readdir(sectionPath);
|
|
393
|
+
const mdFiles = sectionFiles.filter(f => f.endsWith('.md'));
|
|
394
|
+
console.error(`[ReverseSync] Scanning ${dir.name}/ (${mdFiles.length} files)`);
|
|
395
|
+
for (const filename of mdFiles) {
|
|
396
|
+
// Store relative path with section folder
|
|
397
|
+
const relativePath = `${dir.name}/${filename}`;
|
|
398
|
+
await this.loadTaskFile(relativePath, tasksDir);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
console.error(`[ReverseSync] ✅ Loaded ${this.fileMap.size} task files`);
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
console.error(`[ReverseSync] Error loading existing files:`, error);
|
|
405
|
+
// Non-fatal, continue with empty map
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Load a single task file and add to file map
|
|
410
|
+
*/
|
|
411
|
+
async loadTaskFile(relativePath, tasksDir) {
|
|
412
|
+
const fullPath = path.join(tasksDir, relativePath);
|
|
413
|
+
try {
|
|
414
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
415
|
+
// Parse frontmatter to get task_id
|
|
416
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
417
|
+
if (!frontmatterMatch) {
|
|
418
|
+
console.error(`[ReverseSync] No frontmatter in ${relativePath}, skipping`);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const frontmatter = frontmatterMatch[1];
|
|
422
|
+
// Match both formats:
|
|
423
|
+
// Old: task_id: "uuid" or task_id: 'uuid'
|
|
424
|
+
// New: task_id: uuid (under sync block, no quotes)
|
|
425
|
+
const taskIdMatch = frontmatter.match(/task_id: ["']?([a-f0-9-]+)["']?/);
|
|
426
|
+
if (taskIdMatch) {
|
|
427
|
+
const taskId = taskIdMatch[1];
|
|
428
|
+
this.fileMap.set(taskId, relativePath);
|
|
429
|
+
console.error(`[ReverseSync] Loaded: ${relativePath} → ${taskId}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
catch (error) {
|
|
433
|
+
console.error(`[ReverseSync] Error reading ${relativePath}:`, error);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Perform initial sync of all existing tasks in the taskboard
|
|
438
|
+
*
|
|
439
|
+
* IMPORTANT: This does NOT create conflict files. On startup we simply:
|
|
440
|
+
* - Create files for tasks that exist in Commander but not locally
|
|
441
|
+
* - Skip files that already exist locally (trust local state)
|
|
442
|
+
*
|
|
443
|
+
* Conflict detection only happens on LIVE Realtime events (changes while daemon runs).
|
|
444
|
+
*/
|
|
445
|
+
async performInitialSync() {
|
|
446
|
+
console.error('[ReverseSync] Performing initial sync (skip-if-exists mode)...');
|
|
447
|
+
try {
|
|
448
|
+
const { data: tasks, error } = await this.supabase
|
|
449
|
+
.from('tasks')
|
|
450
|
+
.select('id, title, description, status, status_section_id, priority, board_id, updated_at, created_at, metadata')
|
|
451
|
+
.eq('board_id', this.config.taskboardId)
|
|
452
|
+
.order('created_at', { ascending: false });
|
|
453
|
+
if (error) {
|
|
454
|
+
console.error('[ReverseSync] Error querying tasks:', error);
|
|
455
|
+
throw error;
|
|
456
|
+
}
|
|
457
|
+
if (!tasks || tasks.length === 0) {
|
|
458
|
+
console.error('[ReverseSync] No existing tasks found in taskboard');
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
console.error(`[ReverseSync] Found ${tasks.length} tasks in Commander`);
|
|
462
|
+
// Create all section folders first
|
|
463
|
+
const tasksDir = path.join(this.config.projectPath, this.config.tasksFolder);
|
|
464
|
+
for (const [_sectionId, folderName] of this.sectionMap.entries()) {
|
|
465
|
+
const sectionPath = path.join(tasksDir, folderName);
|
|
466
|
+
try {
|
|
467
|
+
await fs.mkdir(sectionPath, { recursive: true });
|
|
468
|
+
}
|
|
469
|
+
catch {
|
|
470
|
+
// Folder may already exist
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
// Track statistics
|
|
474
|
+
let created = 0;
|
|
475
|
+
let skipped = 0;
|
|
476
|
+
// Process each task - simple logic: create if missing, skip if exists
|
|
477
|
+
for (const task of tasks) {
|
|
478
|
+
const existingFilePath = this.fileMap.get(task.id);
|
|
479
|
+
if (!existingFilePath) {
|
|
480
|
+
// Task exists in Commander but no file in IDE → Create file
|
|
481
|
+
await this.syncTaskToFile(task);
|
|
482
|
+
created++;
|
|
483
|
+
}
|
|
484
|
+
else {
|
|
485
|
+
// File already exists locally → Trust it, skip
|
|
486
|
+
skipped++;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
console.error('[ReverseSync] ✅ Initial sync complete:');
|
|
490
|
+
console.error(`[ReverseSync] Created: ${created} files (new from Commander)`);
|
|
491
|
+
console.error(`[ReverseSync] Skipped: ${skipped} files (already exist locally)`);
|
|
492
|
+
console.error(`[ReverseSync] Watching for live changes...`);
|
|
493
|
+
}
|
|
494
|
+
catch (error) {
|
|
495
|
+
console.error('[ReverseSync] Error during initial sync:', error);
|
|
496
|
+
// Non-fatal, continue with realtime sync
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Handle task change from Commander (debounced)
|
|
501
|
+
*/
|
|
502
|
+
async handleTaskChange(change) {
|
|
503
|
+
const taskId = change.task.id;
|
|
504
|
+
// Add to pending changes (newer changes override older ones)
|
|
505
|
+
this.pendingChanges.set(taskId, change);
|
|
506
|
+
// Debounce: wait for sync interval before processing
|
|
507
|
+
if (this.syncTimer) {
|
|
508
|
+
clearTimeout(this.syncTimer);
|
|
509
|
+
}
|
|
510
|
+
this.syncTimer = setTimeout(() => {
|
|
511
|
+
this.processQueuedChanges();
|
|
512
|
+
}, this.config.syncIntervalMs);
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Process all queued task changes
|
|
516
|
+
*/
|
|
517
|
+
async processQueuedChanges() {
|
|
518
|
+
if (this.pendingChanges.size === 0) {
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
console.error(`[ReverseSync] Processing ${this.pendingChanges.size} queued changes...`);
|
|
522
|
+
for (const [taskId, change] of this.pendingChanges) {
|
|
523
|
+
try {
|
|
524
|
+
switch (change.type) {
|
|
525
|
+
case 'INSERT':
|
|
526
|
+
case 'UPDATE':
|
|
527
|
+
await this.syncTaskToFile(change.task);
|
|
528
|
+
break;
|
|
529
|
+
case 'DELETE':
|
|
530
|
+
await this.deleteTaskFile(taskId);
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
console.error(`[ReverseSync] Error processing ${change.type} for ${taskId}:`, error);
|
|
536
|
+
this.emit('error', error);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
// Clear queue
|
|
540
|
+
this.pendingChanges.clear();
|
|
541
|
+
this.syncTimer = null;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Sync task to file (create or update)
|
|
545
|
+
*/
|
|
546
|
+
async syncTaskToFile(task) {
|
|
547
|
+
// Get section folder (e.g., "backlog", "to-do", "in-progress")
|
|
548
|
+
const sectionFolder = task.status_section_id
|
|
549
|
+
? this.sectionMap.get(task.status_section_id)
|
|
550
|
+
: null;
|
|
551
|
+
// Generate filename
|
|
552
|
+
const filename = this.generateFilename(task.title, task.id);
|
|
553
|
+
// Build new file path with section folder
|
|
554
|
+
const newFilePath = sectionFolder
|
|
555
|
+
? `${sectionFolder}/${filename}`
|
|
556
|
+
: filename;
|
|
557
|
+
// Check if file exists
|
|
558
|
+
const existingFilePath = this.fileMap.get(task.id);
|
|
559
|
+
if (existingFilePath) {
|
|
560
|
+
// UPDATE: Check for conflicts
|
|
561
|
+
const hasConflict = await this.checkConflict(task.id, task);
|
|
562
|
+
if (hasConflict) {
|
|
563
|
+
console.error(`[ReverseSync] ⚠️ Conflict detected for task ${task.id}, applying latest timestamp wins`);
|
|
564
|
+
// Will overwrite - conflict resolution logs to sync_transactions
|
|
565
|
+
}
|
|
566
|
+
// Check if section changed (file needs to move)
|
|
567
|
+
if (existingFilePath !== newFilePath) {
|
|
568
|
+
console.error(`[ReverseSync] Task moved sections, relocating file: ${existingFilePath} → ${newFilePath}`);
|
|
569
|
+
// Delete old file
|
|
570
|
+
await this.deleteTaskFile(task.id);
|
|
571
|
+
// Write to new location
|
|
572
|
+
await this.writeTaskFile(task, newFilePath);
|
|
573
|
+
}
|
|
574
|
+
else {
|
|
575
|
+
// Update existing file (same location)
|
|
576
|
+
await this.writeTaskFile(task, existingFilePath);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
// INSERT: Create new file
|
|
581
|
+
await this.writeTaskFile(task, newFilePath);
|
|
582
|
+
}
|
|
583
|
+
this.emit('task-synced', {
|
|
584
|
+
taskId: task.id,
|
|
585
|
+
title: task.title,
|
|
586
|
+
operation: existingFilePath ? 'update' : 'create',
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Convert task to markdown format
|
|
591
|
+
*/
|
|
592
|
+
taskToMarkdown(task, existingDocumentId) {
|
|
593
|
+
const { id, title, description, status, priority, updated_at } = task;
|
|
594
|
+
// Convert status to checkbox
|
|
595
|
+
const checkbox = status === 'done' ? '[x]' : '[ ]';
|
|
596
|
+
// Map task status to sync status
|
|
597
|
+
const syncStatus = status === 'done' ? 'completed' : 'active';
|
|
598
|
+
// Build frontmatter with sync block for SyncEngine compatibility
|
|
599
|
+
// NOTE: Do NOT set ide_modified_at here - that field tracks actual IDE edits
|
|
600
|
+
// ReverseSync only sets commander_modified_at to track Commander's version
|
|
601
|
+
const frontmatter = [
|
|
602
|
+
'---',
|
|
603
|
+
'sync:',
|
|
604
|
+
' type: task',
|
|
605
|
+
` status: ${syncStatus}`,
|
|
606
|
+
` task_id: ${id}`,
|
|
607
|
+
existingDocumentId ? ` document_id: ${existingDocumentId}` : null,
|
|
608
|
+
` last_synced: '${new Date().toISOString()}'`,
|
|
609
|
+
`title: "${title.replace(/"/g, '\\"')}"`,
|
|
610
|
+
`status: "${status}"`,
|
|
611
|
+
priority ? `priority: "${priority}"` : null,
|
|
612
|
+
`commander_modified_at: "${updated_at}"`,
|
|
613
|
+
'---',
|
|
614
|
+
].filter(Boolean).join('\n');
|
|
615
|
+
// Clean description - remove any existing ## Task, ## Notes, # Title sections
|
|
616
|
+
// to prevent accumulation from sync loop
|
|
617
|
+
let cleanDescription = description || '';
|
|
618
|
+
// Remove repeated title headers
|
|
619
|
+
cleanDescription = cleanDescription.replace(new RegExp(`^# ${title.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\s*`, 'gm'), '');
|
|
620
|
+
// Remove ## Task sections and everything after (our template sections)
|
|
621
|
+
cleanDescription = cleanDescription.replace(/## Task[\s\S]*$/m, '').trim();
|
|
622
|
+
// Build content
|
|
623
|
+
const content = [
|
|
624
|
+
`# ${title}`,
|
|
625
|
+
'',
|
|
626
|
+
cleanDescription,
|
|
627
|
+
'',
|
|
628
|
+
'## Task',
|
|
629
|
+
'',
|
|
630
|
+
`- ${checkbox} ${title}`,
|
|
631
|
+
'',
|
|
632
|
+
'## Notes',
|
|
633
|
+
'',
|
|
634
|
+
'_Add your notes here_',
|
|
635
|
+
].join('\n');
|
|
636
|
+
return `${frontmatter}\n\n${content}\n`;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Slugify a name for use in folder/file names
|
|
640
|
+
*/
|
|
641
|
+
slugifyName(name) {
|
|
642
|
+
return name
|
|
643
|
+
.toLowerCase()
|
|
644
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
645
|
+
.replace(/^-+|-+$/g, '');
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Generate filename from task title (slugified)
|
|
649
|
+
*/
|
|
650
|
+
generateFilename(title, taskId) {
|
|
651
|
+
// Slugify: lowercase, replace spaces/special chars with hyphens
|
|
652
|
+
const slug = this.slugifyName(title).substring(0, 50); // Limit length
|
|
653
|
+
// Ensure uniqueness with short UUID suffix if needed
|
|
654
|
+
const filename = `${slug}.md`;
|
|
655
|
+
// Check if file exists with different task_id
|
|
656
|
+
const existingTaskId = Array.from(this.fileMap.entries()).find(([id, file]) => file === filename && id !== taskId)?.[0];
|
|
657
|
+
if (existingTaskId) {
|
|
658
|
+
// Add short UUID to make unique
|
|
659
|
+
const shortId = taskId.substring(0, 8);
|
|
660
|
+
return `${slug}-${shortId}.md`;
|
|
661
|
+
}
|
|
662
|
+
return filename;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Write task to markdown file
|
|
666
|
+
*/
|
|
667
|
+
async writeTaskFile(task, filePath) {
|
|
668
|
+
const fullPath = path.join(this.config.projectPath, this.config.tasksFolder, filePath);
|
|
669
|
+
// Try to read existing file to preserve document_id
|
|
670
|
+
let existingDocumentId;
|
|
671
|
+
try {
|
|
672
|
+
const existingContent = await fs.readFile(fullPath, 'utf-8');
|
|
673
|
+
const frontmatterMatch = existingContent.match(/^---\n([\s\S]*?)\n---/);
|
|
674
|
+
if (frontmatterMatch) {
|
|
675
|
+
const docIdMatch = frontmatterMatch[1].match(/document_id: ["']?([a-f0-9-]+)["']?/);
|
|
676
|
+
if (docIdMatch) {
|
|
677
|
+
existingDocumentId = docIdMatch[1];
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
catch {
|
|
682
|
+
// File doesn't exist yet, that's fine
|
|
683
|
+
}
|
|
684
|
+
const markdown = this.taskToMarkdown(task, existingDocumentId);
|
|
685
|
+
// Ensure directory exists
|
|
686
|
+
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
687
|
+
// Write file
|
|
688
|
+
await fs.writeFile(fullPath, markdown, 'utf-8');
|
|
689
|
+
// Update file map
|
|
690
|
+
this.fileMap.set(task.id, filePath);
|
|
691
|
+
console.error(`[ReverseSync] ✅ Created/Updated: ${filePath}`);
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Delete task markdown file
|
|
695
|
+
*/
|
|
696
|
+
async deleteTaskFile(taskId) {
|
|
697
|
+
const filePath = this.fileMap.get(taskId);
|
|
698
|
+
if (!filePath) {
|
|
699
|
+
console.error(`[ReverseSync] No file found for task: ${taskId}`);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const fullPath = path.join(this.config.projectPath, this.config.tasksFolder, filePath);
|
|
703
|
+
try {
|
|
704
|
+
await fs.unlink(fullPath);
|
|
705
|
+
this.fileMap.delete(taskId);
|
|
706
|
+
console.error(`[ReverseSync] ✅ Deleted: ${filePath}`);
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
console.error(`[ReverseSync] Failed to delete ${filePath}:`, error);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Check for conflicts between IDE file and Commander task
|
|
714
|
+
* Returns true if IDE file was modified after Commander task
|
|
715
|
+
*/
|
|
716
|
+
async checkConflict(taskId, _task) {
|
|
717
|
+
const filePath = this.fileMap.get(taskId);
|
|
718
|
+
if (!filePath) {
|
|
719
|
+
return false; // No existing file, no conflict
|
|
720
|
+
}
|
|
721
|
+
const fullPath = path.join(this.config.projectPath, this.config.tasksFolder, filePath);
|
|
722
|
+
try {
|
|
723
|
+
// Read existing file
|
|
724
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
725
|
+
// Parse frontmatter to get ide_modified_at
|
|
726
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
727
|
+
if (!frontmatterMatch) {
|
|
728
|
+
return false; // No frontmatter, no conflict detection possible
|
|
729
|
+
}
|
|
730
|
+
const frontmatter = frontmatterMatch[1];
|
|
731
|
+
const ideModifiedMatch = frontmatter.match(/ide_modified_at: ["']([^"']+)["']/);
|
|
732
|
+
const commanderModifiedMatch = frontmatter.match(/commander_modified_at: ["']([^"']+)["']/);
|
|
733
|
+
// If no ide_modified_at, file was created by ReverseSync - no IDE edits, no conflict
|
|
734
|
+
if (!ideModifiedMatch) {
|
|
735
|
+
return false;
|
|
736
|
+
}
|
|
737
|
+
if (!commanderModifiedMatch) {
|
|
738
|
+
return false; // Missing commander timestamp, can't compare
|
|
739
|
+
}
|
|
740
|
+
const ideModifiedAt = new Date(ideModifiedMatch[1]);
|
|
741
|
+
const lastKnownCommander = new Date(commanderModifiedMatch[1]);
|
|
742
|
+
// Conflict if IDE was modified after the last known Commander timestamp we stored
|
|
743
|
+
const hasConflict = ideModifiedAt > lastKnownCommander;
|
|
744
|
+
if (hasConflict) {
|
|
745
|
+
console.error(`[ReverseSync] Conflict: IDE modified at ${ideModifiedAt.toISOString()}, last synced Commander at ${lastKnownCommander.toISOString()}`);
|
|
746
|
+
// Log conflict (latest wins, but we record it happened)
|
|
747
|
+
this.emit('conflict', {
|
|
748
|
+
taskId,
|
|
749
|
+
ideModifiedAt: ideModifiedAt.toISOString(),
|
|
750
|
+
commanderModifiedAt: lastKnownCommander.toISOString(),
|
|
751
|
+
resolution: 'commander-wins', // We're overwriting with Commander version
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
return hasConflict;
|
|
755
|
+
}
|
|
756
|
+
catch (error) {
|
|
757
|
+
console.error(`[ReverseSync] Error checking conflict for ${taskId}:`, error);
|
|
758
|
+
return false;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
/**
|
|
762
|
+
* Get current status
|
|
763
|
+
*/
|
|
764
|
+
getStatus() {
|
|
765
|
+
return {
|
|
766
|
+
running: this.running,
|
|
767
|
+
subscribedTo: this.config.taskboardId,
|
|
768
|
+
filesTracked: this.fileMap.size,
|
|
769
|
+
pendingChanges: this.pendingChanges.size,
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
//# sourceMappingURL=reverse-sync.js.map
|