automagik-forge 0.1.13 → 0.1.14
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 +143 -447
- package/dist/linux-x64/automagik-forge-mcp.zip +0 -0
- package/{npx-cli/automagik-forge-0.0.55.tgz → dist/linux-x64/automagik-forge.zip} +0 -0
- package/package.json +13 -23
- package/.cargo/config.toml +0 -13
- package/.claude/commands/commit.md +0 -376
- package/.claude/commands/prompt.md +0 -871
- package/.env.example +0 -20
- package/.github/actions/setup-node/action.yml +0 -29
- package/.github/images/automagik-logo.png +0 -0
- package/.github/workflows/pre-release.yml +0 -470
- package/.github/workflows/publish.yml +0 -145
- package/.github/workflows/test.yml +0 -63
- package/.mcp.json +0 -57
- package/AGENT.md +0 -40
- package/CLAUDE.md +0 -40
- package/CODE-OF-CONDUCT.md +0 -89
- package/Cargo.toml +0 -19
- package/Dockerfile +0 -43
- package/LICENSE +0 -201
- package/Makefile +0 -97
- package/backend/.sqlx/query-01b7e2bac1261d8be3d03c03df3e5220590da6c31c77f161074fc62752d63881.json +0 -12
- package/backend/.sqlx/query-03f2b02ba6dc5ea2b3cf6b1004caea0ad6bcc10ebd63f441d321a389f026e263.json +0 -12
- package/backend/.sqlx/query-0923b77d137a29fc54d399a873ff15fc4af894490bc65a4d344a7575cb0d8643.json +0 -12
- package/backend/.sqlx/query-0f808bcdb63c5f180836e448dd64c435c51758b2fc54a52ce9e67495b1ab200e.json +0 -68
- package/backend/.sqlx/query-1268afe9ca849daa6722e3df7ca8e9e61f0d37052e782bb5452ab8e1018d9b63.json +0 -12
- package/backend/.sqlx/query-1b082630a9622f8667ee7a9aba2c2d3176019a68c6bb83d33008594821415a57.json +0 -12
- package/backend/.sqlx/query-1c7b06ba1e112abf6b945a2ff08a0b40ec23f3738c2e7399f067b558cf8d490e.json +0 -12
- package/backend/.sqlx/query-1f619f01f46859a64ded531dd0ef61abacfe62e758abe7030a6aa745140b95ca.json +0 -104
- package/backend/.sqlx/query-1fca1ce14b4b20205364cd1f1f45ebe1d2e30cd745e59e189d56487b5639dfbb.json +0 -12
- package/backend/.sqlx/query-212828320e8d871ab9d83705a040b23bcf0393dc7252177fc539a74657f578ef.json +0 -32
- package/backend/.sqlx/query-290ce5c152be8d36e58ff42570f9157beb07ab9e77a03ec6fc30b4f56f9b8f6b.json +0 -56
- package/backend/.sqlx/query-2b471d2c2e8ffbe0cd42d2a91b814c0d79f9d09200f147e3cea33ba4ce673c8a.json +0 -68
- package/backend/.sqlx/query-354a48c705bb9bb2048c1b7f10fcb714e23f9db82b7a4ea6932486197b2ede6a.json +0 -92
- package/backend/.sqlx/query-36c9e3dd10648e94b949db5c91a774ecb1e10a899ef95da74066eccedca4d8b2.json +0 -12
- package/backend/.sqlx/query-36e4ba7bbd81b402d5a20b6005755eafbb174c8dda442081823406ac32809a94.json +0 -56
- package/backend/.sqlx/query-3a5b3c98a55ca183ab20c74708e3d7e579dda37972c059e7515c4ceee4bd8dd3.json +0 -62
- package/backend/.sqlx/query-3d0a1cabf2a52e9d90cdfd29c509ca89aeb448d0c1d2446c65cd43db40735e86.json +0 -62
- package/backend/.sqlx/query-3d6bd16fbce59efe30b7f67ea342e0e4ea6d1432389c02468ad79f1f742d4031.json +0 -56
- package/backend/.sqlx/query-4049ca413b285a05aca6b25385e9c8185575f01e9069e4e8581aa45d713f612f.json +0 -32
- package/backend/.sqlx/query-412bacd3477d86369082e90f52240407abce436cb81292d42b2dbe1e5c18eea1.json +0 -104
- package/backend/.sqlx/query-417a8b1ff4e51de82aea0159a3b97932224dc325b23476cb84153d690227fd8b.json +0 -62
- package/backend/.sqlx/query-461cc1b0bb6fd909afc9dd2246e8526b3771cfbb0b22ae4b5d17b51af587b9e2.json +0 -56
- package/backend/.sqlx/query-58408c7a8cdeeda0bef359f1f9bd91299a339dc2b191462fc58c9736a56d5227.json +0 -92
- package/backend/.sqlx/query-5a886026d75d515c01f347cc203c8d99dd04c61dc468e2e4c5aa548436d13834.json +0 -62
- package/backend/.sqlx/query-5b902137b11022d2e1a5c4f6a9c83fec1a856c6a710aff831abd2382ede76b43.json +0 -12
- package/backend/.sqlx/query-5ed1238e52e59bb5f76c0f153fd99a14093f7ce2585bf9843585608f17ec575b.json +0 -104
- package/backend/.sqlx/query-6e8b860b14decfc2227dc57213f38442943d3fbef5c8418fd6b634c6e0f5e2ea.json +0 -104
- package/backend/.sqlx/query-6ec414276994c4ccb2433eaa5b1b342168557d17ddf5a52dac84cb1b59b9de8f.json +0 -68
- package/backend/.sqlx/query-6ecfa16d0cf825aacf233544b5baf151e9adfdca26c226ad71020d291fd802d5.json +0 -62
- package/backend/.sqlx/query-72509d252c39fce77520aa816cb2acbc1fb35dc2605e7be893610599b2427f2e.json +0 -62
- package/backend/.sqlx/query-75239b2da188f749707d77f3c1544332ca70db3d6d6743b2601dc0d167536437.json +0 -62
- package/backend/.sqlx/query-83d10e29f8478aff33434f9ac67068e013b888b953a2657e2bb72a6f619d04f2.json +0 -50
- package/backend/.sqlx/query-8610803360ea18b9b9d078a6981ea56abfbfe84e6354fc1d5ae4c622e01410ed.json +0 -68
- package/backend/.sqlx/query-86d03eb70eef39c59296416867f2ee66c9f7cd8b7f961fbda2f89fc0a1c442c2.json +0 -12
- package/backend/.sqlx/query-87d0feb5a6b442bad9c60068ea7569599cc6fc91a0e2692ecb42e93b03201b9d.json +0 -68
- package/backend/.sqlx/query-8a67b3b3337248f06a57bdf8a908f7ef23177431eaed82dc08c94c3e5944340e.json +0 -12
- package/backend/.sqlx/query-8f01ebd64bdcde6a090479f14810d73ba23020e76fd70854ac57f2da251702c3.json +0 -12
- package/backend/.sqlx/query-90fd607fcb2dca72239ff25e618e21e174b195991eaa33722cbf5f76da84cfab.json +0 -62
- package/backend/.sqlx/query-92e8bdbcd80c5ff3db7a35cf79492048803ef305cbdef0d0a1fe5dc881ca8c71.json +0 -104
- package/backend/.sqlx/query-93a1605f90e9672dad29b472b6ad85fa9a55ea3ffa5abcb8724b09d61be254ca.json +0 -20
- package/backend/.sqlx/query-9472c8fb477958167f5fae40b85ac44252468c5226b2cdd7770f027332eed6d7.json +0 -104
- package/backend/.sqlx/query-96036c4f9e0f48bdc5a4a4588f0c5f288ac7aaa5425cac40fc33f337e1a351f2.json +0 -56
- package/backend/.sqlx/query-9edb2c01e91fd0f0fe7b56e988c7ae0393150f50be3f419a981e035c0121dfc7.json +0 -104
- package/backend/.sqlx/query-a157cf00616f703bfba21927f1eb1c9eec2a81c02da15f66efdba0b6c375de1b.json +0 -26
- package/backend/.sqlx/query-a31fff84f3b8e532fd1160447d89d700f06ae08821fee00c9a5b60492b05259c.json +0 -62
- package/backend/.sqlx/query-a5ba908419fb3e456bdd2daca41ba06cc3212ffffb8520fc7dbbcc8b60ada314.json +0 -12
- package/backend/.sqlx/query-a6d2961718dbc3b1a925e549f49a159c561bef58c105529275f274b27e2eba5b.json +0 -104
- package/backend/.sqlx/query-a9e93d5b09b29faf66e387e4d7596a792d81e75c4d3726e83c2963e8d7c9b56f.json +0 -104
- package/backend/.sqlx/query-ac5247c8d7fb86e4650c4b0eb9420031614c831b7b085083bac20c1af314c538.json +0 -12
- package/backend/.sqlx/query-afef9467be74c411c4cb119a8b2b1aea53049877dfc30cc60b486134ba4b4c9f.json +0 -68
- package/backend/.sqlx/query-b2b2c6b4d0b1a347b5c4cb63c3a46a265d4db53be9554989a814b069d0af82f2.json +0 -62
- package/backend/.sqlx/query-c50d2ff0b12e5bcc81e371089ee2d007e233e7db93aefba4fef08e7aa68f5ab7.json +0 -20
- package/backend/.sqlx/query-c614e6056b244ca07f1b9d44e7edc9d5819225c6f8d9e077070c6e518a17f50b.json +0 -12
- package/backend/.sqlx/query-c67259be8bf4ee0cfd32167b2aa3b7fe9192809181a8171bf1c2d6df731967ae.json +0 -12
- package/backend/.sqlx/query-d2d0a1b985ebbca6a2b3e882a221a219f3199890fa640afc946ef1a792d6d8de.json +0 -12
- package/backend/.sqlx/query-d30aa5786757f32bf2b9c5fe51a45e506c71c28c5994e430d9b0546adb15ffa2.json +0 -20
- package/backend/.sqlx/query-d3b9ea1de1576af71b312924ce7f4ea8ae5dbe2ac138ea3b4470f2d5cd734846.json +0 -12
- package/backend/.sqlx/query-ed8456646fa69ddd412441955f06ff22bfb790f29466450735e0b8bb1bc4ec94.json +0 -12
- package/backend/Cargo.toml +0 -71
- package/backend/build.rs +0 -32
- package/backend/migrations/20250617183714_init.sql +0 -44
- package/backend/migrations/20250620212427_execution_processes.sql +0 -25
- package/backend/migrations/20250620214100_remove_stdout_stderr_from_task_attempts.sql +0 -28
- package/backend/migrations/20250621120000_relate_activities_to_execution_processes.sql +0 -23
- package/backend/migrations/20250623120000_executor_sessions.sql +0 -17
- package/backend/migrations/20250623130000_add_executor_type_to_execution_processes.sql +0 -4
- package/backend/migrations/20250625000000_add_dev_script_to_projects.sql +0 -4
- package/backend/migrations/20250701000000_add_branch_to_task_attempts.sql +0 -2
- package/backend/migrations/20250701000001_add_pr_tracking_to_task_attempts.sql +0 -5
- package/backend/migrations/20250701120000_add_assistant_message_to_executor_sessions.sql +0 -2
- package/backend/migrations/20250708000000_add_base_branch_to_task_attempts.sql +0 -2
- package/backend/migrations/20250709000000_add_worktree_deleted_flag.sql +0 -2
- package/backend/migrations/20250710000000_add_setup_completion.sql +0 -3
- package/backend/migrations/20250715154859_add_task_templates.sql +0 -25
- package/backend/migrations/20250716143725_add_default_templates.sql +0 -174
- package/backend/migrations/20250716161432_update_executor_names_to_kebab_case.sql +0 -20
- package/backend/migrations/20250716170000_add_parent_task_to_tasks.sql +0 -7
- package/backend/migrations/20250717000000_drop_task_attempt_activities.sql +0 -9
- package/backend/migrations/20250719000000_add_cleanup_script_to_projects.sql +0 -2
- package/backend/migrations/20250720000000_add_cleanupscript_to_process_type_constraint.sql +0 -25
- package/backend/migrations/20250723000000_add_wish_to_tasks.sql +0 -7
- package/backend/migrations/20250724000000_remove_unique_wish_constraint.sql +0 -5
- package/backend/scripts/toast-notification.ps1 +0 -23
- package/backend/sounds/abstract-sound1.wav +0 -0
- package/backend/sounds/abstract-sound2.wav +0 -0
- package/backend/sounds/abstract-sound3.wav +0 -0
- package/backend/sounds/abstract-sound4.wav +0 -0
- package/backend/sounds/cow-mooing.wav +0 -0
- package/backend/sounds/phone-vibration.wav +0 -0
- package/backend/sounds/rooster.wav +0 -0
- package/backend/src/app_state.rs +0 -218
- package/backend/src/bin/generate_types.rs +0 -189
- package/backend/src/bin/mcp_task_server.rs +0 -191
- package/backend/src/execution_monitor.rs +0 -1193
- package/backend/src/executor.rs +0 -1053
- package/backend/src/executors/amp.rs +0 -697
- package/backend/src/executors/ccr.rs +0 -91
- package/backend/src/executors/charm_opencode.rs +0 -113
- package/backend/src/executors/claude.rs +0 -887
- package/backend/src/executors/cleanup_script.rs +0 -124
- package/backend/src/executors/dev_server.rs +0 -53
- package/backend/src/executors/echo.rs +0 -79
- package/backend/src/executors/gemini/config.rs +0 -67
- package/backend/src/executors/gemini/streaming.rs +0 -363
- package/backend/src/executors/gemini.rs +0 -765
- package/backend/src/executors/mod.rs +0 -23
- package/backend/src/executors/opencode_ai.rs +0 -113
- package/backend/src/executors/setup_script.rs +0 -130
- package/backend/src/executors/sst_opencode/filter.rs +0 -184
- package/backend/src/executors/sst_opencode/tools.rs +0 -139
- package/backend/src/executors/sst_opencode.rs +0 -756
- package/backend/src/lib.rs +0 -45
- package/backend/src/main.rs +0 -324
- package/backend/src/mcp/mod.rs +0 -1
- package/backend/src/mcp/task_server.rs +0 -850
- package/backend/src/middleware/mod.rs +0 -3
- package/backend/src/middleware/model_loaders.rs +0 -242
- package/backend/src/models/api_response.rs +0 -36
- package/backend/src/models/config.rs +0 -375
- package/backend/src/models/execution_process.rs +0 -430
- package/backend/src/models/executor_session.rs +0 -225
- package/backend/src/models/mod.rs +0 -12
- package/backend/src/models/project.rs +0 -356
- package/backend/src/models/task.rs +0 -345
- package/backend/src/models/task_attempt.rs +0 -1214
- package/backend/src/models/task_template.rs +0 -146
- package/backend/src/openapi.rs +0 -93
- package/backend/src/routes/auth.rs +0 -297
- package/backend/src/routes/config.rs +0 -385
- package/backend/src/routes/filesystem.rs +0 -228
- package/backend/src/routes/health.rs +0 -16
- package/backend/src/routes/mod.rs +0 -9
- package/backend/src/routes/projects.rs +0 -562
- package/backend/src/routes/stream.rs +0 -244
- package/backend/src/routes/task_attempts.rs +0 -1172
- package/backend/src/routes/task_templates.rs +0 -229
- package/backend/src/routes/tasks.rs +0 -353
- package/backend/src/services/analytics.rs +0 -216
- package/backend/src/services/git_service.rs +0 -1321
- package/backend/src/services/github_service.rs +0 -307
- package/backend/src/services/mod.rs +0 -13
- package/backend/src/services/notification_service.rs +0 -263
- package/backend/src/services/pr_monitor.rs +0 -214
- package/backend/src/services/process_service.rs +0 -940
- package/backend/src/utils/path.rs +0 -96
- package/backend/src/utils/shell.rs +0 -19
- package/backend/src/utils/text.rs +0 -24
- package/backend/src/utils/worktree_manager.rs +0 -578
- package/backend/src/utils.rs +0 -125
- package/backend/test.db +0 -0
- package/build-npm-package.sh +0 -61
- package/dev_assets_seed/config.json +0 -19
- package/frontend/.eslintrc.json +0 -25
- package/frontend/.prettierrc.json +0 -8
- package/frontend/components.json +0 -17
- package/frontend/index.html +0 -19
- package/frontend/package-lock.json +0 -7321
- package/frontend/package.json +0 -61
- package/frontend/postcss.config.js +0 -6
- package/frontend/public/android-chrome-192x192.png +0 -0
- package/frontend/public/android-chrome-512x512.png +0 -0
- package/frontend/public/apple-touch-icon.png +0 -0
- package/frontend/public/automagik-forge-logo-dark.svg +0 -3
- package/frontend/public/automagik-forge-logo.svg +0 -3
- package/frontend/public/automagik-forge-screenshot-overview.png +0 -0
- package/frontend/public/favicon-16x16.png +0 -0
- package/frontend/public/favicon-32x32.png +0 -0
- package/frontend/public/favicon.ico +0 -0
- package/frontend/public/site.webmanifest +0 -1
- package/frontend/public/viba-kanban-favicon.png +0 -0
- package/frontend/src/App.tsx +0 -157
- package/frontend/src/components/DisclaimerDialog.tsx +0 -106
- package/frontend/src/components/GitHubLoginDialog.tsx +0 -314
- package/frontend/src/components/OnboardingDialog.tsx +0 -185
- package/frontend/src/components/PrivacyOptInDialog.tsx +0 -130
- package/frontend/src/components/ProvidePatDialog.tsx +0 -98
- package/frontend/src/components/TaskTemplateManager.tsx +0 -336
- package/frontend/src/components/config-provider.tsx +0 -119
- package/frontend/src/components/context/TaskDetailsContextProvider.tsx +0 -470
- package/frontend/src/components/context/taskDetailsContext.ts +0 -125
- package/frontend/src/components/keyboard-shortcuts-demo.tsx +0 -35
- package/frontend/src/components/layout/navbar.tsx +0 -86
- package/frontend/src/components/logo.tsx +0 -44
- package/frontend/src/components/projects/ProjectCard.tsx +0 -155
- package/frontend/src/components/projects/project-detail.tsx +0 -251
- package/frontend/src/components/projects/project-form-fields.tsx +0 -238
- package/frontend/src/components/projects/project-form.tsx +0 -301
- package/frontend/src/components/projects/project-list.tsx +0 -200
- package/frontend/src/components/projects/projects-page.tsx +0 -20
- package/frontend/src/components/tasks/BranchSelector.tsx +0 -169
- package/frontend/src/components/tasks/DeleteFileConfirmationDialog.tsx +0 -94
- package/frontend/src/components/tasks/EditorSelectionDialog.tsx +0 -119
- package/frontend/src/components/tasks/TaskCard.tsx +0 -154
- package/frontend/src/components/tasks/TaskDetails/CollapsibleToolbar.tsx +0 -33
- package/frontend/src/components/tasks/TaskDetails/DiffCard.tsx +0 -109
- package/frontend/src/components/tasks/TaskDetails/DiffChunkSection.tsx +0 -135
- package/frontend/src/components/tasks/TaskDetails/DiffFile.tsx +0 -296
- package/frontend/src/components/tasks/TaskDetails/DiffTab.tsx +0 -32
- package/frontend/src/components/tasks/TaskDetails/DisplayConversationEntry.tsx +0 -392
- package/frontend/src/components/tasks/TaskDetails/LogsTab/Conversation.tsx +0 -256
- package/frontend/src/components/tasks/TaskDetails/LogsTab/ConversationEntry.tsx +0 -56
- package/frontend/src/components/tasks/TaskDetails/LogsTab/NormalizedConversationViewer.tsx +0 -92
- package/frontend/src/components/tasks/TaskDetails/LogsTab/Prompt.tsx +0 -22
- package/frontend/src/components/tasks/TaskDetails/LogsTab/SetupScriptRunning.tsx +0 -49
- package/frontend/src/components/tasks/TaskDetails/LogsTab.tsx +0 -186
- package/frontend/src/components/tasks/TaskDetails/ProcessesTab.tsx +0 -288
- package/frontend/src/components/tasks/TaskDetails/RelatedTasksTab.tsx +0 -216
- package/frontend/src/components/tasks/TaskDetails/TabNavigation.tsx +0 -93
- package/frontend/src/components/tasks/TaskDetailsHeader.tsx +0 -169
- package/frontend/src/components/tasks/TaskDetailsPanel.tsx +0 -126
- package/frontend/src/components/tasks/TaskDetailsToolbar.tsx +0 -302
- package/frontend/src/components/tasks/TaskFollowUpSection.tsx +0 -130
- package/frontend/src/components/tasks/TaskFormDialog.tsx +0 -400
- package/frontend/src/components/tasks/TaskKanbanBoard.tsx +0 -180
- package/frontend/src/components/tasks/Toolbar/CreateAttempt.tsx +0 -259
- package/frontend/src/components/tasks/Toolbar/CreatePRDialog.tsx +0 -243
- package/frontend/src/components/tasks/Toolbar/CurrentAttempt.tsx +0 -899
- package/frontend/src/components/tasks/index.ts +0 -2
- package/frontend/src/components/theme-provider.tsx +0 -82
- package/frontend/src/components/theme-toggle.tsx +0 -36
- package/frontend/src/components/ui/alert.tsx +0 -59
- package/frontend/src/components/ui/auto-expanding-textarea.tsx +0 -70
- package/frontend/src/components/ui/badge.tsx +0 -36
- package/frontend/src/components/ui/button.tsx +0 -56
- package/frontend/src/components/ui/card.tsx +0 -86
- package/frontend/src/components/ui/checkbox.tsx +0 -44
- package/frontend/src/components/ui/chip.tsx +0 -25
- package/frontend/src/components/ui/dialog.tsx +0 -124
- package/frontend/src/components/ui/dropdown-menu.tsx +0 -198
- package/frontend/src/components/ui/file-search-textarea.tsx +0 -292
- package/frontend/src/components/ui/folder-picker.tsx +0 -279
- package/frontend/src/components/ui/input.tsx +0 -25
- package/frontend/src/components/ui/label.tsx +0 -24
- package/frontend/src/components/ui/loader.tsx +0 -26
- package/frontend/src/components/ui/markdown-renderer.tsx +0 -75
- package/frontend/src/components/ui/select.tsx +0 -160
- package/frontend/src/components/ui/separator.tsx +0 -31
- package/frontend/src/components/ui/shadcn-io/kanban/index.tsx +0 -185
- package/frontend/src/components/ui/table.tsx +0 -117
- package/frontend/src/components/ui/tabs.tsx +0 -53
- package/frontend/src/components/ui/textarea.tsx +0 -22
- package/frontend/src/components/ui/tooltip.tsx +0 -28
- package/frontend/src/hooks/useNormalizedConversation.ts +0 -440
- package/frontend/src/index.css +0 -225
- package/frontend/src/lib/api.ts +0 -630
- package/frontend/src/lib/keyboard-shortcuts.ts +0 -266
- package/frontend/src/lib/responsive-config.ts +0 -70
- package/frontend/src/lib/types.ts +0 -39
- package/frontend/src/lib/utils.ts +0 -10
- package/frontend/src/main.tsx +0 -50
- package/frontend/src/pages/McpServers.tsx +0 -418
- package/frontend/src/pages/Settings.tsx +0 -610
- package/frontend/src/pages/project-tasks.tsx +0 -575
- package/frontend/src/pages/projects.tsx +0 -18
- package/frontend/src/vite-env.d.ts +0 -1
- package/frontend/tailwind.config.js +0 -125
- package/frontend/tsconfig.json +0 -26
- package/frontend/tsconfig.node.json +0 -10
- package/frontend/vite.config.ts +0 -33
- package/npx-cli/README.md +0 -159
- package/npx-cli/automagik-forge-0.1.0.tgz +0 -0
- package/npx-cli/automagik-forge-0.1.10.tgz +0 -0
- package/npx-cli/package.json +0 -17
- package/npx-cli/vibe-kanban-0.0.55.tgz +0 -0
- package/pnpm-workspace.yaml +0 -2
- package/rust-toolchain.toml +0 -11
- package/rustfmt.toml +0 -3
- package/scripts/load-env.js +0 -43
- package/scripts/mcp_test.js +0 -374
- package/scripts/prepare-db.js +0 -45
- package/scripts/setup-dev-environment.js +0 -274
- package/scripts/start-mcp-sse.js +0 -70
- package/scripts/test-debug.js +0 -32
- package/scripts/test-mcp-sse.js +0 -138
- package/scripts/test-simple.js +0 -44
- package/scripts/test-wish-final.js +0 -179
- package/scripts/test-wish-system.js +0 -221
- package/shared/types.ts +0 -182
- package/test-npm-package.sh +0 -42
- /package/{npx-cli/bin → bin}/cli.js +0 -0
@@ -1,242 +0,0 @@
|
|
1
|
-
use axum::{
|
2
|
-
extract::{Path, State},
|
3
|
-
http::StatusCode,
|
4
|
-
middleware::Next,
|
5
|
-
response::Response,
|
6
|
-
};
|
7
|
-
use uuid::Uuid;
|
8
|
-
|
9
|
-
use crate::{
|
10
|
-
app_state::AppState,
|
11
|
-
models::{
|
12
|
-
execution_process::ExecutionProcess, project::Project, task::Task,
|
13
|
-
task_attempt::TaskAttempt, task_template::TaskTemplate,
|
14
|
-
},
|
15
|
-
};
|
16
|
-
|
17
|
-
/// Middleware that loads and injects a Project based on the project_id path parameter
|
18
|
-
pub async fn load_project_middleware(
|
19
|
-
State(app_state): State<AppState>,
|
20
|
-
Path(project_id): Path<Uuid>,
|
21
|
-
request: axum::extract::Request,
|
22
|
-
next: Next,
|
23
|
-
) -> Result<Response, StatusCode> {
|
24
|
-
// Load the project from the database
|
25
|
-
let project = match Project::find_by_id(&app_state.db_pool, project_id).await {
|
26
|
-
Ok(Some(project)) => project,
|
27
|
-
Ok(None) => {
|
28
|
-
tracing::warn!("Project {} not found", project_id);
|
29
|
-
return Err(StatusCode::NOT_FOUND);
|
30
|
-
}
|
31
|
-
Err(e) => {
|
32
|
-
tracing::error!("Failed to fetch project {}: {}", project_id, e);
|
33
|
-
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
34
|
-
}
|
35
|
-
};
|
36
|
-
|
37
|
-
// Insert the project as an extension
|
38
|
-
let mut request = request;
|
39
|
-
request.extensions_mut().insert(project);
|
40
|
-
|
41
|
-
// Continue with the next middleware/handler
|
42
|
-
Ok(next.run(request).await)
|
43
|
-
}
|
44
|
-
|
45
|
-
/// Middleware that loads and injects both Project and Task based on project_id and task_id path parameters
|
46
|
-
pub async fn load_task_middleware(
|
47
|
-
State(app_state): State<AppState>,
|
48
|
-
Path((project_id, task_id)): Path<(Uuid, Uuid)>,
|
49
|
-
request: axum::extract::Request,
|
50
|
-
next: Next,
|
51
|
-
) -> Result<Response, StatusCode> {
|
52
|
-
// Load the project first
|
53
|
-
let project = match Project::find_by_id(&app_state.db_pool, project_id).await {
|
54
|
-
Ok(Some(project)) => project,
|
55
|
-
Ok(None) => {
|
56
|
-
tracing::warn!("Project {} not found", project_id);
|
57
|
-
return Err(StatusCode::NOT_FOUND);
|
58
|
-
}
|
59
|
-
Err(e) => {
|
60
|
-
tracing::error!("Failed to fetch project {}: {}", project_id, e);
|
61
|
-
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
62
|
-
}
|
63
|
-
};
|
64
|
-
|
65
|
-
// Load the task and validate it belongs to the project
|
66
|
-
let task = match Task::find_by_id_and_project_id(&app_state.db_pool, task_id, project_id).await
|
67
|
-
{
|
68
|
-
Ok(Some(task)) => task,
|
69
|
-
Ok(None) => {
|
70
|
-
tracing::warn!("Task {} not found in project {}", task_id, project_id);
|
71
|
-
return Err(StatusCode::NOT_FOUND);
|
72
|
-
}
|
73
|
-
Err(e) => {
|
74
|
-
tracing::error!(
|
75
|
-
"Failed to fetch task {} in project {}: {}",
|
76
|
-
task_id,
|
77
|
-
project_id,
|
78
|
-
e
|
79
|
-
);
|
80
|
-
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
81
|
-
}
|
82
|
-
};
|
83
|
-
|
84
|
-
// Insert both models as extensions
|
85
|
-
let mut request = request;
|
86
|
-
request.extensions_mut().insert(project);
|
87
|
-
request.extensions_mut().insert(task);
|
88
|
-
|
89
|
-
// Continue with the next middleware/handler
|
90
|
-
Ok(next.run(request).await)
|
91
|
-
}
|
92
|
-
|
93
|
-
/// Middleware that loads and injects Project, Task, and TaskAttempt based on project_id, task_id, and attempt_id path parameters
|
94
|
-
pub async fn load_task_attempt_middleware(
|
95
|
-
State(app_state): State<AppState>,
|
96
|
-
Path((project_id, task_id, attempt_id)): Path<(Uuid, Uuid, Uuid)>,
|
97
|
-
request: axum::extract::Request,
|
98
|
-
next: Next,
|
99
|
-
) -> Result<Response, StatusCode> {
|
100
|
-
// Load the full context in one call using the existing method
|
101
|
-
let context = match TaskAttempt::load_context(
|
102
|
-
&app_state.db_pool,
|
103
|
-
attempt_id,
|
104
|
-
task_id,
|
105
|
-
project_id,
|
106
|
-
)
|
107
|
-
.await
|
108
|
-
{
|
109
|
-
Ok(context) => context,
|
110
|
-
Err(e) => {
|
111
|
-
tracing::error!(
|
112
|
-
"Failed to load context for attempt {} in task {} in project {}: {}",
|
113
|
-
attempt_id,
|
114
|
-
task_id,
|
115
|
-
project_id,
|
116
|
-
e
|
117
|
-
);
|
118
|
-
return Err(StatusCode::NOT_FOUND);
|
119
|
-
}
|
120
|
-
};
|
121
|
-
|
122
|
-
// Insert all models as extensions
|
123
|
-
let mut request = request;
|
124
|
-
request.extensions_mut().insert(context.project);
|
125
|
-
request.extensions_mut().insert(context.task);
|
126
|
-
request.extensions_mut().insert(context.task_attempt);
|
127
|
-
|
128
|
-
// Continue with the next middleware/handler
|
129
|
-
Ok(next.run(request).await)
|
130
|
-
}
|
131
|
-
|
132
|
-
/// Simple middleware that loads and injects ExecutionProcess based on the process_id path parameter
|
133
|
-
/// without any additional validation
|
134
|
-
pub async fn load_execution_process_simple_middleware(
|
135
|
-
State(app_state): State<AppState>,
|
136
|
-
Path(process_id): Path<Uuid>,
|
137
|
-
mut request: axum::extract::Request,
|
138
|
-
next: Next,
|
139
|
-
) -> Result<Response, StatusCode> {
|
140
|
-
// Load the execution process from the database
|
141
|
-
let execution_process = match ExecutionProcess::find_by_id(&app_state.db_pool, process_id).await
|
142
|
-
{
|
143
|
-
Ok(Some(process)) => process,
|
144
|
-
Ok(None) => {
|
145
|
-
tracing::warn!("ExecutionProcess {} not found", process_id);
|
146
|
-
return Err(StatusCode::NOT_FOUND);
|
147
|
-
}
|
148
|
-
Err(e) => {
|
149
|
-
tracing::error!("Failed to fetch execution process {}: {}", process_id, e);
|
150
|
-
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
151
|
-
}
|
152
|
-
};
|
153
|
-
|
154
|
-
// Inject the execution process into the request
|
155
|
-
request.extensions_mut().insert(execution_process);
|
156
|
-
|
157
|
-
// Continue to the next middleware/handler
|
158
|
-
Ok(next.run(request).await)
|
159
|
-
}
|
160
|
-
|
161
|
-
/// Middleware that loads and injects Project, Task, TaskAttempt, and ExecutionProcess
|
162
|
-
/// based on the path parameters: project_id, task_id, attempt_id, process_id
|
163
|
-
pub async fn load_execution_process_with_context_middleware(
|
164
|
-
State(app_state): State<AppState>,
|
165
|
-
Path((project_id, task_id, attempt_id, process_id)): Path<(Uuid, Uuid, Uuid, Uuid)>,
|
166
|
-
request: axum::extract::Request,
|
167
|
-
next: Next,
|
168
|
-
) -> Result<Response, StatusCode> {
|
169
|
-
// Load the task attempt context first
|
170
|
-
let context = match TaskAttempt::load_context(
|
171
|
-
&app_state.db_pool,
|
172
|
-
attempt_id,
|
173
|
-
task_id,
|
174
|
-
project_id,
|
175
|
-
)
|
176
|
-
.await
|
177
|
-
{
|
178
|
-
Ok(context) => context,
|
179
|
-
Err(e) => {
|
180
|
-
tracing::error!(
|
181
|
-
"Failed to load context for attempt {} in task {} in project {}: {}",
|
182
|
-
attempt_id,
|
183
|
-
task_id,
|
184
|
-
project_id,
|
185
|
-
e
|
186
|
-
);
|
187
|
-
return Err(StatusCode::NOT_FOUND);
|
188
|
-
}
|
189
|
-
};
|
190
|
-
|
191
|
-
// Load the execution process
|
192
|
-
let execution_process = match ExecutionProcess::find_by_id(&app_state.db_pool, process_id).await
|
193
|
-
{
|
194
|
-
Ok(Some(process)) => process,
|
195
|
-
Ok(None) => {
|
196
|
-
tracing::warn!("ExecutionProcess {} not found", process_id);
|
197
|
-
return Err(StatusCode::NOT_FOUND);
|
198
|
-
}
|
199
|
-
Err(e) => {
|
200
|
-
tracing::error!("Failed to fetch execution process {}: {}", process_id, e);
|
201
|
-
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
202
|
-
}
|
203
|
-
};
|
204
|
-
|
205
|
-
// Insert all models as extensions
|
206
|
-
let mut request = request;
|
207
|
-
request.extensions_mut().insert(context.project);
|
208
|
-
request.extensions_mut().insert(context.task);
|
209
|
-
request.extensions_mut().insert(context.task_attempt);
|
210
|
-
request.extensions_mut().insert(execution_process);
|
211
|
-
|
212
|
-
// Continue with the next middleware/handler
|
213
|
-
Ok(next.run(request).await)
|
214
|
-
}
|
215
|
-
|
216
|
-
/// Middleware that loads and injects TaskTemplate based on the template_id path parameter
|
217
|
-
pub async fn load_task_template_middleware(
|
218
|
-
State(app_state): State<AppState>,
|
219
|
-
Path(template_id): Path<Uuid>,
|
220
|
-
request: axum::extract::Request,
|
221
|
-
next: Next,
|
222
|
-
) -> Result<Response, StatusCode> {
|
223
|
-
// Load the task template from the database
|
224
|
-
let task_template = match TaskTemplate::find_by_id(&app_state.db_pool, template_id).await {
|
225
|
-
Ok(Some(template)) => template,
|
226
|
-
Ok(None) => {
|
227
|
-
tracing::warn!("TaskTemplate {} not found", template_id);
|
228
|
-
return Err(StatusCode::NOT_FOUND);
|
229
|
-
}
|
230
|
-
Err(e) => {
|
231
|
-
tracing::error!("Failed to fetch task template {}: {}", template_id, e);
|
232
|
-
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
233
|
-
}
|
234
|
-
};
|
235
|
-
|
236
|
-
// Insert the task template as an extension
|
237
|
-
let mut request = request;
|
238
|
-
request.extensions_mut().insert(task_template);
|
239
|
-
|
240
|
-
// Continue with the next middleware/handler
|
241
|
-
Ok(next.run(request).await)
|
242
|
-
}
|
@@ -1,36 +0,0 @@
|
|
1
|
-
mod response {
|
2
|
-
use serde::Serialize;
|
3
|
-
use ts_rs::TS;
|
4
|
-
use utoipa::ToSchema;
|
5
|
-
|
6
|
-
#[derive(Debug, Serialize, TS, ToSchema)]
|
7
|
-
#[ts(export)]
|
8
|
-
pub struct ApiResponse<T> {
|
9
|
-
success: bool,
|
10
|
-
data: Option<T>,
|
11
|
-
message: Option<String>,
|
12
|
-
}
|
13
|
-
|
14
|
-
impl<T> ApiResponse<T> {
|
15
|
-
/// Creates a successful response, with `data` and no message.
|
16
|
-
pub fn success(data: T) -> Self {
|
17
|
-
ApiResponse {
|
18
|
-
success: true,
|
19
|
-
data: Some(data),
|
20
|
-
message: None,
|
21
|
-
}
|
22
|
-
}
|
23
|
-
|
24
|
-
/// Creates an error response, with `message` and no data.
|
25
|
-
pub fn error(message: &str) -> Self {
|
26
|
-
ApiResponse {
|
27
|
-
success: false,
|
28
|
-
data: None,
|
29
|
-
message: Some(message.to_string()),
|
30
|
-
}
|
31
|
-
}
|
32
|
-
}
|
33
|
-
}
|
34
|
-
|
35
|
-
// Re-export the type, but its fields remain private
|
36
|
-
pub use response::ApiResponse;
|
@@ -1,375 +0,0 @@
|
|
1
|
-
use std::path::PathBuf;
|
2
|
-
|
3
|
-
use serde::{Deserialize, Serialize};
|
4
|
-
use ts_rs::TS;
|
5
|
-
use utoipa::ToSchema;
|
6
|
-
|
7
|
-
use crate::executor::ExecutorConfig;
|
8
|
-
|
9
|
-
#[derive(Debug, Clone, Serialize, Deserialize, TS, ToSchema)]
|
10
|
-
#[ts(export)]
|
11
|
-
pub struct Config {
|
12
|
-
pub theme: ThemeMode,
|
13
|
-
pub executor: ExecutorConfig,
|
14
|
-
pub disclaimer_acknowledged: bool,
|
15
|
-
pub onboarding_acknowledged: bool,
|
16
|
-
pub github_login_acknowledged: bool,
|
17
|
-
pub telemetry_acknowledged: bool,
|
18
|
-
pub sound_alerts: bool,
|
19
|
-
pub sound_file: SoundFile,
|
20
|
-
pub push_notifications: bool,
|
21
|
-
pub editor: EditorConfig,
|
22
|
-
pub github: GitHubConfig,
|
23
|
-
pub analytics_enabled: Option<bool>,
|
24
|
-
}
|
25
|
-
|
26
|
-
#[derive(Debug, Clone, Serialize, Deserialize, TS, ToSchema)]
|
27
|
-
#[ts(export)]
|
28
|
-
#[serde(rename_all = "lowercase")]
|
29
|
-
pub enum ThemeMode {
|
30
|
-
Light,
|
31
|
-
Dark,
|
32
|
-
System,
|
33
|
-
Purple,
|
34
|
-
Green,
|
35
|
-
Blue,
|
36
|
-
Orange,
|
37
|
-
Red,
|
38
|
-
}
|
39
|
-
|
40
|
-
#[derive(Debug, Clone, Serialize, Deserialize, TS, ToSchema)]
|
41
|
-
#[ts(export)]
|
42
|
-
pub struct EditorConfig {
|
43
|
-
pub editor_type: EditorType,
|
44
|
-
pub custom_command: Option<String>,
|
45
|
-
}
|
46
|
-
|
47
|
-
#[derive(Debug, Clone, Serialize, Deserialize, TS, ToSchema)]
|
48
|
-
#[ts(export)]
|
49
|
-
pub struct GitHubConfig {
|
50
|
-
pub pat: Option<String>,
|
51
|
-
pub token: Option<String>,
|
52
|
-
pub username: Option<String>,
|
53
|
-
pub primary_email: Option<String>,
|
54
|
-
pub default_pr_base: Option<String>,
|
55
|
-
}
|
56
|
-
|
57
|
-
#[derive(Debug, Clone, Serialize, Deserialize, TS, ToSchema)]
|
58
|
-
#[ts(export)]
|
59
|
-
#[serde(rename_all = "lowercase")]
|
60
|
-
pub enum EditorType {
|
61
|
-
VSCode,
|
62
|
-
Cursor,
|
63
|
-
Windsurf,
|
64
|
-
IntelliJ,
|
65
|
-
Zed,
|
66
|
-
Custom,
|
67
|
-
}
|
68
|
-
|
69
|
-
#[derive(Debug, Clone, Serialize, Deserialize, TS, ToSchema)]
|
70
|
-
#[ts(export)]
|
71
|
-
#[serde(rename_all = "kebab-case")]
|
72
|
-
pub enum SoundFile {
|
73
|
-
AbstractSound1,
|
74
|
-
AbstractSound2,
|
75
|
-
AbstractSound3,
|
76
|
-
AbstractSound4,
|
77
|
-
CowMooing,
|
78
|
-
PhoneVibration,
|
79
|
-
Rooster,
|
80
|
-
}
|
81
|
-
|
82
|
-
// Constants for frontend
|
83
|
-
#[derive(Debug, Clone, Serialize, Deserialize, TS, ToSchema)]
|
84
|
-
#[ts(export)]
|
85
|
-
pub struct EditorConstants {
|
86
|
-
pub editor_types: Vec<EditorType>,
|
87
|
-
pub editor_labels: Vec<String>,
|
88
|
-
}
|
89
|
-
|
90
|
-
#[derive(Debug, Clone, Serialize, Deserialize, TS, ToSchema)]
|
91
|
-
#[ts(export)]
|
92
|
-
pub struct SoundConstants {
|
93
|
-
pub sound_files: Vec<SoundFile>,
|
94
|
-
pub sound_labels: Vec<String>,
|
95
|
-
}
|
96
|
-
|
97
|
-
impl EditorConstants {
|
98
|
-
pub fn new() -> Self {
|
99
|
-
Self {
|
100
|
-
editor_types: vec![
|
101
|
-
EditorType::VSCode,
|
102
|
-
EditorType::Cursor,
|
103
|
-
EditorType::Windsurf,
|
104
|
-
EditorType::IntelliJ,
|
105
|
-
EditorType::Zed,
|
106
|
-
EditorType::Custom,
|
107
|
-
],
|
108
|
-
editor_labels: vec![
|
109
|
-
"VS Code".to_string(),
|
110
|
-
"Cursor".to_string(),
|
111
|
-
"Windsurf".to_string(),
|
112
|
-
"IntelliJ IDEA".to_string(),
|
113
|
-
"Zed".to_string(),
|
114
|
-
"Custom".to_string(),
|
115
|
-
],
|
116
|
-
}
|
117
|
-
}
|
118
|
-
}
|
119
|
-
|
120
|
-
impl Default for EditorConstants {
|
121
|
-
fn default() -> Self {
|
122
|
-
Self::new()
|
123
|
-
}
|
124
|
-
}
|
125
|
-
|
126
|
-
impl SoundConstants {
|
127
|
-
pub fn new() -> Self {
|
128
|
-
Self {
|
129
|
-
sound_files: vec![
|
130
|
-
SoundFile::AbstractSound1,
|
131
|
-
SoundFile::AbstractSound2,
|
132
|
-
SoundFile::AbstractSound3,
|
133
|
-
SoundFile::AbstractSound4,
|
134
|
-
SoundFile::CowMooing,
|
135
|
-
SoundFile::PhoneVibration,
|
136
|
-
SoundFile::Rooster,
|
137
|
-
],
|
138
|
-
sound_labels: vec![
|
139
|
-
"Gentle Chime".to_string(),
|
140
|
-
"Soft Bell".to_string(),
|
141
|
-
"Digital Tone".to_string(),
|
142
|
-
"Subtle Alert".to_string(),
|
143
|
-
"Cow Mooing".to_string(),
|
144
|
-
"Phone Vibration".to_string(),
|
145
|
-
"Rooster Call".to_string(),
|
146
|
-
],
|
147
|
-
}
|
148
|
-
}
|
149
|
-
}
|
150
|
-
|
151
|
-
impl Default for SoundConstants {
|
152
|
-
fn default() -> Self {
|
153
|
-
Self::new()
|
154
|
-
}
|
155
|
-
}
|
156
|
-
|
157
|
-
impl Default for Config {
|
158
|
-
fn default() -> Self {
|
159
|
-
Self {
|
160
|
-
theme: ThemeMode::System,
|
161
|
-
executor: ExecutorConfig::Claude,
|
162
|
-
disclaimer_acknowledged: false,
|
163
|
-
onboarding_acknowledged: false,
|
164
|
-
github_login_acknowledged: false,
|
165
|
-
telemetry_acknowledged: false,
|
166
|
-
sound_alerts: true,
|
167
|
-
sound_file: SoundFile::AbstractSound4,
|
168
|
-
push_notifications: true,
|
169
|
-
editor: EditorConfig::default(),
|
170
|
-
github: GitHubConfig::default(),
|
171
|
-
analytics_enabled: None,
|
172
|
-
}
|
173
|
-
}
|
174
|
-
}
|
175
|
-
|
176
|
-
impl Default for EditorConfig {
|
177
|
-
fn default() -> Self {
|
178
|
-
Self {
|
179
|
-
editor_type: EditorType::VSCode,
|
180
|
-
custom_command: None,
|
181
|
-
}
|
182
|
-
}
|
183
|
-
}
|
184
|
-
|
185
|
-
impl Default for GitHubConfig {
|
186
|
-
fn default() -> Self {
|
187
|
-
Self {
|
188
|
-
pat: None,
|
189
|
-
token: None,
|
190
|
-
username: None,
|
191
|
-
primary_email: None,
|
192
|
-
default_pr_base: Some("main".to_string()),
|
193
|
-
}
|
194
|
-
}
|
195
|
-
}
|
196
|
-
|
197
|
-
impl EditorConfig {
|
198
|
-
pub fn get_command(&self) -> Vec<String> {
|
199
|
-
match &self.editor_type {
|
200
|
-
EditorType::VSCode => vec!["code".to_string()],
|
201
|
-
EditorType::Cursor => vec!["cursor".to_string()],
|
202
|
-
EditorType::Windsurf => vec!["windsurf".to_string()],
|
203
|
-
EditorType::IntelliJ => vec!["idea".to_string()],
|
204
|
-
EditorType::Zed => vec!["zed".to_string()],
|
205
|
-
EditorType::Custom => {
|
206
|
-
if let Some(custom) = &self.custom_command {
|
207
|
-
custom.split_whitespace().map(|s| s.to_string()).collect()
|
208
|
-
} else {
|
209
|
-
vec!["code".to_string()] // fallback to VSCode
|
210
|
-
}
|
211
|
-
}
|
212
|
-
}
|
213
|
-
}
|
214
|
-
}
|
215
|
-
|
216
|
-
impl SoundFile {
|
217
|
-
pub fn to_filename(&self) -> &'static str {
|
218
|
-
match self {
|
219
|
-
SoundFile::AbstractSound1 => "abstract-sound1.wav",
|
220
|
-
SoundFile::AbstractSound2 => "abstract-sound2.wav",
|
221
|
-
SoundFile::AbstractSound3 => "abstract-sound3.wav",
|
222
|
-
SoundFile::AbstractSound4 => "abstract-sound4.wav",
|
223
|
-
SoundFile::CowMooing => "cow-mooing.wav",
|
224
|
-
SoundFile::PhoneVibration => "phone-vibration.wav",
|
225
|
-
SoundFile::Rooster => "rooster.wav",
|
226
|
-
}
|
227
|
-
}
|
228
|
-
|
229
|
-
/// Get or create a cached sound file with the embedded sound data
|
230
|
-
pub async fn get_path(&self) -> Result<PathBuf, Box<dyn std::error::Error + Send + Sync>> {
|
231
|
-
use std::io::Write;
|
232
|
-
|
233
|
-
let filename = self.to_filename();
|
234
|
-
let cache_dir = crate::utils::cache_dir();
|
235
|
-
let cached_path = cache_dir.join(format!("sound-{}", filename));
|
236
|
-
|
237
|
-
// Check if cached file already exists and is valid
|
238
|
-
if cached_path.exists() {
|
239
|
-
// Verify file has content (basic validation)
|
240
|
-
if let Ok(metadata) = std::fs::metadata(&cached_path) {
|
241
|
-
if metadata.len() > 0 {
|
242
|
-
return Ok(cached_path);
|
243
|
-
}
|
244
|
-
}
|
245
|
-
}
|
246
|
-
|
247
|
-
// File doesn't exist or is invalid, create it
|
248
|
-
let sound_data = crate::SoundAssets::get(filename)
|
249
|
-
.ok_or_else(|| format!("Embedded sound file not found: {}", filename))?
|
250
|
-
.data;
|
251
|
-
|
252
|
-
// Ensure cache directory exists
|
253
|
-
std::fs::create_dir_all(&cache_dir)
|
254
|
-
.map_err(|e| format!("Failed to create cache directory: {}", e))?;
|
255
|
-
|
256
|
-
let mut file = std::fs::File::create(&cached_path)
|
257
|
-
.map_err(|e| format!("Failed to create cached sound file: {}", e))?;
|
258
|
-
|
259
|
-
file.write_all(&sound_data)
|
260
|
-
.map_err(|e| format!("Failed to write sound data to cached file: {}", e))?;
|
261
|
-
|
262
|
-
drop(file); // Ensure file is closed
|
263
|
-
|
264
|
-
Ok(cached_path)
|
265
|
-
}
|
266
|
-
}
|
267
|
-
|
268
|
-
impl Config {
|
269
|
-
pub fn load(config_path: &PathBuf) -> anyhow::Result<Self> {
|
270
|
-
if config_path.exists() {
|
271
|
-
let content = std::fs::read_to_string(config_path)?;
|
272
|
-
|
273
|
-
// Try to deserialize as is first
|
274
|
-
match serde_json::from_str::<Config>(&content) {
|
275
|
-
Ok(mut config) => {
|
276
|
-
if config.analytics_enabled.is_none() {
|
277
|
-
config.analytics_enabled = Some(true);
|
278
|
-
}
|
279
|
-
|
280
|
-
// Always save back to ensure new fields are written to disk
|
281
|
-
config.save(config_path)?;
|
282
|
-
Ok(config)
|
283
|
-
}
|
284
|
-
Err(_) => {
|
285
|
-
// If full deserialization fails, try to merge with defaults
|
286
|
-
match Self::load_with_defaults(&content, config_path) {
|
287
|
-
Ok(config) => Ok(config),
|
288
|
-
Err(_) => {
|
289
|
-
// Even partial loading failed - backup the corrupted file
|
290
|
-
if let Err(e) = Self::backup_corrupted_config(config_path) {
|
291
|
-
tracing::error!("Failed to backup corrupted config: {}", e);
|
292
|
-
}
|
293
|
-
|
294
|
-
// Remove corrupted file and create a default config
|
295
|
-
if let Err(e) = std::fs::remove_file(config_path) {
|
296
|
-
tracing::error!("Failed to remove corrupted config file: {}", e);
|
297
|
-
}
|
298
|
-
|
299
|
-
// Create and save default config
|
300
|
-
let config = Config::default();
|
301
|
-
config.save(config_path)?;
|
302
|
-
Ok(config)
|
303
|
-
}
|
304
|
-
}
|
305
|
-
}
|
306
|
-
}
|
307
|
-
} else {
|
308
|
-
let config = Config::default();
|
309
|
-
config.save(config_path)?;
|
310
|
-
Ok(config)
|
311
|
-
}
|
312
|
-
}
|
313
|
-
|
314
|
-
fn load_with_defaults(content: &str, config_path: &PathBuf) -> anyhow::Result<Self> {
|
315
|
-
// Parse as generic JSON value
|
316
|
-
let existing_value: serde_json::Value = serde_json::from_str(content)?;
|
317
|
-
|
318
|
-
// Get default config as JSON value
|
319
|
-
let default_config = Config::default();
|
320
|
-
let default_value = serde_json::to_value(&default_config)?;
|
321
|
-
|
322
|
-
// Merge existing config with defaults
|
323
|
-
let merged_value = Self::merge_json_values(default_value, existing_value);
|
324
|
-
|
325
|
-
// Deserialize merged value back to Config
|
326
|
-
let config: Config = serde_json::from_value(merged_value)?;
|
327
|
-
|
328
|
-
// Save the updated config with any missing defaults
|
329
|
-
config.save(config_path)?;
|
330
|
-
|
331
|
-
Ok(config)
|
332
|
-
}
|
333
|
-
|
334
|
-
fn merge_json_values(
|
335
|
-
mut base: serde_json::Value,
|
336
|
-
overlay: serde_json::Value,
|
337
|
-
) -> serde_json::Value {
|
338
|
-
match (&mut base, overlay) {
|
339
|
-
(serde_json::Value::Object(base_map), serde_json::Value::Object(overlay_map)) => {
|
340
|
-
for (key, value) in overlay_map {
|
341
|
-
base_map
|
342
|
-
.entry(key)
|
343
|
-
.and_modify(|base_value| {
|
344
|
-
*base_value =
|
345
|
-
Self::merge_json_values(base_value.clone(), value.clone());
|
346
|
-
})
|
347
|
-
.or_insert(value);
|
348
|
-
}
|
349
|
-
base
|
350
|
-
}
|
351
|
-
(_, overlay) => overlay, // Use overlay value for non-objects
|
352
|
-
}
|
353
|
-
}
|
354
|
-
|
355
|
-
/// Create a backup of the corrupted config file
|
356
|
-
fn backup_corrupted_config(config_path: &PathBuf) -> anyhow::Result<()> {
|
357
|
-
let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
|
358
|
-
let backup_filename = format!("config_backup_{}.json", timestamp);
|
359
|
-
|
360
|
-
let backup_path = config_path
|
361
|
-
.parent()
|
362
|
-
.unwrap_or_else(|| std::path::Path::new("."))
|
363
|
-
.join(backup_filename);
|
364
|
-
|
365
|
-
std::fs::copy(config_path, &backup_path)?;
|
366
|
-
tracing::info!("Corrupted config backed up to: {}", backup_path.display());
|
367
|
-
Ok(())
|
368
|
-
}
|
369
|
-
|
370
|
-
pub fn save(&self, config_path: &PathBuf) -> anyhow::Result<()> {
|
371
|
-
let content = serde_json::to_string_pretty(self)?;
|
372
|
-
std::fs::write(config_path, content)?;
|
373
|
-
Ok(())
|
374
|
-
}
|
375
|
-
}
|