shipwright-cli 3.2.0 → 3.3.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/.claude/agents/code-reviewer.md +2 -0
- package/.claude/agents/devops-engineer.md +2 -0
- package/.claude/agents/doc-fleet-agent.md +2 -0
- package/.claude/agents/pipeline-agent.md +2 -0
- package/.claude/agents/shell-script-specialist.md +2 -0
- package/.claude/agents/test-specialist.md +2 -0
- package/.claude/hooks/agent-crash-capture.sh +32 -0
- package/.claude/hooks/post-tool-use.sh +3 -2
- package/.claude/hooks/pre-tool-use.sh +35 -3
- package/README.md +4 -4
- package/claude-code/hooks/config-change.sh +18 -0
- package/claude-code/hooks/instructions-reloaded.sh +7 -0
- package/claude-code/hooks/worktree-create.sh +25 -0
- package/claude-code/hooks/worktree-remove.sh +20 -0
- package/config/code-constitution.json +130 -0
- package/dashboard/middleware/auth.ts +134 -0
- package/dashboard/middleware/constants.ts +21 -0
- package/dashboard/public/index.html +2 -6
- package/dashboard/public/styles.css +100 -97
- package/dashboard/routes/auth.ts +38 -0
- package/dashboard/server.ts +66 -25
- package/dashboard/services/config.ts +26 -0
- package/dashboard/services/db.ts +118 -0
- package/dashboard/src/canvas/pixel-agent.ts +298 -0
- package/dashboard/src/canvas/pixel-sprites.ts +440 -0
- package/dashboard/src/canvas/shipyard-effects.ts +367 -0
- package/dashboard/src/canvas/shipyard-scene.ts +616 -0
- package/dashboard/src/canvas/submarine-layout.ts +267 -0
- package/dashboard/src/components/header.ts +8 -7
- package/dashboard/src/core/router.ts +1 -0
- package/dashboard/src/design/submarine-theme.ts +253 -0
- package/dashboard/src/main.ts +2 -0
- package/dashboard/src/types/api.ts +2 -1
- package/dashboard/src/views/activity.ts +2 -1
- package/dashboard/src/views/shipyard.ts +39 -0
- package/dashboard/types/index.ts +166 -0
- package/docs/plans/2026-02-28-compound-audit-and-shipyard-design.md +186 -0
- package/docs/plans/2026-02-28-skipper-shipwright-implementation-plan.md +1182 -0
- package/docs/plans/2026-02-28-skipper-shipwright-integration-design.md +531 -0
- package/docs/plans/2026-03-01-ai-powered-skill-injection-design.md +298 -0
- package/docs/plans/2026-03-01-ai-powered-skill-injection-plan.md +1109 -0
- package/docs/plans/2026-03-01-capabilities-cleanup-plan.md +658 -0
- package/docs/plans/2026-03-01-clean-architecture-plan.md +924 -0
- package/docs/plans/2026-03-01-compound-audit-cascade-design.md +191 -0
- package/docs/plans/2026-03-01-compound-audit-cascade-plan.md +921 -0
- package/docs/plans/2026-03-01-deep-integration-plan.md +851 -0
- package/docs/plans/2026-03-01-pipeline-audit-trail-design.md +145 -0
- package/docs/plans/2026-03-01-pipeline-audit-trail-plan.md +770 -0
- package/docs/plans/2026-03-01-refined-depths-brand-design.md +382 -0
- package/docs/plans/2026-03-01-refined-depths-implementation.md +599 -0
- package/docs/plans/2026-03-01-skipper-kernel-integration-design.md +203 -0
- package/docs/plans/2026-03-01-unified-platform-design.md +272 -0
- package/docs/plans/2026-03-07-claude-code-feature-integration-design.md +189 -0
- package/docs/plans/2026-03-07-claude-code-feature-integration-plan.md +1165 -0
- package/docs/research/BACKLOG_QUICK_REFERENCE.md +352 -0
- package/docs/research/CUTTING_EDGE_RESEARCH_2026.md +546 -0
- package/docs/research/RESEARCH_INDEX.md +439 -0
- package/docs/research/RESEARCH_SOURCES.md +440 -0
- package/docs/research/RESEARCH_SUMMARY.txt +275 -0
- package/docs/superpowers/specs/2026-03-10-pipeline-quality-revolution-design.md +341 -0
- package/package.json +2 -2
- package/scripts/lib/adaptive-model.sh +427 -0
- package/scripts/lib/adaptive-timeout.sh +316 -0
- package/scripts/lib/audit-trail.sh +309 -0
- package/scripts/lib/auto-recovery.sh +471 -0
- package/scripts/lib/bandit-selector.sh +431 -0
- package/scripts/lib/bootstrap.sh +104 -2
- package/scripts/lib/causal-graph.sh +455 -0
- package/scripts/lib/compat.sh +126 -0
- package/scripts/lib/compound-audit.sh +337 -0
- package/scripts/lib/constitutional.sh +454 -0
- package/scripts/lib/context-budget.sh +359 -0
- package/scripts/lib/convergence.sh +594 -0
- package/scripts/lib/cost-optimizer.sh +634 -0
- package/scripts/lib/daemon-adaptive.sh +10 -0
- package/scripts/lib/daemon-dispatch.sh +106 -17
- package/scripts/lib/daemon-failure.sh +34 -4
- package/scripts/lib/daemon-patrol.sh +23 -2
- package/scripts/lib/daemon-poll-github.sh +361 -0
- package/scripts/lib/daemon-poll-health.sh +299 -0
- package/scripts/lib/daemon-poll.sh +27 -611
- package/scripts/lib/daemon-state.sh +112 -66
- package/scripts/lib/daemon-triage.sh +10 -0
- package/scripts/lib/dod-scorecard.sh +442 -0
- package/scripts/lib/error-actionability.sh +300 -0
- package/scripts/lib/formal-spec.sh +461 -0
- package/scripts/lib/helpers.sh +177 -4
- package/scripts/lib/intent-analysis.sh +409 -0
- package/scripts/lib/loop-convergence.sh +350 -0
- package/scripts/lib/loop-iteration.sh +682 -0
- package/scripts/lib/loop-progress.sh +48 -0
- package/scripts/lib/loop-restart.sh +185 -0
- package/scripts/lib/memory-effectiveness.sh +506 -0
- package/scripts/lib/mutation-executor.sh +352 -0
- package/scripts/lib/outcome-feedback.sh +521 -0
- package/scripts/lib/pipeline-cli.sh +336 -0
- package/scripts/lib/pipeline-commands.sh +1216 -0
- package/scripts/lib/pipeline-detection.sh +100 -2
- package/scripts/lib/pipeline-execution.sh +897 -0
- package/scripts/lib/pipeline-github.sh +28 -3
- package/scripts/lib/pipeline-intelligence-compound.sh +431 -0
- package/scripts/lib/pipeline-intelligence-scoring.sh +407 -0
- package/scripts/lib/pipeline-intelligence-skip.sh +181 -0
- package/scripts/lib/pipeline-intelligence.sh +100 -1136
- package/scripts/lib/pipeline-quality-bash-compat.sh +182 -0
- package/scripts/lib/pipeline-quality-checks.sh +17 -715
- package/scripts/lib/pipeline-quality-gates.sh +563 -0
- package/scripts/lib/pipeline-stages-build.sh +730 -0
- package/scripts/lib/pipeline-stages-delivery.sh +965 -0
- package/scripts/lib/pipeline-stages-intake.sh +1133 -0
- package/scripts/lib/pipeline-stages-monitor.sh +407 -0
- package/scripts/lib/pipeline-stages-review.sh +1022 -0
- package/scripts/lib/pipeline-stages.sh +59 -2929
- package/scripts/lib/pipeline-state.sh +36 -5
- package/scripts/lib/pipeline-util.sh +487 -0
- package/scripts/lib/policy-learner.sh +438 -0
- package/scripts/lib/process-reward.sh +493 -0
- package/scripts/lib/project-detect.sh +649 -0
- package/scripts/lib/quality-profile.sh +334 -0
- package/scripts/lib/recruit-commands.sh +885 -0
- package/scripts/lib/recruit-learning.sh +739 -0
- package/scripts/lib/recruit-roles.sh +648 -0
- package/scripts/lib/reward-aggregator.sh +458 -0
- package/scripts/lib/rl-optimizer.sh +362 -0
- package/scripts/lib/root-cause.sh +427 -0
- package/scripts/lib/scope-enforcement.sh +445 -0
- package/scripts/lib/session-restart.sh +493 -0
- package/scripts/lib/skill-memory.sh +300 -0
- package/scripts/lib/skill-registry.sh +775 -0
- package/scripts/lib/spec-driven.sh +476 -0
- package/scripts/lib/test-helpers.sh +18 -7
- package/scripts/lib/test-holdout.sh +429 -0
- package/scripts/lib/test-optimizer.sh +511 -0
- package/scripts/shipwright-file-suggest.sh +45 -0
- package/scripts/skills/adversarial-quality.md +61 -0
- package/scripts/skills/api-design.md +44 -0
- package/scripts/skills/architecture-design.md +50 -0
- package/scripts/skills/brainstorming.md +43 -0
- package/scripts/skills/data-pipeline.md +44 -0
- package/scripts/skills/deploy-safety.md +64 -0
- package/scripts/skills/documentation.md +38 -0
- package/scripts/skills/frontend-design.md +45 -0
- package/scripts/skills/generated/.gitkeep +0 -0
- package/scripts/skills/generated/_refinements/.gitkeep +0 -0
- package/scripts/skills/generated/_refinements/adversarial-quality.patch.md +3 -0
- package/scripts/skills/generated/_refinements/architecture-design.patch.md +3 -0
- package/scripts/skills/generated/_refinements/brainstorming.patch.md +3 -0
- package/scripts/skills/generated/cli-version-management.md +29 -0
- package/scripts/skills/generated/collection-system-validation.md +99 -0
- package/scripts/skills/generated/large-scale-c-refactoring-coordination.md +97 -0
- package/scripts/skills/generated/pattern-matching-similarity-scoring.md +195 -0
- package/scripts/skills/generated/test-parallelization-detection.md +65 -0
- package/scripts/skills/observability.md +79 -0
- package/scripts/skills/performance.md +48 -0
- package/scripts/skills/pr-quality.md +49 -0
- package/scripts/skills/product-thinking.md +43 -0
- package/scripts/skills/security-audit.md +49 -0
- package/scripts/skills/systematic-debugging.md +40 -0
- package/scripts/skills/testing-strategy.md +47 -0
- package/scripts/skills/two-stage-review.md +52 -0
- package/scripts/skills/validation-thoroughness.md +55 -0
- package/scripts/sw +9 -3
- package/scripts/sw-activity.sh +9 -2
- package/scripts/sw-adaptive.sh +2 -1
- package/scripts/sw-adversarial.sh +2 -1
- package/scripts/sw-architecture-enforcer.sh +3 -1
- package/scripts/sw-auth.sh +12 -2
- package/scripts/sw-autonomous.sh +5 -1
- package/scripts/sw-changelog.sh +4 -1
- package/scripts/sw-checkpoint.sh +2 -1
- package/scripts/sw-ci.sh +5 -1
- package/scripts/sw-cleanup.sh +4 -26
- package/scripts/sw-code-review.sh +10 -4
- package/scripts/sw-connect.sh +2 -1
- package/scripts/sw-context.sh +2 -1
- package/scripts/sw-cost.sh +48 -3
- package/scripts/sw-daemon.sh +66 -9
- package/scripts/sw-dashboard.sh +3 -1
- package/scripts/sw-db.sh +59 -16
- package/scripts/sw-decide.sh +8 -2
- package/scripts/sw-decompose.sh +360 -17
- package/scripts/sw-deps.sh +4 -1
- package/scripts/sw-developer-simulation.sh +4 -1
- package/scripts/sw-discovery.sh +325 -2
- package/scripts/sw-doc-fleet.sh +4 -1
- package/scripts/sw-docs-agent.sh +3 -1
- package/scripts/sw-docs.sh +2 -1
- package/scripts/sw-doctor.sh +453 -2
- package/scripts/sw-dora.sh +4 -1
- package/scripts/sw-durable.sh +4 -3
- package/scripts/sw-e2e-orchestrator.sh +17 -16
- package/scripts/sw-eventbus.sh +7 -1
- package/scripts/sw-evidence.sh +364 -12
- package/scripts/sw-feedback.sh +550 -9
- package/scripts/sw-fix.sh +20 -1
- package/scripts/sw-fleet-discover.sh +6 -2
- package/scripts/sw-fleet-viz.sh +4 -1
- package/scripts/sw-fleet.sh +5 -1
- package/scripts/sw-github-app.sh +16 -3
- package/scripts/sw-github-checks.sh +3 -2
- package/scripts/sw-github-deploy.sh +3 -2
- package/scripts/sw-github-graphql.sh +18 -7
- package/scripts/sw-guild.sh +5 -1
- package/scripts/sw-heartbeat.sh +5 -30
- package/scripts/sw-hello.sh +67 -0
- package/scripts/sw-hygiene.sh +6 -1
- package/scripts/sw-incident.sh +265 -1
- package/scripts/sw-init.sh +18 -2
- package/scripts/sw-instrument.sh +10 -2
- package/scripts/sw-intelligence.sh +42 -6
- package/scripts/sw-jira.sh +5 -1
- package/scripts/sw-launchd.sh +2 -1
- package/scripts/sw-linear.sh +4 -1
- package/scripts/sw-logs.sh +4 -1
- package/scripts/sw-loop.sh +432 -1128
- package/scripts/sw-memory.sh +356 -2
- package/scripts/sw-mission-control.sh +6 -1
- package/scripts/sw-model-router.sh +481 -26
- package/scripts/sw-otel.sh +13 -4
- package/scripts/sw-oversight.sh +14 -5
- package/scripts/sw-patrol-meta.sh +334 -0
- package/scripts/sw-pipeline-composer.sh +5 -1
- package/scripts/sw-pipeline-vitals.sh +2 -1
- package/scripts/sw-pipeline.sh +53 -2664
- package/scripts/sw-pm.sh +12 -5
- package/scripts/sw-pr-lifecycle.sh +2 -1
- package/scripts/sw-predictive.sh +7 -1
- package/scripts/sw-prep.sh +185 -2
- package/scripts/sw-ps.sh +5 -25
- package/scripts/sw-public-dashboard.sh +15 -3
- package/scripts/sw-quality.sh +2 -1
- package/scripts/sw-reaper.sh +8 -25
- package/scripts/sw-recruit.sh +156 -2303
- package/scripts/sw-regression.sh +19 -12
- package/scripts/sw-release-manager.sh +3 -1
- package/scripts/sw-release.sh +4 -1
- package/scripts/sw-remote.sh +3 -1
- package/scripts/sw-replay.sh +7 -1
- package/scripts/sw-retro.sh +158 -1
- package/scripts/sw-review-rerun.sh +3 -1
- package/scripts/sw-scale.sh +10 -3
- package/scripts/sw-security-audit.sh +6 -1
- package/scripts/sw-self-optimize.sh +6 -3
- package/scripts/sw-session.sh +9 -3
- package/scripts/sw-setup.sh +3 -1
- package/scripts/sw-stall-detector.sh +406 -0
- package/scripts/sw-standup.sh +15 -7
- package/scripts/sw-status.sh +3 -1
- package/scripts/sw-strategic.sh +4 -1
- package/scripts/sw-stream.sh +7 -1
- package/scripts/sw-swarm.sh +18 -6
- package/scripts/sw-team-stages.sh +13 -6
- package/scripts/sw-templates.sh +5 -29
- package/scripts/sw-testgen.sh +7 -1
- package/scripts/sw-tmux-pipeline.sh +4 -1
- package/scripts/sw-tmux-role-color.sh +2 -0
- package/scripts/sw-tmux-status.sh +1 -1
- package/scripts/sw-tmux.sh +3 -1
- package/scripts/sw-trace.sh +3 -1
- package/scripts/sw-tracker-github.sh +3 -0
- package/scripts/sw-tracker-jira.sh +3 -0
- package/scripts/sw-tracker-linear.sh +3 -0
- package/scripts/sw-tracker.sh +3 -1
- package/scripts/sw-triage.sh +2 -1
- package/scripts/sw-upgrade.sh +3 -1
- package/scripts/sw-ux.sh +5 -2
- package/scripts/sw-webhook.sh +3 -1
- package/scripts/sw-widgets.sh +3 -1
- package/scripts/sw-worktree.sh +15 -3
- package/scripts/test-skill-injection.sh +1233 -0
- package/templates/pipelines/autonomous.json +27 -3
- package/templates/pipelines/cost-aware.json +34 -8
- package/templates/pipelines/deployed.json +12 -0
- package/templates/pipelines/enterprise.json +12 -0
- package/templates/pipelines/fast.json +6 -0
- package/templates/pipelines/full.json +27 -3
- package/templates/pipelines/hotfix.json +6 -0
- package/templates/pipelines/standard.json +12 -0
- package/templates/pipelines/tdd.json +12 -0
|
@@ -8,39 +8,47 @@
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
:root {
|
|
11
|
-
|
|
12
|
-
--
|
|
13
|
-
--
|
|
14
|
-
--
|
|
15
|
-
--
|
|
16
|
-
|
|
11
|
+
/* Ocean Depths — quieted */
|
|
12
|
+
--abyss: #050508;
|
|
13
|
+
--deep: #0a0d14;
|
|
14
|
+
--ocean: #111520;
|
|
15
|
+
--surface: #171c28;
|
|
16
|
+
--foam: #1e2536;
|
|
17
|
+
|
|
18
|
+
/* Accent — cyan primary */
|
|
17
19
|
--cyan: #00d4ff;
|
|
18
|
-
--cyan-
|
|
19
|
-
--cyan-
|
|
20
|
+
--cyan-subtle: rgba(0, 212, 255, 0.08);
|
|
21
|
+
--cyan-muted: rgba(0, 212, 255, 0.25);
|
|
20
22
|
--purple: #7c3aed;
|
|
21
|
-
--purple-glow: rgba(124, 58, 237, 0.15);
|
|
22
23
|
--blue: #0066ff;
|
|
23
24
|
|
|
25
|
+
/* Status */
|
|
24
26
|
--green: #4ade80;
|
|
25
27
|
--amber: #fbbf24;
|
|
26
28
|
--rose: #f43f5e;
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
--text-
|
|
30
|
-
--text-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
--card-
|
|
35
|
-
--card-
|
|
36
|
-
|
|
37
|
-
--
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
/* Text */
|
|
31
|
+
--text-primary: #e8eaed;
|
|
32
|
+
--text-secondary: #8b8f9a;
|
|
33
|
+
--text-muted: #555a66;
|
|
34
|
+
|
|
35
|
+
/* Cards */
|
|
36
|
+
--card-bg: rgba(10, 13, 20, 0.8);
|
|
37
|
+
--card-border: rgba(255, 255, 255, 0.06);
|
|
38
|
+
--card-hover-border: rgba(0, 212, 255, 0.15);
|
|
39
|
+
--card-radius: 14px;
|
|
40
|
+
|
|
41
|
+
/* Typography — system fonts */
|
|
42
|
+
--font-body:
|
|
43
|
+
-apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, "Helvetica Neue",
|
|
44
|
+
sans-serif;
|
|
45
|
+
--font-mono:
|
|
46
|
+
"SF Mono", ui-monospace, "Cascadia Code", "Geist Mono", monospace;
|
|
47
|
+
|
|
48
|
+
/* Transitions */
|
|
41
49
|
--transition-fast: 0.15s ease;
|
|
42
|
-
--transition-base: 0.
|
|
43
|
-
--transition-slow: 0.
|
|
50
|
+
--transition-base: 0.25s ease;
|
|
51
|
+
--transition-slow: 0.4s ease;
|
|
44
52
|
|
|
45
53
|
/* Spacing scale */
|
|
46
54
|
--space-1: 4px;
|
|
@@ -55,18 +63,17 @@
|
|
|
55
63
|
--space-16: 64px;
|
|
56
64
|
|
|
57
65
|
/* Border radius */
|
|
58
|
-
--radius-sm:
|
|
59
|
-
--radius-md:
|
|
60
|
-
--radius-lg:
|
|
61
|
-
--radius-xl:
|
|
66
|
+
--radius-sm: 6px;
|
|
67
|
+
--radius-md: 10px;
|
|
68
|
+
--radius-lg: 14px;
|
|
69
|
+
--radius-xl: 20px;
|
|
62
70
|
--radius-full: 9999px;
|
|
63
71
|
|
|
64
72
|
/* Shadows */
|
|
65
|
-
--shadow-
|
|
66
|
-
--shadow-
|
|
67
|
-
--shadow-
|
|
68
|
-
--shadow-
|
|
69
|
-
--shadow-elevated: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
73
|
+
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3), 0 0 1px rgba(0, 0, 0, 0.1);
|
|
74
|
+
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.3), 0 0 1px rgba(0, 0, 0, 0.1);
|
|
75
|
+
--shadow-lg: 0 12px 24px rgba(0, 0, 0, 0.4), 0 0 1px rgba(0, 0, 0, 0.1);
|
|
76
|
+
--shadow-elevated: 0 24px 48px rgba(0, 0, 0, 0.5), 0 0 1px rgba(0, 0, 0, 0.1);
|
|
70
77
|
|
|
71
78
|
/* Z-index */
|
|
72
79
|
--z-base: 1;
|
|
@@ -78,46 +85,42 @@
|
|
|
78
85
|
|
|
79
86
|
/* Easing */
|
|
80
87
|
--ease-smooth: cubic-bezier(0.4, 0, 0.2, 1);
|
|
81
|
-
--ease-spring: cubic-bezier(0.34, 1.
|
|
88
|
+
--ease-spring: cubic-bezier(0.34, 1.2, 0.64, 1);
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
/* ── Light Mode ───────────────────────────────── */
|
|
85
92
|
:root[data-theme="light"] {
|
|
86
|
-
--abyss: #
|
|
87
|
-
--deep: #
|
|
88
|
-
--ocean: #
|
|
89
|
-
--surface: #
|
|
90
|
-
--foam: #
|
|
91
|
-
|
|
92
|
-
--cyan: #
|
|
93
|
-
--cyan-
|
|
94
|
-
--cyan-
|
|
93
|
+
--abyss: #f8f9fa;
|
|
94
|
+
--deep: #ffffff;
|
|
95
|
+
--ocean: #f1f3f5;
|
|
96
|
+
--surface: #e9ecef;
|
|
97
|
+
--foam: #dee2e6;
|
|
98
|
+
|
|
99
|
+
--cyan: #0091b3;
|
|
100
|
+
--cyan-subtle: rgba(0, 145, 179, 0.08);
|
|
101
|
+
--cyan-muted: rgba(0, 145, 179, 0.25);
|
|
95
102
|
--purple: #6d28d9;
|
|
96
|
-
--purple-glow: rgba(109, 40, 217, 0.1);
|
|
97
103
|
--blue: #0055cc;
|
|
98
104
|
|
|
99
105
|
--green: #16a34a;
|
|
100
106
|
--amber: #d97706;
|
|
101
107
|
--rose: #dc2626;
|
|
102
108
|
|
|
103
|
-
--text-primary: #
|
|
104
|
-
--text-secondary: #
|
|
105
|
-
--text-muted: #
|
|
109
|
+
--text-primary: #1a1d21;
|
|
110
|
+
--text-secondary: #495057;
|
|
111
|
+
--text-muted: #868e96;
|
|
106
112
|
|
|
107
113
|
--card-bg: rgba(255, 255, 255, 0.9);
|
|
108
|
-
--card-border: rgba(0,
|
|
109
|
-
--card-hover-border: rgba(0,
|
|
114
|
+
--card-border: rgba(0, 0, 0, 0.06);
|
|
115
|
+
--card-hover-border: rgba(0, 145, 179, 0.15);
|
|
110
116
|
|
|
111
|
-
--shadow-
|
|
112
|
-
--shadow-
|
|
113
|
-
--shadow-
|
|
114
|
-
--shadow-
|
|
115
|
-
|
|
117
|
+
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08), 0 0 1px rgba(0, 0, 0, 0.04);
|
|
118
|
+
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04);
|
|
119
|
+
--shadow-lg: 0 12px 28px rgba(0, 0, 0, 0.1), 0 4px 8px rgba(0, 0, 0, 0.04);
|
|
120
|
+
--shadow-elevated:
|
|
121
|
+
0 24px 48px rgba(0, 0, 0, 0.12), 0 8px 16px rgba(0, 0, 0, 0.06);
|
|
116
122
|
|
|
117
|
-
--
|
|
118
|
-
--bg-deep: #ebeef3;
|
|
119
|
-
--bg-foam: #dde2ea;
|
|
120
|
-
--border: rgba(0, 0, 0, 0.1);
|
|
123
|
+
--border: rgba(0, 0, 0, 0.08);
|
|
121
124
|
}
|
|
122
125
|
|
|
123
126
|
::selection {
|
|
@@ -136,7 +139,7 @@ body {
|
|
|
136
139
|
overflow-x: hidden;
|
|
137
140
|
-webkit-font-smoothing: antialiased;
|
|
138
141
|
-moz-osx-font-smoothing: grayscale;
|
|
139
|
-
scrollbar-color: var(--cyan-
|
|
142
|
+
scrollbar-color: var(--cyan-muted) var(--deep);
|
|
140
143
|
}
|
|
141
144
|
|
|
142
145
|
body::-webkit-scrollbar {
|
|
@@ -146,7 +149,7 @@ body::-webkit-scrollbar-track {
|
|
|
146
149
|
background: var(--deep);
|
|
147
150
|
}
|
|
148
151
|
body::-webkit-scrollbar-thumb {
|
|
149
|
-
background: var(--cyan-
|
|
152
|
+
background: var(--cyan-muted);
|
|
150
153
|
border-radius: 4px;
|
|
151
154
|
}
|
|
152
155
|
|
|
@@ -184,7 +187,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
184
187
|
}
|
|
185
188
|
|
|
186
189
|
.header-title {
|
|
187
|
-
font-family: var(--font-
|
|
190
|
+
font-family: var(--font-body);
|
|
188
191
|
font-size: 1.25rem;
|
|
189
192
|
color: var(--text-primary);
|
|
190
193
|
line-height: 1;
|
|
@@ -265,7 +268,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
265
268
|
|
|
266
269
|
.user-avatar:hover {
|
|
267
270
|
border-color: var(--card-hover-border);
|
|
268
|
-
box-shadow: 0 0 12px var(--cyan-
|
|
271
|
+
box-shadow: 0 0 12px var(--cyan-subtle);
|
|
269
272
|
}
|
|
270
273
|
|
|
271
274
|
.user-avatar img {
|
|
@@ -460,7 +463,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
460
463
|
}
|
|
461
464
|
|
|
462
465
|
.stat-value {
|
|
463
|
-
font-family: var(--font-
|
|
466
|
+
font-family: var(--font-body);
|
|
464
467
|
font-size: 1.75rem;
|
|
465
468
|
font-weight: 400;
|
|
466
469
|
line-height: 1.2;
|
|
@@ -661,7 +664,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
661
664
|
@keyframes stage-glow {
|
|
662
665
|
0%,
|
|
663
666
|
100% {
|
|
664
|
-
box-shadow: 0 0 6px var(--cyan-
|
|
667
|
+
box-shadow: 0 0 6px var(--cyan-subtle);
|
|
665
668
|
}
|
|
666
669
|
50% {
|
|
667
670
|
box-shadow: 0 0 14px rgba(0, 212, 255, 0.35);
|
|
@@ -775,7 +778,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
775
778
|
.activity-feed {
|
|
776
779
|
max-height: 400px;
|
|
777
780
|
overflow-y: auto;
|
|
778
|
-
scrollbar-color: var(--cyan-
|
|
781
|
+
scrollbar-color: var(--cyan-muted) var(--deep);
|
|
779
782
|
}
|
|
780
783
|
|
|
781
784
|
.activity-feed::-webkit-scrollbar {
|
|
@@ -785,7 +788,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
785
788
|
background: transparent;
|
|
786
789
|
}
|
|
787
790
|
.activity-feed::-webkit-scrollbar-thumb {
|
|
788
|
-
background: var(--cyan-
|
|
791
|
+
background: var(--cyan-muted);
|
|
789
792
|
border-radius: 3px;
|
|
790
793
|
}
|
|
791
794
|
|
|
@@ -802,7 +805,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
802
805
|
}
|
|
803
806
|
|
|
804
807
|
.activity-row:hover {
|
|
805
|
-
border-left-color: var(--cyan-
|
|
808
|
+
border-left-color: var(--cyan-subtle);
|
|
806
809
|
background: rgba(0, 212, 255, 0.02);
|
|
807
810
|
}
|
|
808
811
|
|
|
@@ -1022,7 +1025,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
1022
1025
|
.issue-filter-input:focus {
|
|
1023
1026
|
outline: none;
|
|
1024
1027
|
border-color: var(--cyan);
|
|
1025
|
-
box-shadow: 0 0 12px var(--cyan-
|
|
1028
|
+
box-shadow: 0 0 12px var(--cyan-subtle);
|
|
1026
1029
|
}
|
|
1027
1030
|
|
|
1028
1031
|
/* ── Segmented Control ────────────────────────── */
|
|
@@ -1267,7 +1270,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
1267
1270
|
.stage-timeline-dot.active {
|
|
1268
1271
|
background: var(--cyan);
|
|
1269
1272
|
border-color: var(--cyan);
|
|
1270
|
-
box-shadow: 0 0 8px var(--cyan-
|
|
1273
|
+
box-shadow: 0 0 8px var(--cyan-subtle);
|
|
1271
1274
|
}
|
|
1272
1275
|
.stage-timeline-dot.failed {
|
|
1273
1276
|
background: var(--rose);
|
|
@@ -1325,7 +1328,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
1325
1328
|
background: transparent;
|
|
1326
1329
|
}
|
|
1327
1330
|
.detail-plan-content::-webkit-scrollbar-thumb {
|
|
1328
|
-
background: var(--cyan-
|
|
1331
|
+
background: var(--cyan-muted);
|
|
1329
1332
|
border-radius: 3px;
|
|
1330
1333
|
}
|
|
1331
1334
|
|
|
@@ -1359,7 +1362,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
1359
1362
|
.activity-timeline {
|
|
1360
1363
|
max-height: calc(100vh - 260px);
|
|
1361
1364
|
overflow-y: auto;
|
|
1362
|
-
scrollbar-color: var(--cyan-
|
|
1365
|
+
scrollbar-color: var(--cyan-muted) var(--deep);
|
|
1363
1366
|
}
|
|
1364
1367
|
|
|
1365
1368
|
.activity-timeline::-webkit-scrollbar {
|
|
@@ -1369,7 +1372,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
1369
1372
|
background: transparent;
|
|
1370
1373
|
}
|
|
1371
1374
|
.activity-timeline::-webkit-scrollbar-thumb {
|
|
1372
|
-
background: var(--cyan-
|
|
1375
|
+
background: var(--cyan-muted);
|
|
1373
1376
|
border-radius: 3px;
|
|
1374
1377
|
}
|
|
1375
1378
|
|
|
@@ -1388,7 +1391,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
1388
1391
|
|
|
1389
1392
|
.timeline-row:hover {
|
|
1390
1393
|
background: rgba(0, 212, 255, 0.03);
|
|
1391
|
-
border-left-color: var(--cyan-
|
|
1394
|
+
border-left-color: var(--cyan-subtle);
|
|
1392
1395
|
}
|
|
1393
1396
|
|
|
1394
1397
|
.timeline-ts {
|
|
@@ -1502,7 +1505,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
1502
1505
|
}
|
|
1503
1506
|
|
|
1504
1507
|
.metric-big-value {
|
|
1505
|
-
font-family: var(--font-
|
|
1508
|
+
font-family: var(--font-body);
|
|
1506
1509
|
font-size: 2rem;
|
|
1507
1510
|
font-weight: 400;
|
|
1508
1511
|
line-height: 1.2;
|
|
@@ -1552,7 +1555,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
1552
1555
|
.donut-value {
|
|
1553
1556
|
position: relative;
|
|
1554
1557
|
z-index: 1;
|
|
1555
|
-
font-family: var(--font-
|
|
1558
|
+
font-family: var(--font-body);
|
|
1556
1559
|
font-size: 1.5rem;
|
|
1557
1560
|
font-weight: 400;
|
|
1558
1561
|
color: var(--text-primary);
|
|
@@ -1596,7 +1599,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
1596
1599
|
}
|
|
1597
1600
|
|
|
1598
1601
|
.svg-donut text {
|
|
1599
|
-
font-family: var(--font-
|
|
1602
|
+
font-family: var(--font-body);
|
|
1600
1603
|
fill: var(--text-primary);
|
|
1601
1604
|
text-anchor: middle;
|
|
1602
1605
|
dominant-baseline: central;
|
|
@@ -2304,7 +2307,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
2304
2307
|
.modal-textarea:focus {
|
|
2305
2308
|
outline: none;
|
|
2306
2309
|
border-color: var(--cyan);
|
|
2307
|
-
box-shadow: 0 0 12px var(--cyan-
|
|
2310
|
+
box-shadow: 0 0 12px var(--cyan-subtle);
|
|
2308
2311
|
}
|
|
2309
2312
|
|
|
2310
2313
|
.modal-footer {
|
|
@@ -2485,7 +2488,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
2485
2488
|
@keyframes stage-node-pulse {
|
|
2486
2489
|
0%,
|
|
2487
2490
|
100% {
|
|
2488
|
-
filter: drop-shadow(0 0 4px var(--cyan-
|
|
2491
|
+
filter: drop-shadow(0 0 4px var(--cyan-subtle));
|
|
2489
2492
|
}
|
|
2490
2493
|
50% {
|
|
2491
2494
|
filter: drop-shadow(0 0 12px rgba(0, 212, 255, 0.4));
|
|
@@ -2515,7 +2518,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
2515
2518
|
width: 48px;
|
|
2516
2519
|
height: 48px;
|
|
2517
2520
|
border-radius: 12px;
|
|
2518
|
-
font-family: var(--font-
|
|
2521
|
+
font-family: var(--font-body);
|
|
2519
2522
|
font-size: 1.25rem;
|
|
2520
2523
|
font-weight: 400;
|
|
2521
2524
|
letter-spacing: 0.02em;
|
|
@@ -2676,7 +2679,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
2676
2679
|
|
|
2677
2680
|
/* ── KPI Cards ────────────────────────────────── */
|
|
2678
2681
|
.kpi-value {
|
|
2679
|
-
font-family: var(--font-
|
|
2682
|
+
font-family: var(--font-body);
|
|
2680
2683
|
font-size: 2.5rem;
|
|
2681
2684
|
font-weight: 400;
|
|
2682
2685
|
line-height: 1;
|
|
@@ -2762,7 +2765,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
2762
2765
|
.artifact-content {
|
|
2763
2766
|
max-height: 400px;
|
|
2764
2767
|
overflow-y: auto;
|
|
2765
|
-
scrollbar-color: var(--cyan-
|
|
2768
|
+
scrollbar-color: var(--cyan-muted) var(--deep);
|
|
2766
2769
|
}
|
|
2767
2770
|
|
|
2768
2771
|
.artifact-content::-webkit-scrollbar {
|
|
@@ -2772,7 +2775,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
2772
2775
|
background: transparent;
|
|
2773
2776
|
}
|
|
2774
2777
|
.artifact-content::-webkit-scrollbar-thumb {
|
|
2775
|
-
background: var(--cyan-
|
|
2778
|
+
background: var(--cyan-muted);
|
|
2776
2779
|
border-radius: 3px;
|
|
2777
2780
|
}
|
|
2778
2781
|
|
|
@@ -2790,7 +2793,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
2790
2793
|
overflow-y: auto;
|
|
2791
2794
|
white-space: pre-wrap;
|
|
2792
2795
|
word-break: break-all;
|
|
2793
|
-
scrollbar-color: var(--cyan-
|
|
2796
|
+
scrollbar-color: var(--cyan-muted) var(--abyss);
|
|
2794
2797
|
}
|
|
2795
2798
|
|
|
2796
2799
|
.log-viewer::-webkit-scrollbar {
|
|
@@ -2800,7 +2803,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
2800
2803
|
background: transparent;
|
|
2801
2804
|
}
|
|
2802
2805
|
.log-viewer::-webkit-scrollbar-thumb {
|
|
2803
|
-
background: var(--cyan-
|
|
2806
|
+
background: var(--cyan-muted);
|
|
2804
2807
|
border-radius: 3px;
|
|
2805
2808
|
}
|
|
2806
2809
|
|
|
@@ -3324,7 +3327,7 @@ body::-webkit-scrollbar-thumb {
|
|
|
3324
3327
|
}
|
|
3325
3328
|
|
|
3326
3329
|
.capacity-value {
|
|
3327
|
-
font-family: var(--font-
|
|
3330
|
+
font-family: var(--font-body);
|
|
3328
3331
|
font-size: 1.5rem;
|
|
3329
3332
|
color: var(--text-primary);
|
|
3330
3333
|
}
|
|
@@ -5010,7 +5013,7 @@ select.form-input {
|
|
|
5010
5013
|
|
|
5011
5014
|
.theater-pipeline-item.selected {
|
|
5012
5015
|
background: var(--surface);
|
|
5013
|
-
border: 1px solid var(--cyan-
|
|
5016
|
+
border: 1px solid var(--cyan-muted);
|
|
5014
5017
|
}
|
|
5015
5018
|
|
|
5016
5019
|
.theater-issue {
|
|
@@ -5156,7 +5159,7 @@ select.form-input {
|
|
|
5156
5159
|
|
|
5157
5160
|
.cockpit-agent-btn:hover {
|
|
5158
5161
|
background: var(--surface);
|
|
5159
|
-
border-color: var(--cyan-
|
|
5162
|
+
border-color: var(--cyan-muted);
|
|
5160
5163
|
}
|
|
5161
5164
|
|
|
5162
5165
|
.cockpit-agent-btn.selected {
|
|
@@ -5269,7 +5272,7 @@ select.form-input {
|
|
|
5269
5272
|
}
|
|
5270
5273
|
|
|
5271
5274
|
.sound-toggle:hover {
|
|
5272
|
-
border-color: var(--cyan-
|
|
5275
|
+
border-color: var(--cyan-muted);
|
|
5273
5276
|
color: var(--text-secondary);
|
|
5274
5277
|
}
|
|
5275
5278
|
|
|
@@ -5378,7 +5381,7 @@ select.form-input {
|
|
|
5378
5381
|
gap: 0.5rem;
|
|
5379
5382
|
}
|
|
5380
5383
|
.learning-card {
|
|
5381
|
-
background: var(--
|
|
5384
|
+
background: var(--foam);
|
|
5382
5385
|
border: 1px solid var(--border);
|
|
5383
5386
|
border-radius: var(--radius-md);
|
|
5384
5387
|
padding: 0.75rem 1rem;
|
|
@@ -5392,7 +5395,7 @@ select.form-input {
|
|
|
5392
5395
|
}
|
|
5393
5396
|
.learning-category {
|
|
5394
5397
|
background: var(--cyan);
|
|
5395
|
-
color: var(--
|
|
5398
|
+
color: var(--abyss);
|
|
5396
5399
|
padding: 0.125rem 0.5rem;
|
|
5397
5400
|
border-radius: var(--radius-sm);
|
|
5398
5401
|
font-weight: 600;
|
|
@@ -5434,7 +5437,7 @@ select.form-input {
|
|
|
5434
5437
|
display: flex;
|
|
5435
5438
|
align-items: center;
|
|
5436
5439
|
gap: 0.5rem;
|
|
5437
|
-
background: var(--
|
|
5440
|
+
background: var(--foam);
|
|
5438
5441
|
border: 1px solid var(--border);
|
|
5439
5442
|
border-radius: var(--radius-md);
|
|
5440
5443
|
padding: 0.75rem 1rem;
|
|
@@ -5445,7 +5448,7 @@ select.form-input {
|
|
|
5445
5448
|
white-space: nowrap;
|
|
5446
5449
|
}
|
|
5447
5450
|
.invite-url {
|
|
5448
|
-
background: var(--
|
|
5451
|
+
background: var(--deep);
|
|
5449
5452
|
padding: 0.25rem 0.5rem;
|
|
5450
5453
|
border-radius: var(--radius-sm);
|
|
5451
5454
|
font-size: 0.8rem;
|
|
@@ -5472,7 +5475,7 @@ select.form-input {
|
|
|
5472
5475
|
align-items: center;
|
|
5473
5476
|
gap: 0.5rem;
|
|
5474
5477
|
padding: 0.5rem 0.75rem;
|
|
5475
|
-
background: var(--
|
|
5478
|
+
background: var(--foam);
|
|
5476
5479
|
border-bottom: 1px solid var(--border);
|
|
5477
5480
|
font-size: 0.85rem;
|
|
5478
5481
|
font-weight: 600;
|
|
@@ -5541,7 +5544,7 @@ select.form-input {
|
|
|
5541
5544
|
.changes-diff {
|
|
5542
5545
|
margin-top: 0.25rem;
|
|
5543
5546
|
padding: 0.5rem;
|
|
5544
|
-
background: var(--
|
|
5547
|
+
background: var(--abyss);
|
|
5545
5548
|
border-radius: var(--radius-sm);
|
|
5546
5549
|
font-size: 0.75rem;
|
|
5547
5550
|
max-height: 300px;
|
|
@@ -5558,7 +5561,7 @@ select.form-input {
|
|
|
5558
5561
|
}
|
|
5559
5562
|
.reasoning-entry,
|
|
5560
5563
|
.failure-entry {
|
|
5561
|
-
background: var(--
|
|
5564
|
+
background: var(--foam);
|
|
5562
5565
|
border: 1px solid var(--border);
|
|
5563
5566
|
border-radius: var(--radius-md);
|
|
5564
5567
|
padding: 0.75rem 1rem;
|
|
@@ -5684,7 +5687,7 @@ select.form-input {
|
|
|
5684
5687
|
display: flex;
|
|
5685
5688
|
align-items: center;
|
|
5686
5689
|
gap: 0.5rem;
|
|
5687
|
-
background: var(--
|
|
5690
|
+
background: var(--foam);
|
|
5688
5691
|
border: 1px solid var(--border);
|
|
5689
5692
|
border-radius: var(--radius-md);
|
|
5690
5693
|
padding: 0.5rem 0.75rem;
|
|
@@ -5825,7 +5828,7 @@ select.form-input {
|
|
|
5825
5828
|
overflow: auto;
|
|
5826
5829
|
}
|
|
5827
5830
|
.admin-debug-pre {
|
|
5828
|
-
background: var(--
|
|
5831
|
+
background: var(--deep);
|
|
5829
5832
|
border: 1px solid var(--card-border);
|
|
5830
5833
|
border-radius: var(--radius-md);
|
|
5831
5834
|
padding: 1rem;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Session } from "../types/index.js";
|
|
2
|
+
import { CORS_HEADERS } from "../middleware/constants.js";
|
|
3
|
+
import { getAuthMode, isAuthEnabled, createSession, sessionCookie, clearSessionCookie } from "../middleware/auth.js";
|
|
4
|
+
|
|
5
|
+
export function handleAuthRoutes(req: Request, pathname: string, url: URL): Response | null {
|
|
6
|
+
// POST /auth/pat-login — PAT-based login (returns session)
|
|
7
|
+
if (pathname === "/auth/pat-login" && req.method === "POST") {
|
|
8
|
+
// This endpoint requires body parsing which is done in server.ts
|
|
9
|
+
// Returning null to signal server.ts to handle this
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// GET /auth/logout — Clear session and redirect
|
|
14
|
+
if (pathname === "/auth/logout") {
|
|
15
|
+
return new Response("Redirecting to login...", {
|
|
16
|
+
status: 303,
|
|
17
|
+
headers: {
|
|
18
|
+
Location: "/login",
|
|
19
|
+
"Set-Cookie": clearSessionCookie(),
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// GET /auth/github — OAuth redirect (requires server.ts to handle)
|
|
25
|
+
if (pathname === "/auth/github") {
|
|
26
|
+
if (getAuthMode() !== "oauth") {
|
|
27
|
+
return new Response("OAuth not configured", { status: 500 });
|
|
28
|
+
}
|
|
29
|
+
return null; // server.ts handles
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// GET /auth/callback — OAuth callback (requires server.ts to handle)
|
|
33
|
+
if (pathname === "/auth/callback") {
|
|
34
|
+
return null; // server.ts handles with query params
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return null;
|
|
38
|
+
}
|
package/dashboard/server.ts
CHANGED
|
@@ -41,6 +41,10 @@ const SESSION_SECRET = process.env.SESSION_SECRET || crypto.randomUUID();
|
|
|
41
41
|
const SESSION_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
42
42
|
const ALLOWED_PERMISSIONS = ["admin", "write"];
|
|
43
43
|
|
|
44
|
+
// ─── WebSocket Security ──────────────────────────────────────────────
|
|
45
|
+
const MAX_WS_CLIENTS = 50;
|
|
46
|
+
const WS_CONNECTION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
47
|
+
|
|
44
48
|
// ─── SQLite Database (optional) ──────────────────────────────────────
|
|
45
49
|
const DB_FILE = join(HOME, ".shipwright", "shipwright.db");
|
|
46
50
|
let db: Database | null = null;
|
|
@@ -333,6 +337,15 @@ function sessionCookie(sessionId: string): string {
|
|
|
333
337
|
return `fleet_session=${sessionId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${Math.floor(SESSION_TTL_MS / 1000)}`;
|
|
334
338
|
}
|
|
335
339
|
|
|
340
|
+
function isLocalConnection(req: Request): boolean {
|
|
341
|
+
const host = req.headers.get("host") || "";
|
|
342
|
+
return (
|
|
343
|
+
host.startsWith("localhost:") ||
|
|
344
|
+
host.startsWith("127.0.0.1:") ||
|
|
345
|
+
host.startsWith("[::1]:")
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
336
349
|
function clearSessionCookie(): string {
|
|
337
350
|
return "fleet_session=; Path=/; HttpOnly; SameSite=Lax; Max-Age=0";
|
|
338
351
|
}
|
|
@@ -932,18 +945,19 @@ function broadcastNewEvents(): void {
|
|
|
932
945
|
}
|
|
933
946
|
|
|
934
947
|
// ─── Data Collection ─────────────────────────────────────────────────
|
|
935
|
-
function readEvents(): DaemonEvent[] {
|
|
948
|
+
function readEvents(limit = 200): DaemonEvent[] {
|
|
936
949
|
// Try SQLite first (faster for large event logs)
|
|
937
|
-
const dbEvents = dbQueryEvents(0,
|
|
950
|
+
const dbEvents = dbQueryEvents(0, limit);
|
|
938
951
|
if (dbEvents.length > 0) return dbEvents;
|
|
939
952
|
|
|
940
|
-
// Fallback to JSONL
|
|
953
|
+
// Fallback to JSONL — only read last 1000 lines to avoid OOM on large files
|
|
941
954
|
if (!existsSync(EVENTS_FILE)) return [];
|
|
942
955
|
try {
|
|
943
956
|
const content = readFileSync(EVENTS_FILE, "utf-8").trim();
|
|
944
957
|
if (!content) return [];
|
|
945
|
-
|
|
946
|
-
|
|
958
|
+
const lines = content.split("\n");
|
|
959
|
+
const recent = lines.length > 1000 ? lines.slice(-1000) : lines;
|
|
960
|
+
return recent
|
|
947
961
|
.filter((l) => l.trim())
|
|
948
962
|
.map((l) => {
|
|
949
963
|
try {
|
|
@@ -2204,6 +2218,12 @@ function ghCached<T>(key: string, fn: () => T): T {
|
|
|
2204
2218
|
const now = Date.now();
|
|
2205
2219
|
const cached = ghCache.get(key);
|
|
2206
2220
|
if (cached && now - cached.ts < GH_CACHE_TTL_MS) return cached.data as T;
|
|
2221
|
+
// Evict expired entries to prevent unbounded memory growth
|
|
2222
|
+
if (ghCache.size > 50) {
|
|
2223
|
+
for (const [k, v] of ghCache) {
|
|
2224
|
+
if (now - v.ts > GH_CACHE_TTL_MS) ghCache.delete(k);
|
|
2225
|
+
}
|
|
2226
|
+
}
|
|
2207
2227
|
const data = fn();
|
|
2208
2228
|
ghCache.set(key, { data, ts: now });
|
|
2209
2229
|
return data;
|
|
@@ -2689,15 +2709,43 @@ const server = Bun.serve({
|
|
|
2689
2709
|
return handleAuthLogout(req);
|
|
2690
2710
|
}
|
|
2691
2711
|
|
|
2692
|
-
// ── Auth gate
|
|
2693
|
-
//
|
|
2712
|
+
// ── WebSocket Auth gate ──────────────────────────────────────
|
|
2713
|
+
// WebSocket always requires authentication (unless local connection)
|
|
2714
|
+
if (pathname === "/ws" || pathname === "/ws/events") {
|
|
2715
|
+
const isLocal = isLocalConnection(req);
|
|
2716
|
+
if (!isLocal) {
|
|
2717
|
+
const session = getSession(req);
|
|
2718
|
+
if (!session) {
|
|
2719
|
+
return new Response("Unauthorized", { status: 401 });
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
// Check connection limit
|
|
2724
|
+
const totalClients = wsClients.size + eventClients.size;
|
|
2725
|
+
if (totalClients >= MAX_WS_CLIENTS && !isLocal) {
|
|
2726
|
+
return new Response("Too Many Connections", { status: 429 });
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
// WebSocket upgrade — /ws/events streams raw events, /ws streams aggregated state
|
|
2730
|
+
if (pathname === "/ws/events") {
|
|
2731
|
+
const upgraded = server.upgrade(req, {
|
|
2732
|
+
data: { type: "events", lastEventId: 0 },
|
|
2733
|
+
});
|
|
2734
|
+
if (upgraded) return undefined as unknown as Response;
|
|
2735
|
+
return new Response("WebSocket upgrade failed", { status: 400 });
|
|
2736
|
+
}
|
|
2737
|
+
if (pathname === "/ws") {
|
|
2738
|
+
const upgraded = server.upgrade(req);
|
|
2739
|
+
if (upgraded) return undefined as unknown as Response;
|
|
2740
|
+
return new Response("WebSocket upgrade failed", { status: 400 });
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
|
|
2744
|
+
// ── HTTP Auth gate ───────────────────────────────────────────
|
|
2745
|
+
// If auth is enabled, enforce it on all other remaining routes
|
|
2694
2746
|
if (isAuthEnabled()) {
|
|
2695
2747
|
const session = getSession(req);
|
|
2696
2748
|
if (!session) {
|
|
2697
|
-
// WebSocket upgrade attempt without auth
|
|
2698
|
-
if (pathname === "/ws" || pathname === "/ws/events") {
|
|
2699
|
-
return new Response("Unauthorized", { status: 401 });
|
|
2700
|
-
}
|
|
2701
2749
|
return new Response(null, {
|
|
2702
2750
|
status: 302,
|
|
2703
2751
|
headers: { Location: "/login" },
|
|
@@ -2707,20 +2755,6 @@ const server = Bun.serve({
|
|
|
2707
2755
|
|
|
2708
2756
|
// ── Protected routes ──────────────────────────────────────────
|
|
2709
2757
|
|
|
2710
|
-
// WebSocket upgrade — /ws/events streams raw events, /ws streams aggregated state
|
|
2711
|
-
if (pathname === "/ws/events") {
|
|
2712
|
-
const upgraded = server.upgrade(req, {
|
|
2713
|
-
data: { type: "events", lastEventId: 0 },
|
|
2714
|
-
});
|
|
2715
|
-
if (upgraded) return undefined as unknown as Response;
|
|
2716
|
-
return new Response("WebSocket upgrade failed", { status: 400 });
|
|
2717
|
-
}
|
|
2718
|
-
if (pathname === "/ws") {
|
|
2719
|
-
const upgraded = server.upgrade(req);
|
|
2720
|
-
if (upgraded) return undefined as unknown as Response;
|
|
2721
|
-
return new Response("WebSocket upgrade failed", { status: 400 });
|
|
2722
|
-
}
|
|
2723
|
-
|
|
2724
2758
|
// REST: fleet state
|
|
2725
2759
|
if (pathname === "/api/status") {
|
|
2726
2760
|
return new Response(JSON.stringify(getFleetState()), {
|
|
@@ -5897,6 +5931,13 @@ process.on("SIGINT", () => {
|
|
|
5897
5931
|
clearInterval(staleClaimInterval);
|
|
5898
5932
|
clearInterval(inviteCleanupInterval);
|
|
5899
5933
|
if (eventsWatcher) eventsWatcher.close();
|
|
5934
|
+
if (db) {
|
|
5935
|
+
try {
|
|
5936
|
+
db.close();
|
|
5937
|
+
} catch {
|
|
5938
|
+
/* ignore */
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5900
5941
|
for (const ws of wsClients) {
|
|
5901
5942
|
try {
|
|
5902
5943
|
ws.close(1001, "Server shutting down");
|