prjct-cli 1.22.0 → 1.24.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/CHANGELOG.md +230 -0
- package/bin/prjct +30 -13
- package/dist/bin/prjct-core.mjs +1748 -0
- package/dist/bin/prjct.mjs +17 -36672
- package/dist/cli/linear.mjs +14 -0
- package/dist/daemon/entry.mjs +1429 -0
- package/dist/templates.json +1 -0
- package/package.json +4 -5
- package/bin/prjct.ts +0 -342
- package/core/__tests__/agentic/analysis-injection.test.ts +0 -377
- package/core/__tests__/agentic/cache-eviction.test.ts +0 -294
- package/core/__tests__/agentic/command-context.test.ts +0 -281
- package/core/__tests__/agentic/command-executor.test.ts +0 -659
- package/core/__tests__/agentic/domain-classifier.test.ts +0 -330
- package/core/__tests__/agentic/injection-validator.test.ts +0 -255
- package/core/__tests__/agentic/memory-system.test.ts +0 -281
- package/core/__tests__/agentic/plan-mode.test.ts +0 -386
- package/core/__tests__/agentic/prompt-assembly.test.ts +0 -298
- package/core/__tests__/agentic/prompt-builder.test.ts +0 -243
- package/core/__tests__/agentic/response-validator.test.ts +0 -263
- package/core/__tests__/agentic/semantic-matching.test.ts +0 -131
- package/core/__tests__/agentic/smart-context.test.ts +0 -372
- package/core/__tests__/agentic/tech-normalizer.test.ts +0 -136
- package/core/__tests__/agentic/token-budget.test.ts +0 -294
- package/core/__tests__/ai-tools/formatters.test.ts +0 -476
- package/core/__tests__/domain/bm25.test.ts +0 -225
- package/core/__tests__/domain/change-propagator.test.ts +0 -100
- package/core/__tests__/domain/fibonacci.test.ts +0 -113
- package/core/__tests__/domain/file-hasher.test.ts +0 -146
- package/core/__tests__/domain/file-ranker.test.ts +0 -169
- package/core/__tests__/domain/git-cochange.test.ts +0 -121
- package/core/__tests__/domain/import-graph.test.ts +0 -156
- package/core/__tests__/domain/velocity.test.ts +0 -623
- package/core/__tests__/infrastructure/performance-tracker.test.ts +0 -328
- package/core/__tests__/schemas/model.test.ts +0 -272
- package/core/__tests__/services/dependency-validator.test.ts +0 -175
- package/core/__tests__/services/hierarchical-agent-resolver.test.ts +0 -359
- package/core/__tests__/services/nested-context-resolver.test.ts +0 -443
- package/core/__tests__/services/project-index.test.ts +0 -355
- package/core/__tests__/services/staleness-checker.test.ts +0 -204
- package/core/__tests__/storage/analysis-storage.test.ts +0 -641
- package/core/__tests__/storage/archive-storage.test.ts +0 -455
- package/core/__tests__/storage/safe-reader.test.ts +0 -262
- package/core/__tests__/storage/sqlite-migration.test.ts +0 -1016
- package/core/__tests__/storage/state-storage-feedback.test.ts +0 -463
- package/core/__tests__/storage/state-storage-history.test.ts +0 -469
- package/core/__tests__/storage/storage-manager.test.ts +0 -383
- package/core/__tests__/storage/subtask-handoff.test.ts +0 -237
- package/core/__tests__/types/fs.test.ts +0 -125
- package/core/__tests__/utils/date-helper.test.ts +0 -449
- package/core/__tests__/utils/output.test.ts +0 -278
- package/core/__tests__/utils/preserve-sections.test.ts +0 -216
- package/core/__tests__/utils/project-commands.test.ts +0 -71
- package/core/__tests__/utils/retry.test.ts +0 -381
- package/core/__tests__/workflow/state-machine.test.ts +0 -216
- package/core/agentic/agent-router.ts +0 -150
- package/core/agentic/anti-hallucination.ts +0 -141
- package/core/agentic/chain-of-thought.ts +0 -234
- package/core/agentic/command-classifier.ts +0 -141
- package/core/agentic/command-context.ts +0 -168
- package/core/agentic/command-executor.ts +0 -471
- package/core/agentic/context-builder.ts +0 -285
- package/core/agentic/domain-classifier.ts +0 -525
- package/core/agentic/environment-block.ts +0 -102
- package/core/agentic/ground-truth.ts +0 -706
- package/core/agentic/index.ts +0 -193
- package/core/agentic/injection-validator.ts +0 -208
- package/core/agentic/loop-detector.ts +0 -451
- package/core/agentic/memory-system.ts +0 -1547
- package/core/agentic/orchestrator-executor.ts +0 -579
- package/core/agentic/plan-mode.ts +0 -525
- package/core/agentic/prompt-builder.ts +0 -1069
- package/core/agentic/response-validator.ts +0 -98
- package/core/agentic/services.ts +0 -167
- package/core/agentic/skill-loader.ts +0 -106
- package/core/agentic/smart-context.ts +0 -393
- package/core/agentic/tech-normalizer.ts +0 -167
- package/core/agentic/template-executor.ts +0 -272
- package/core/agentic/template-loader.ts +0 -109
- package/core/agentic/token-budget.ts +0 -226
- package/core/agentic/tool-registry.ts +0 -146
- package/core/agents/index.ts +0 -28
- package/core/agents/performance.ts +0 -429
- package/core/ai-tools/formatters.ts +0 -341
- package/core/ai-tools/generator.ts +0 -144
- package/core/ai-tools/index.ts +0 -15
- package/core/ai-tools/registry.ts +0 -201
- package/core/bus/bus.ts +0 -314
- package/core/bus/index.ts +0 -8
- package/core/cli/linear.ts +0 -500
- package/core/cli/lint-meta-commentary.ts +0 -177
- package/core/cli/start.ts +0 -386
- package/core/commands/analysis.ts +0 -1274
- package/core/commands/analytics.ts +0 -342
- package/core/commands/base.ts +0 -118
- package/core/commands/cleanup.ts +0 -157
- package/core/commands/command-data.ts +0 -463
- package/core/commands/commands.ts +0 -306
- package/core/commands/context.ts +0 -238
- package/core/commands/design.ts +0 -77
- package/core/commands/index.ts +0 -19
- package/core/commands/maintenance.ts +0 -77
- package/core/commands/performance.ts +0 -114
- package/core/commands/planning.ts +0 -662
- package/core/commands/register.ts +0 -127
- package/core/commands/registry.ts +0 -444
- package/core/commands/setup.ts +0 -280
- package/core/commands/shipping.ts +0 -267
- package/core/commands/snapshots.ts +0 -297
- package/core/commands/uninstall.ts +0 -542
- package/core/commands/velocity.ts +0 -149
- package/core/commands/workflow.ts +0 -505
- package/core/config/command-context.config.json +0 -66
- package/core/constants/index.ts +0 -379
- package/core/context/generator.ts +0 -368
- package/core/context-tools/files-tool.ts +0 -577
- package/core/context-tools/imports-tool.ts +0 -400
- package/core/context-tools/index.ts +0 -434
- package/core/context-tools/recent-tool.ts +0 -301
- package/core/context-tools/signatures-tool.ts +0 -495
- package/core/context-tools/summary-tool.ts +0 -301
- package/core/context-tools/token-counter.ts +0 -273
- package/core/context-tools/types.ts +0 -253
- package/core/domain/agent-generator.ts +0 -186
- package/core/domain/agent-loader.ts +0 -419
- package/core/domain/analyzer.ts +0 -387
- package/core/domain/architecture-generator.ts +0 -108
- package/core/domain/bm25.ts +0 -525
- package/core/domain/change-propagator.ts +0 -162
- package/core/domain/context-estimator.ts +0 -175
- package/core/domain/fibonacci.ts +0 -128
- package/core/domain/file-hasher.ts +0 -296
- package/core/domain/file-ranker.ts +0 -151
- package/core/domain/git-cochange.ts +0 -250
- package/core/domain/import-graph.ts +0 -315
- package/core/domain/snapshot-manager.ts +0 -415
- package/core/domain/task-stack.ts +0 -578
- package/core/domain/velocity.ts +0 -470
- package/core/errors.ts +0 -335
- package/core/events/events.ts +0 -85
- package/core/events/index.ts +0 -8
- package/core/index.ts +0 -481
- package/core/infrastructure/agent-detector.ts +0 -135
- package/core/infrastructure/ai-provider.ts +0 -578
- package/core/infrastructure/author-detector.ts +0 -133
- package/core/infrastructure/capability-installer.ts +0 -76
- package/core/infrastructure/claude-agent.ts +0 -297
- package/core/infrastructure/command-installer.ts +0 -752
- package/core/infrastructure/config-manager.ts +0 -364
- package/core/infrastructure/editors-config.ts +0 -172
- package/core/infrastructure/path-manager.ts +0 -571
- package/core/infrastructure/performance-tracker.ts +0 -326
- package/core/infrastructure/permission-manager.ts +0 -289
- package/core/infrastructure/setup.ts +0 -1061
- package/core/infrastructure/update-checker.ts +0 -246
- package/core/integrations/issue-tracker/enricher.ts +0 -271
- package/core/integrations/issue-tracker/index.ts +0 -8
- package/core/integrations/issue-tracker/manager.ts +0 -286
- package/core/integrations/issue-tracker/types.ts +0 -310
- package/core/integrations/jira/cache.ts +0 -57
- package/core/integrations/jira/client.ts +0 -688
- package/core/integrations/jira/index.ts +0 -23
- package/core/integrations/jira/service.ts +0 -244
- package/core/integrations/linear/cache.ts +0 -68
- package/core/integrations/linear/client.ts +0 -436
- package/core/integrations/linear/index.ts +0 -20
- package/core/integrations/linear/service.ts +0 -260
- package/core/integrations/linear/sync.ts +0 -314
- package/core/outcomes/analyzer.ts +0 -286
- package/core/outcomes/index.ts +0 -34
- package/core/outcomes/recorder.ts +0 -195
- package/core/plugin/builtin/webhook.ts +0 -148
- package/core/plugin/hooks.ts +0 -315
- package/core/plugin/index.ts +0 -50
- package/core/plugin/loader.ts +0 -354
- package/core/plugin/registry.ts +0 -326
- package/core/schemas/agents.ts +0 -27
- package/core/schemas/analysis.ts +0 -530
- package/core/schemas/classification.ts +0 -91
- package/core/schemas/command-context.ts +0 -29
- package/core/schemas/enriched-task.ts +0 -291
- package/core/schemas/ideas.ts +0 -114
- package/core/schemas/index.ts +0 -53
- package/core/schemas/issues.ts +0 -159
- package/core/schemas/llm-output.ts +0 -170
- package/core/schemas/metrics.ts +0 -143
- package/core/schemas/model.ts +0 -153
- package/core/schemas/outcomes.ts +0 -487
- package/core/schemas/performance.ts +0 -128
- package/core/schemas/permissions.ts +0 -180
- package/core/schemas/prd.ts +0 -450
- package/core/schemas/project.ts +0 -57
- package/core/schemas/roadmap.ts +0 -322
- package/core/schemas/schemas.ts +0 -38
- package/core/schemas/shipped.ts +0 -109
- package/core/schemas/state.ts +0 -284
- package/core/schemas/velocity.ts +0 -103
- package/core/server/index.ts +0 -21
- package/core/server/routes-extended.ts +0 -566
- package/core/server/routes.ts +0 -176
- package/core/server/server.ts +0 -149
- package/core/server/sse.ts +0 -192
- package/core/services/agent-generator.ts +0 -385
- package/core/services/agent-service.ts +0 -168
- package/core/services/breakdown-service.ts +0 -124
- package/core/services/context-generator.ts +0 -445
- package/core/services/context-selector.ts +0 -429
- package/core/services/dependency-validator.ts +0 -318
- package/core/services/diff-generator.ts +0 -313
- package/core/services/doctor-service.ts +0 -423
- package/core/services/file-categorizer.ts +0 -448
- package/core/services/file-scorer.ts +0 -270
- package/core/services/git-analyzer.ts +0 -293
- package/core/services/hierarchical-agent-resolver.ts +0 -236
- package/core/services/hooks-service.ts +0 -685
- package/core/services/index.ts +0 -46
- package/core/services/local-state-generator.ts +0 -158
- package/core/services/memory-service.ts +0 -181
- package/core/services/nested-context-resolver.ts +0 -842
- package/core/services/project-index.ts +0 -911
- package/core/services/project-service.ts +0 -155
- package/core/services/session-tracker.ts +0 -287
- package/core/services/skill-installer.ts +0 -447
- package/core/services/skill-lock.ts +0 -132
- package/core/services/skill-service.ts +0 -306
- package/core/services/stack-detector.ts +0 -229
- package/core/services/staleness-checker.ts +0 -327
- package/core/services/sync-service.ts +0 -1515
- package/core/services/sync-verifier.ts +0 -253
- package/core/services/watch-service.ts +0 -312
- package/core/session/compaction.ts +0 -248
- package/core/session/index.ts +0 -35
- package/core/session/log-migration.ts +0 -88
- package/core/session/metrics.ts +0 -323
- package/core/session/session-log-manager.ts +0 -307
- package/core/session/task-session-manager.ts +0 -404
- package/core/session/utils.ts +0 -51
- package/core/storage/analysis-storage.ts +0 -373
- package/core/storage/archive-storage.ts +0 -205
- package/core/storage/database.ts +0 -575
- package/core/storage/ideas-storage.ts +0 -298
- package/core/storage/index-storage.ts +0 -523
- package/core/storage/index.ts +0 -79
- package/core/storage/metrics-storage.ts +0 -321
- package/core/storage/migrate-json.ts +0 -720
- package/core/storage/queue-storage.ts +0 -336
- package/core/storage/safe-reader.ts +0 -105
- package/core/storage/shipped-storage.ts +0 -253
- package/core/storage/state-storage.ts +0 -1035
- package/core/storage/storage-manager.ts +0 -205
- package/core/storage/storage.ts +0 -177
- package/core/storage/velocity-storage.ts +0 -149
- package/core/sync/auth-config.ts +0 -138
- package/core/sync/index.ts +0 -31
- package/core/sync/oauth-handler.ts +0 -143
- package/core/sync/sync-client.ts +0 -251
- package/core/sync/sync-manager.ts +0 -327
- package/core/tsconfig.json +0 -22
- package/core/types/agentic.ts +0 -760
- package/core/types/agents.ts +0 -150
- package/core/types/bus.ts +0 -193
- package/core/types/citations.ts +0 -22
- package/core/types/commands.ts +0 -399
- package/core/types/config.ts +0 -92
- package/core/types/core.ts +0 -96
- package/core/types/diff.ts +0 -41
- package/core/types/domain.ts +0 -71
- package/core/types/errors.ts +0 -111
- package/core/types/events.ts +0 -42
- package/core/types/fs.ts +0 -72
- package/core/types/index.ts +0 -510
- package/core/types/infrastructure.ts +0 -210
- package/core/types/integrations.ts +0 -31
- package/core/types/jira.ts +0 -51
- package/core/types/logger.ts +0 -17
- package/core/types/memory.ts +0 -313
- package/core/types/outcomes.ts +0 -190
- package/core/types/output.ts +0 -47
- package/core/types/plugin.ts +0 -25
- package/core/types/project-sync.ts +0 -129
- package/core/types/provider.ts +0 -163
- package/core/types/server.ts +0 -71
- package/core/types/services.ts +0 -84
- package/core/types/session.ts +0 -135
- package/core/types/stack.ts +0 -19
- package/core/types/storage.ts +0 -318
- package/core/types/sync-verifier.ts +0 -33
- package/core/types/sync.ts +0 -121
- package/core/types/task.ts +0 -72
- package/core/types/template.ts +0 -24
- package/core/types/utils.ts +0 -92
- package/core/types/workflow.ts +0 -23
- package/core/utils/agent-stream.ts +0 -140
- package/core/utils/animations.ts +0 -251
- package/core/utils/branding.ts +0 -88
- package/core/utils/cache.ts +0 -187
- package/core/utils/citations.ts +0 -39
- package/core/utils/collection-filters.ts +0 -209
- package/core/utils/date-helper.ts +0 -176
- package/core/utils/error-messages.ts +0 -38
- package/core/utils/file-helper.ts +0 -277
- package/core/utils/fs-helpers.ts +0 -14
- package/core/utils/help.ts +0 -314
- package/core/utils/jsonl-helper.ts +0 -290
- package/core/utils/keychain.ts +0 -127
- package/core/utils/logger.ts +0 -77
- package/core/utils/markdown-builder.ts +0 -280
- package/core/utils/next-steps.ts +0 -95
- package/core/utils/output.ts +0 -403
- package/core/utils/preserve-sections.ts +0 -218
- package/core/utils/project-commands.ts +0 -126
- package/core/utils/project-credentials.ts +0 -143
- package/core/utils/provider-cache.ts +0 -49
- package/core/utils/retry.ts +0 -318
- package/core/utils/runtime.ts +0 -108
- package/core/utils/session-helper.ts +0 -278
- package/core/utils/subtask-table.ts +0 -227
- package/core/utils/version.ts +0 -128
- package/core/wizard/index.ts +0 -13
- package/core/wizard/onboarding.ts +0 -633
- package/core/workflow/index.ts +0 -7
- package/core/workflow/state-machine.ts +0 -198
- package/core/workflow/workflow-preferences.ts +0 -294
- package/dist/core/infrastructure/command-installer.js +0 -1141
- package/dist/core/infrastructure/editors-config.js +0 -177
- package/dist/core/infrastructure/setup.js +0 -2244
- package/dist/core/utils/version.js +0 -141
- package/templates/agentic/agent-routing.md +0 -45
- package/templates/agentic/agents/uxui.md +0 -63
- package/templates/agentic/checklist-routing.md +0 -98
- package/templates/agentic/orchestrator.md +0 -68
- package/templates/agentic/task-fragmentation.md +0 -89
- package/templates/agents/AGENTS.md +0 -68
- package/templates/analysis/analyze.md +0 -84
- package/templates/analysis/patterns.md +0 -60
- package/templates/antigravity/SKILL.md +0 -39
- package/templates/architect/discovery.md +0 -67
- package/templates/architect/phases.md +0 -59
- package/templates/checklists/architecture.md +0 -28
- package/templates/checklists/code-quality.md +0 -28
- package/templates/checklists/data.md +0 -33
- package/templates/checklists/documentation.md +0 -33
- package/templates/checklists/infrastructure.md +0 -33
- package/templates/checklists/performance.md +0 -33
- package/templates/checklists/security.md +0 -33
- package/templates/checklists/testing.md +0 -33
- package/templates/checklists/ux-ui.md +0 -37
- package/templates/commands/analyze.md +0 -56
- package/templates/commands/auth.md +0 -234
- package/templates/commands/bug.md +0 -163
- package/templates/commands/cleanup.md +0 -19
- package/templates/commands/dash.md +0 -99
- package/templates/commands/design.md +0 -15
- package/templates/commands/done.md +0 -291
- package/templates/commands/enrich.md +0 -174
- package/templates/commands/git.md +0 -295
- package/templates/commands/history.md +0 -389
- package/templates/commands/idea.md +0 -88
- package/templates/commands/impact.md +0 -864
- package/templates/commands/init.md +0 -54
- package/templates/commands/jira.md +0 -278
- package/templates/commands/linear.md +0 -288
- package/templates/commands/merge.md +0 -206
- package/templates/commands/next.md +0 -80
- package/templates/commands/p.md +0 -67
- package/templates/commands/p.toml +0 -37
- package/templates/commands/pause.md +0 -136
- package/templates/commands/plan.md +0 -696
- package/templates/commands/prd.md +0 -356
- package/templates/commands/resume.md +0 -171
- package/templates/commands/review.md +0 -276
- package/templates/commands/serve.md +0 -118
- package/templates/commands/setup.md +0 -91
- package/templates/commands/ship.md +0 -475
- package/templates/commands/skill.md +0 -259
- package/templates/commands/spec.md +0 -218
- package/templates/commands/status.md +0 -207
- package/templates/commands/sync.md +0 -104
- package/templates/commands/task.md +0 -312
- package/templates/commands/test.md +0 -93
- package/templates/commands/update.md +0 -63
- package/templates/commands/verify.md +0 -204
- package/templates/commands/workflow.md +0 -150
- package/templates/config/skill-mappings.json +0 -82
- package/templates/context/dashboard.md +0 -256
- package/templates/context/roadmap.md +0 -221
- package/templates/cursor/commands/bug.md +0 -8
- package/templates/cursor/commands/done.md +0 -4
- package/templates/cursor/commands/pause.md +0 -6
- package/templates/cursor/commands/resume.md +0 -4
- package/templates/cursor/commands/ship.md +0 -8
- package/templates/cursor/commands/sync.md +0 -4
- package/templates/cursor/commands/task.md +0 -8
- package/templates/cursor/p.md +0 -29
- package/templates/cursor/router.mdc +0 -28
- package/templates/design/api.md +0 -95
- package/templates/design/architecture.md +0 -77
- package/templates/design/component.md +0 -89
- package/templates/design/database.md +0 -78
- package/templates/design/flow.md +0 -94
- package/templates/global/ANTIGRAVITY.md +0 -254
- package/templates/global/CLAUDE.md +0 -497
- package/templates/global/CURSOR.mdc +0 -266
- package/templates/global/GEMINI.md +0 -293
- package/templates/global/STORAGE-SPEC.md +0 -391
- package/templates/global/WINDSURF.md +0 -266
- package/templates/global/modules/CLAUDE-commands.md +0 -70
- package/templates/global/modules/CLAUDE-core.md +0 -105
- package/templates/global/modules/CLAUDE-git.md +0 -50
- package/templates/global/modules/CLAUDE-intelligence.md +0 -92
- package/templates/global/modules/CLAUDE-storage.md +0 -50
- package/templates/global/modules/module-config.json +0 -36
- package/templates/mcp-config.json +0 -19
- package/templates/permissions/default.jsonc +0 -60
- package/templates/permissions/permissive.jsonc +0 -49
- package/templates/permissions/strict.jsonc +0 -58
- package/templates/planning-methodology.md +0 -195
- package/templates/skills/code-review.md +0 -47
- package/templates/skills/debug.md +0 -61
- package/templates/skills/refactor.md +0 -47
- package/templates/subagents/agent-base.md +0 -20
- package/templates/subagents/domain/backend.md +0 -109
- package/templates/subagents/domain/database.md +0 -121
- package/templates/subagents/domain/devops.md +0 -152
- package/templates/subagents/domain/frontend.md +0 -103
- package/templates/subagents/domain/testing.md +0 -169
- package/templates/subagents/pm-expert.md +0 -366
- package/templates/subagents/workflow/chief-architect.md +0 -657
- package/templates/subagents/workflow/prjct-planner.md +0 -159
- package/templates/subagents/workflow/prjct-shipper.md +0 -188
- package/templates/subagents/workflow/prjct-workflow.md +0 -98
- package/templates/tools/bash.txt +0 -22
- package/templates/tools/edit.txt +0 -18
- package/templates/tools/glob.txt +0 -19
- package/templates/tools/grep.txt +0 -21
- package/templates/tools/read.txt +0 -14
- package/templates/tools/task.txt +0 -20
- package/templates/tools/webfetch.txt +0 -16
- package/templates/tools/websearch.txt +0 -18
- package/templates/tools/write.txt +0 -17
- package/templates/windsurf/router.md +0 -28
- package/templates/windsurf/workflows/bug.md +0 -8
- package/templates/windsurf/workflows/done.md +0 -4
- package/templates/windsurf/workflows/pause.md +0 -4
- package/templates/windsurf/workflows/resume.md +0 -4
- package/templates/windsurf/workflows/ship.md +0 -8
- package/templates/windsurf/workflows/sync.md +0 -4
- package/templates/windsurf/workflows/task.md +0 -8
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OAuth Handler - Manages authentication flow for CLI
|
|
3
|
-
*
|
|
4
|
-
* Two modes:
|
|
5
|
-
* 1. Simple: User copies API key from web dashboard
|
|
6
|
-
* 2. Browser (future): Full OAuth device flow
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { AuthResult } from '../types'
|
|
10
|
-
import { authConfig } from './auth-config'
|
|
11
|
-
import { syncClient } from './sync-client'
|
|
12
|
-
|
|
13
|
-
class OAuthHandler {
|
|
14
|
-
/**
|
|
15
|
-
* Start authentication flow
|
|
16
|
-
* Opens browser to dashboard where user can create/copy API key
|
|
17
|
-
*/
|
|
18
|
-
async startAuthFlow(): Promise<{ url: string }> {
|
|
19
|
-
const apiUrl = await authConfig.getApiUrl()
|
|
20
|
-
// Dashboard URL where user can get their API key
|
|
21
|
-
const dashboardUrl = apiUrl.replace('api.', 'app.')
|
|
22
|
-
const authUrl = `${dashboardUrl}/settings/api-keys`
|
|
23
|
-
|
|
24
|
-
return { url: authUrl }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Save API key after user provides it
|
|
29
|
-
*/
|
|
30
|
-
async saveApiKey(apiKey: string): Promise<AuthResult> {
|
|
31
|
-
// Validate format
|
|
32
|
-
if (!apiKey.startsWith('prjct_')) {
|
|
33
|
-
return {
|
|
34
|
-
success: false,
|
|
35
|
-
error: 'Invalid API key format. Keys start with "prjct_"',
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Save temporarily to test connection
|
|
40
|
-
await authConfig.write({ apiKey })
|
|
41
|
-
|
|
42
|
-
// Test the key by making a request
|
|
43
|
-
const isValid = await syncClient.testConnection()
|
|
44
|
-
|
|
45
|
-
if (!isValid) {
|
|
46
|
-
// Clear the invalid key
|
|
47
|
-
await authConfig.clearAuth()
|
|
48
|
-
return {
|
|
49
|
-
success: false,
|
|
50
|
-
error: 'API key is invalid or expired',
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Key is valid - fetch user info
|
|
55
|
-
try {
|
|
56
|
-
const userInfo = await this.fetchUserInfo(apiKey)
|
|
57
|
-
|
|
58
|
-
await authConfig.saveAuth(apiKey, userInfo.id, userInfo.email)
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
success: true,
|
|
62
|
-
email: userInfo.email,
|
|
63
|
-
}
|
|
64
|
-
} catch (_error) {
|
|
65
|
-
// Key works but couldn't fetch user info - still save it
|
|
66
|
-
await authConfig.write({ apiKey })
|
|
67
|
-
return {
|
|
68
|
-
success: true,
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Fetch user info using the API key
|
|
75
|
-
*/
|
|
76
|
-
private async fetchUserInfo(
|
|
77
|
-
apiKey: string
|
|
78
|
-
): Promise<{ id: string; email: string; name: string }> {
|
|
79
|
-
const apiUrl = await authConfig.getApiUrl()
|
|
80
|
-
|
|
81
|
-
const response = await fetch(`${apiUrl}/auth/me`, {
|
|
82
|
-
headers: {
|
|
83
|
-
'X-Api-Key': apiKey,
|
|
84
|
-
},
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
if (!response.ok) {
|
|
88
|
-
throw new Error('Failed to fetch user info')
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const data = (await response.json()) as {
|
|
92
|
-
user: { id: string; email: string; name: string }
|
|
93
|
-
}
|
|
94
|
-
return data.user
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Check if currently authenticated
|
|
99
|
-
*/
|
|
100
|
-
async isAuthenticated(): Promise<boolean> {
|
|
101
|
-
return await authConfig.hasAuth()
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Get current auth status
|
|
106
|
-
*/
|
|
107
|
-
async getStatus(): Promise<{
|
|
108
|
-
authenticated: boolean
|
|
109
|
-
email: string | null
|
|
110
|
-
apiKeyPrefix: string | null
|
|
111
|
-
}> {
|
|
112
|
-
return await authConfig.getStatus()
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Logout - clear all auth data
|
|
117
|
-
*/
|
|
118
|
-
async logout(): Promise<void> {
|
|
119
|
-
await authConfig.clearAuth()
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Open URL in default browser
|
|
124
|
-
*/
|
|
125
|
-
async openBrowser(url: string): Promise<void> {
|
|
126
|
-
const { exec } = await import('node:child_process')
|
|
127
|
-
const { promisify } = await import('node:util')
|
|
128
|
-
const execAsync = promisify(exec)
|
|
129
|
-
|
|
130
|
-
const platform = process.platform
|
|
131
|
-
const command =
|
|
132
|
-
platform === 'darwin'
|
|
133
|
-
? `open "${url}"`
|
|
134
|
-
: platform === 'win32'
|
|
135
|
-
? `start "${url}"`
|
|
136
|
-
: `xdg-open "${url}"`
|
|
137
|
-
|
|
138
|
-
await execAsync(command)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export const oauthHandler = new OAuthHandler()
|
|
143
|
-
export default oauthHandler
|
package/core/sync/sync-client.ts
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sync Client - HTTP client for prjct API
|
|
3
|
-
*
|
|
4
|
-
* Handles communication with the backend API for push/pull operations.
|
|
5
|
-
* Uses native fetch API (available in Node 18+ and Bun).
|
|
6
|
-
*
|
|
7
|
-
* PRJ-111: Includes configurable timeout support via AbortController.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { getTimeout } from '../constants'
|
|
11
|
-
import type { SyncEvent } from '../events'
|
|
12
|
-
import type { SyncBatchResult, SyncClientError, SyncPullResult, SyncStatus } from '../types'
|
|
13
|
-
import authConfig from './auth-config'
|
|
14
|
-
|
|
15
|
-
// ============================================
|
|
16
|
-
// Sync Client
|
|
17
|
-
// ============================================
|
|
18
|
-
|
|
19
|
-
class SyncClient {
|
|
20
|
-
private retryConfig = {
|
|
21
|
-
maxRetries: 3,
|
|
22
|
-
baseDelayMs: 1000,
|
|
23
|
-
maxDelayMs: 30000,
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Push local events to the server
|
|
28
|
-
*/
|
|
29
|
-
async pushEvents(projectId: string, events: SyncEvent[]): Promise<SyncBatchResult> {
|
|
30
|
-
const { apiUrl, apiKey } = await this.getAuthHeaders()
|
|
31
|
-
|
|
32
|
-
if (!apiKey) {
|
|
33
|
-
throw this.createError('AUTH_REQUIRED', 'No API key configured')
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const response = await this.fetchWithRetry(`${apiUrl}/sync/batch`, {
|
|
37
|
-
method: 'POST',
|
|
38
|
-
headers: {
|
|
39
|
-
'Content-Type': 'application/json',
|
|
40
|
-
'X-Api-Key': apiKey,
|
|
41
|
-
},
|
|
42
|
-
body: JSON.stringify({
|
|
43
|
-
projectId,
|
|
44
|
-
events: events.map((e) => ({
|
|
45
|
-
type: e.type,
|
|
46
|
-
path: e.path,
|
|
47
|
-
data: e.data,
|
|
48
|
-
timestamp: e.timestamp,
|
|
49
|
-
})),
|
|
50
|
-
}),
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
if (!response.ok) {
|
|
54
|
-
const error = await this.parseErrorResponse(response)
|
|
55
|
-
throw error
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return (await response.json()) as SyncBatchResult
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Pull events from the server since a timestamp
|
|
63
|
-
*/
|
|
64
|
-
async pullEvents(projectId: string, since?: string): Promise<SyncPullResult> {
|
|
65
|
-
const { apiUrl, apiKey } = await this.getAuthHeaders()
|
|
66
|
-
|
|
67
|
-
if (!apiKey) {
|
|
68
|
-
throw this.createError('AUTH_REQUIRED', 'No API key configured')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const response = await this.fetchWithRetry(`${apiUrl}/sync/pull`, {
|
|
72
|
-
method: 'POST',
|
|
73
|
-
headers: {
|
|
74
|
-
'Content-Type': 'application/json',
|
|
75
|
-
'X-Api-Key': apiKey,
|
|
76
|
-
},
|
|
77
|
-
body: JSON.stringify({
|
|
78
|
-
projectId,
|
|
79
|
-
since,
|
|
80
|
-
}),
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
if (!response.ok) {
|
|
84
|
-
const error = await this.parseErrorResponse(response)
|
|
85
|
-
throw error
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return (await response.json()) as SyncPullResult
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Get sync status for a project
|
|
93
|
-
*/
|
|
94
|
-
async getStatus(projectId: string): Promise<SyncStatus> {
|
|
95
|
-
const { apiUrl, apiKey } = await this.getAuthHeaders()
|
|
96
|
-
|
|
97
|
-
if (!apiKey) {
|
|
98
|
-
throw this.createError('AUTH_REQUIRED', 'No API key configured')
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const response = await this.fetchWithRetry(`${apiUrl}/sync/status/${projectId}`, {
|
|
102
|
-
method: 'GET',
|
|
103
|
-
headers: {
|
|
104
|
-
'X-Api-Key': apiKey,
|
|
105
|
-
},
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
if (!response.ok) {
|
|
109
|
-
const error = await this.parseErrorResponse(response)
|
|
110
|
-
throw error
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return (await response.json()) as SyncStatus
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Test connection to the API
|
|
118
|
-
*/
|
|
119
|
-
async testConnection(): Promise<boolean> {
|
|
120
|
-
// PRJ-111: Add timeout to connection test
|
|
121
|
-
const controller = new AbortController()
|
|
122
|
-
const timeoutId = setTimeout(() => controller.abort(), getTimeout('API_REQUEST'))
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
const { apiUrl, apiKey } = await this.getAuthHeaders()
|
|
126
|
-
|
|
127
|
-
if (!apiKey) {
|
|
128
|
-
clearTimeout(timeoutId)
|
|
129
|
-
return false
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const response = await fetch(`${apiUrl}/health`, {
|
|
133
|
-
method: 'GET',
|
|
134
|
-
headers: {
|
|
135
|
-
'X-Api-Key': apiKey,
|
|
136
|
-
},
|
|
137
|
-
signal: controller.signal,
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
clearTimeout(timeoutId)
|
|
141
|
-
return response.ok
|
|
142
|
-
} catch (_error) {
|
|
143
|
-
// Network error, timeout, or other issue - expected
|
|
144
|
-
clearTimeout(timeoutId)
|
|
145
|
-
return false
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Check if we have valid authentication
|
|
151
|
-
*/
|
|
152
|
-
async hasAuth(): Promise<boolean> {
|
|
153
|
-
return await authConfig.hasAuth()
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// ============================================
|
|
157
|
-
// Private helpers
|
|
158
|
-
// ============================================
|
|
159
|
-
|
|
160
|
-
private async getAuthHeaders(): Promise<{ apiUrl: string; apiKey: string | null }> {
|
|
161
|
-
const [apiUrl, apiKey] = await Promise.all([authConfig.getApiUrl(), authConfig.getApiKey()])
|
|
162
|
-
return { apiUrl, apiKey }
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private async fetchWithRetry(
|
|
166
|
-
url: string,
|
|
167
|
-
options: RequestInit,
|
|
168
|
-
retryCount = 0
|
|
169
|
-
): Promise<Response> {
|
|
170
|
-
// PRJ-111: Add AbortController-based timeout (default: 30s, configurable via PRJCT_TIMEOUT_API_REQUEST)
|
|
171
|
-
const controller = new AbortController()
|
|
172
|
-
const timeoutId = setTimeout(() => controller.abort(), getTimeout('API_REQUEST'))
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
const response = await fetch(url, {
|
|
176
|
-
...options,
|
|
177
|
-
signal: controller.signal,
|
|
178
|
-
})
|
|
179
|
-
|
|
180
|
-
clearTimeout(timeoutId)
|
|
181
|
-
|
|
182
|
-
// Retry on server errors (5xx) but not client errors (4xx)
|
|
183
|
-
if (response.status >= 500 && retryCount < this.retryConfig.maxRetries) {
|
|
184
|
-
const delay = Math.min(
|
|
185
|
-
this.retryConfig.baseDelayMs * 2 ** retryCount,
|
|
186
|
-
this.retryConfig.maxDelayMs
|
|
187
|
-
)
|
|
188
|
-
await this.sleep(delay)
|
|
189
|
-
return this.fetchWithRetry(url, options, retryCount + 1)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
return response
|
|
193
|
-
} catch (error) {
|
|
194
|
-
clearTimeout(timeoutId)
|
|
195
|
-
|
|
196
|
-
// Check if this is a timeout (AbortError)
|
|
197
|
-
if (error instanceof Error && error.name === 'AbortError') {
|
|
198
|
-
throw this.createError(
|
|
199
|
-
'NETWORK_ERROR',
|
|
200
|
-
`Request timed out. Try increasing PRJCT_TIMEOUT_API_REQUEST (current: ${getTimeout('API_REQUEST')}ms)`
|
|
201
|
-
)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Retry on network errors
|
|
205
|
-
if (retryCount < this.retryConfig.maxRetries) {
|
|
206
|
-
const delay = Math.min(
|
|
207
|
-
this.retryConfig.baseDelayMs * 2 ** retryCount,
|
|
208
|
-
this.retryConfig.maxDelayMs
|
|
209
|
-
)
|
|
210
|
-
await this.sleep(delay)
|
|
211
|
-
return this.fetchWithRetry(url, options, retryCount + 1)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
throw this.createError(
|
|
215
|
-
'NETWORK_ERROR',
|
|
216
|
-
error instanceof Error ? error.message : 'Network request failed'
|
|
217
|
-
)
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
private async parseErrorResponse(response: Response): Promise<SyncClientError> {
|
|
222
|
-
try {
|
|
223
|
-
const body = (await response.json()) as { message?: string; error?: string }
|
|
224
|
-
const message = body.message || body.error || `HTTP ${response.status}`
|
|
225
|
-
|
|
226
|
-
if (response.status === 401 || response.status === 403) {
|
|
227
|
-
return this.createError('AUTH_REQUIRED', message, response.status)
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return this.createError('API_ERROR', message, response.status)
|
|
231
|
-
} catch (_error) {
|
|
232
|
-
// JSON parse error - use generic message (expected for non-JSON responses)
|
|
233
|
-
return this.createError('API_ERROR', `HTTP ${response.status}`, response.status)
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
private createError(
|
|
238
|
-
code: SyncClientError['code'],
|
|
239
|
-
message: string,
|
|
240
|
-
status?: number
|
|
241
|
-
): SyncClientError {
|
|
242
|
-
return { code, message, status }
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
private sleep(ms: number): Promise<void> {
|
|
246
|
-
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
export const syncClient = new SyncClient()
|
|
251
|
-
export default syncClient
|
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sync Manager - Orchestrates push/pull operations
|
|
3
|
-
*
|
|
4
|
-
* Main entry point for sync operations.
|
|
5
|
-
* Handles the coordination between local storage (EventBus) and remote API (SyncClient).
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import eventBus from '../events'
|
|
9
|
-
import type { IdeaPriority } from '../schemas/ideas'
|
|
10
|
-
import type { Priority, TaskSection, TaskType } from '../schemas/state'
|
|
11
|
-
import { ideasStorage } from '../storage/ideas-storage'
|
|
12
|
-
import { queueStorage } from '../storage/queue-storage'
|
|
13
|
-
import { shippedStorage } from '../storage/shipped-storage'
|
|
14
|
-
import { stateStorage } from '../storage/state-storage'
|
|
15
|
-
import type {
|
|
16
|
-
PullResult,
|
|
17
|
-
PushResult,
|
|
18
|
-
SyncBatchResult,
|
|
19
|
-
SyncPullResult,
|
|
20
|
-
SyncManagerResult as SyncResult,
|
|
21
|
-
SyncStatus,
|
|
22
|
-
} from '../types'
|
|
23
|
-
import authConfig from './auth-config'
|
|
24
|
-
import { syncClient } from './sync-client'
|
|
25
|
-
|
|
26
|
-
// ============================================
|
|
27
|
-
// Sync Manager
|
|
28
|
-
// ============================================
|
|
29
|
-
|
|
30
|
-
class SyncManager {
|
|
31
|
-
/**
|
|
32
|
-
* Check if user is authenticated
|
|
33
|
-
*/
|
|
34
|
-
async hasAuth(): Promise<boolean> {
|
|
35
|
-
return await authConfig.hasAuth()
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Get sync status from API
|
|
40
|
-
*/
|
|
41
|
-
async getStatus(projectId: string): Promise<SyncStatus | null> {
|
|
42
|
-
if (!(await this.hasAuth())) {
|
|
43
|
-
return null
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
return await syncClient.getStatus(projectId)
|
|
48
|
-
} catch (_error) {
|
|
49
|
-
return null
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Full sync: push local changes, then pull remote changes
|
|
55
|
-
*/
|
|
56
|
-
async sync(projectId: string): Promise<SyncResult> {
|
|
57
|
-
// Check auth first
|
|
58
|
-
if (!(await this.hasAuth())) {
|
|
59
|
-
return { success: true, skipped: true, reason: 'no_auth' }
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const result: SyncResult = { success: true, skipped: false }
|
|
63
|
-
|
|
64
|
-
// Push first
|
|
65
|
-
const pushResult = await this.push(projectId)
|
|
66
|
-
if (pushResult.success && !pushResult.skipped) {
|
|
67
|
-
result.pushed = {
|
|
68
|
-
count: pushResult.count || 0,
|
|
69
|
-
syncedAt: pushResult.syncedAt || new Date().toISOString(),
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Then pull
|
|
74
|
-
const pullResult = await this.pull(projectId)
|
|
75
|
-
if (pullResult.success && !pullResult.skipped) {
|
|
76
|
-
result.pulled = {
|
|
77
|
-
count: pullResult.count || 0,
|
|
78
|
-
syncedAt: pullResult.syncedAt || new Date().toISOString(),
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Determine overall success
|
|
83
|
-
if (!pushResult.success || !pullResult.success) {
|
|
84
|
-
result.success = false
|
|
85
|
-
result.error = pushResult.error || pullResult.error
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return result
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Push local pending events to the server
|
|
93
|
-
*/
|
|
94
|
-
async push(projectId: string): Promise<PushResult> {
|
|
95
|
-
// Check auth first
|
|
96
|
-
if (!(await this.hasAuth())) {
|
|
97
|
-
return { success: true, skipped: true, reason: 'no_auth' }
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
try {
|
|
101
|
-
// Get pending events
|
|
102
|
-
const pending = await eventBus.getPending(projectId)
|
|
103
|
-
|
|
104
|
-
if (pending.length === 0) {
|
|
105
|
-
return { success: true, skipped: true, reason: 'no_pending' }
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Push to server
|
|
109
|
-
const result: SyncBatchResult = await syncClient.pushEvents(projectId, pending)
|
|
110
|
-
|
|
111
|
-
if (result.success) {
|
|
112
|
-
// Clear pending events on success
|
|
113
|
-
await eventBus.clearPending(projectId)
|
|
114
|
-
await eventBus.updateLastSync(projectId)
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
success: true,
|
|
118
|
-
skipped: false,
|
|
119
|
-
count: result.processed,
|
|
120
|
-
syncedAt: result.syncedAt,
|
|
121
|
-
}
|
|
122
|
-
} else {
|
|
123
|
-
// Partial success - some events failed
|
|
124
|
-
const successCount = result.processed
|
|
125
|
-
const errorCount = result.errors.length
|
|
126
|
-
const errorMessages = result.errors.map((e) => e.error).join(', ')
|
|
127
|
-
|
|
128
|
-
return {
|
|
129
|
-
success: false,
|
|
130
|
-
skipped: false,
|
|
131
|
-
count: successCount,
|
|
132
|
-
syncedAt: result.syncedAt,
|
|
133
|
-
error: `${errorCount} events failed: ${errorMessages}`,
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
} catch (error) {
|
|
137
|
-
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
138
|
-
return {
|
|
139
|
-
success: false,
|
|
140
|
-
skipped: false,
|
|
141
|
-
reason: 'error',
|
|
142
|
-
error: message,
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Pull remote changes from the server
|
|
149
|
-
*/
|
|
150
|
-
async pull(projectId: string): Promise<PullResult> {
|
|
151
|
-
// Check auth first
|
|
152
|
-
if (!(await this.hasAuth())) {
|
|
153
|
-
return { success: true, skipped: true, reason: 'no_auth' }
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
// Get last sync timestamp
|
|
158
|
-
const lastSync = await eventBus.getLastSync(projectId)
|
|
159
|
-
const since = lastSync?.timestamp
|
|
160
|
-
|
|
161
|
-
// Pull from server
|
|
162
|
-
const result: SyncPullResult = await syncClient.pullEvents(projectId, since)
|
|
163
|
-
|
|
164
|
-
if (result.events.length === 0) {
|
|
165
|
-
return {
|
|
166
|
-
success: true,
|
|
167
|
-
skipped: false,
|
|
168
|
-
count: 0,
|
|
169
|
-
applied: 0,
|
|
170
|
-
syncedAt: result.syncedAt,
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Apply pulled events to local storage
|
|
175
|
-
const applied = await this.applyPulledEvents(projectId, result.events)
|
|
176
|
-
|
|
177
|
-
// Update last sync timestamp
|
|
178
|
-
await eventBus.updateLastSync(projectId)
|
|
179
|
-
|
|
180
|
-
return {
|
|
181
|
-
success: true,
|
|
182
|
-
skipped: false,
|
|
183
|
-
count: result.events.length,
|
|
184
|
-
applied,
|
|
185
|
-
syncedAt: result.syncedAt,
|
|
186
|
-
}
|
|
187
|
-
} catch (error) {
|
|
188
|
-
const message = error instanceof Error ? error.message : 'Unknown error'
|
|
189
|
-
return {
|
|
190
|
-
success: false,
|
|
191
|
-
skipped: false,
|
|
192
|
-
reason: 'error',
|
|
193
|
-
error: message,
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Apply pulled events to local storage
|
|
200
|
-
* Returns number of events successfully applied
|
|
201
|
-
*/
|
|
202
|
-
async applyPulledEvents(
|
|
203
|
-
projectId: string,
|
|
204
|
-
events: Array<{ type: string; path: string[]; data: unknown; timestamp: string }>
|
|
205
|
-
): Promise<number> {
|
|
206
|
-
let applied = 0
|
|
207
|
-
|
|
208
|
-
for (const event of events) {
|
|
209
|
-
try {
|
|
210
|
-
await this.applyEvent(projectId, event)
|
|
211
|
-
applied++
|
|
212
|
-
} catch (error) {
|
|
213
|
-
// Log but continue with other events
|
|
214
|
-
console.error(`Failed to apply event ${event.type}:`, error)
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return applied
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Apply a single event to local storage
|
|
223
|
-
*/
|
|
224
|
-
private async applyEvent(
|
|
225
|
-
projectId: string,
|
|
226
|
-
event: { type: string; path: string[]; data: unknown; timestamp: string }
|
|
227
|
-
): Promise<void> {
|
|
228
|
-
const [entity, action] = event.type.split('.') as [string, string]
|
|
229
|
-
const data = event.data as Record<string, unknown>
|
|
230
|
-
|
|
231
|
-
switch (entity) {
|
|
232
|
-
case 'task':
|
|
233
|
-
await this.applyTaskEvent(projectId, action, data)
|
|
234
|
-
break
|
|
235
|
-
case 'idea':
|
|
236
|
-
await this.applyIdeaEvent(projectId, action, data)
|
|
237
|
-
break
|
|
238
|
-
case 'shipped':
|
|
239
|
-
await this.applyShippedEvent(projectId, action, data)
|
|
240
|
-
break
|
|
241
|
-
// Add more entity handlers as needed
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
private async applyTaskEvent(
|
|
246
|
-
projectId: string,
|
|
247
|
-
action: string,
|
|
248
|
-
data: Record<string, unknown>
|
|
249
|
-
): Promise<void> {
|
|
250
|
-
switch (action) {
|
|
251
|
-
case 'started':
|
|
252
|
-
// Update state if this is a newer task
|
|
253
|
-
await stateStorage.update(projectId, (state) => {
|
|
254
|
-
if (!state.currentTask || (data.id as string) !== state.currentTask.id) {
|
|
255
|
-
return {
|
|
256
|
-
...state,
|
|
257
|
-
currentTask: {
|
|
258
|
-
id: data.id as string,
|
|
259
|
-
description: data.description as string,
|
|
260
|
-
startedAt: data.startedAt as string,
|
|
261
|
-
sessionId: data.sessionId as string,
|
|
262
|
-
},
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
return state
|
|
266
|
-
})
|
|
267
|
-
break
|
|
268
|
-
case 'completed':
|
|
269
|
-
// Clear current task if it matches
|
|
270
|
-
await stateStorage.update(projectId, (state) => {
|
|
271
|
-
if (state.currentTask?.id === data.id) {
|
|
272
|
-
return { ...state, currentTask: null }
|
|
273
|
-
}
|
|
274
|
-
return state
|
|
275
|
-
})
|
|
276
|
-
break
|
|
277
|
-
case 'created':
|
|
278
|
-
// Add to queue
|
|
279
|
-
await queueStorage.addTask(projectId, {
|
|
280
|
-
description: data.description as string,
|
|
281
|
-
priority: (data.priority as Priority) || 'medium',
|
|
282
|
-
type: (data.type as TaskType) || 'feature',
|
|
283
|
-
section: 'backlog' as TaskSection,
|
|
284
|
-
})
|
|
285
|
-
break
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
private async applyIdeaEvent(
|
|
290
|
-
projectId: string,
|
|
291
|
-
action: string,
|
|
292
|
-
data: Record<string, unknown>
|
|
293
|
-
): Promise<void> {
|
|
294
|
-
switch (action) {
|
|
295
|
-
case 'created':
|
|
296
|
-
await ideasStorage.addIdea(projectId, (data.title as string) || (data.text as string), {
|
|
297
|
-
priority: (data.priority as IdeaPriority) || 'medium',
|
|
298
|
-
})
|
|
299
|
-
break
|
|
300
|
-
case 'archived':
|
|
301
|
-
await ideasStorage.update(projectId, (ideas) => ({
|
|
302
|
-
...ideas,
|
|
303
|
-
ideas: ideas.ideas.map((idea) =>
|
|
304
|
-
idea.id === data.id ? { ...idea, status: 'archived' as const } : idea
|
|
305
|
-
),
|
|
306
|
-
}))
|
|
307
|
-
break
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
private async applyShippedEvent(
|
|
312
|
-
projectId: string,
|
|
313
|
-
action: string,
|
|
314
|
-
data: Record<string, unknown>
|
|
315
|
-
): Promise<void> {
|
|
316
|
-
if (action === 'created') {
|
|
317
|
-
await shippedStorage.addShipped(projectId, {
|
|
318
|
-
name: (data.name as string) || (data.title as string),
|
|
319
|
-
version: data.version as string,
|
|
320
|
-
description: data.description as string,
|
|
321
|
-
})
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
export const syncManager = new SyncManager()
|
|
327
|
-
export default syncManager
|