edsger 0.36.2 → 0.37.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/settings.local.json +23 -3
- package/.env.local +12 -0
- package/dist/api/features/__tests__/regression-prevention.test.d.ts +5 -0
- package/dist/api/features/__tests__/regression-prevention.test.js +338 -0
- package/dist/api/features/__tests__/status-updater.integration.test.d.ts +5 -0
- package/dist/api/features/__tests__/status-updater.integration.test.js +497 -0
- package/dist/api/growth.d.ts +23 -1
- package/dist/api/growth.js +25 -0
- package/dist/commands/app-store/index.js +2 -6
- package/dist/commands/code-review/index.js +3 -3
- package/dist/commands/growth-analysis/index.js +2 -0
- package/dist/commands/init/index.js +3 -3
- package/dist/commands/workflow/pipeline-runner.d.ts +17 -0
- package/dist/commands/workflow/pipeline-runner.js +393 -0
- package/dist/commands/workflow/runner.d.ts +26 -0
- package/dist/commands/workflow/runner.js +119 -0
- package/dist/commands/workflow/workflow-runner.d.ts +26 -0
- package/dist/commands/workflow/workflow-runner.js +119 -0
- package/dist/index.js +4 -0
- package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +452 -32
- package/dist/phases/app-store-generation/assets/inter-latin-ext.woff2 +0 -0
- package/dist/phases/app-store-generation/assets/inter-latin.woff2 +0 -0
- package/dist/phases/app-store-generation/inter-font.d.ts +20 -0
- package/dist/phases/app-store-generation/inter-font.js +49 -0
- package/dist/phases/app-store-generation/screenshot-composer.js +183 -19
- package/dist/phases/code-implementation/analyzer-helpers.d.ts +28 -0
- package/dist/phases/code-implementation/analyzer-helpers.js +177 -0
- package/dist/phases/code-implementation/analyzer.d.ts +32 -0
- package/dist/phases/code-implementation/analyzer.js +629 -0
- package/dist/phases/code-implementation/context-fetcher.d.ts +17 -0
- package/dist/phases/code-implementation/context-fetcher.js +86 -0
- package/dist/phases/code-implementation/mcp-server.d.ts +1 -0
- package/dist/phases/code-implementation/mcp-server.js +93 -0
- package/dist/phases/code-implementation/prompts-improvement.d.ts +5 -0
- package/dist/phases/code-implementation/prompts-improvement.js +108 -0
- package/dist/phases/code-implementation-verification/verifier.d.ts +31 -0
- package/dist/phases/code-implementation-verification/verifier.js +196 -0
- package/dist/phases/code-refine/analyzer.d.ts +41 -0
- package/dist/phases/code-refine/analyzer.js +561 -0
- package/dist/phases/code-refine/context-fetcher.d.ts +94 -0
- package/dist/phases/code-refine/context-fetcher.js +423 -0
- package/dist/phases/code-refine-verification/analysis/llm-analyzer.d.ts +22 -0
- package/dist/phases/code-refine-verification/analysis/llm-analyzer.js +134 -0
- package/dist/phases/code-refine-verification/verifier.d.ts +47 -0
- package/dist/phases/code-refine-verification/verifier.js +597 -0
- package/dist/phases/code-review/analyzer.d.ts +29 -0
- package/dist/phases/code-review/analyzer.js +363 -0
- package/dist/phases/code-review/context-fetcher.d.ts +92 -0
- package/dist/phases/code-review/context-fetcher.js +296 -0
- package/dist/phases/feature-analysis/analyzer-helpers.d.ts +10 -0
- package/dist/phases/feature-analysis/analyzer-helpers.js +47 -0
- package/dist/phases/feature-analysis/analyzer.d.ts +11 -0
- package/dist/phases/feature-analysis/analyzer.js +208 -0
- package/dist/phases/feature-analysis/context-fetcher.d.ts +26 -0
- package/dist/phases/feature-analysis/context-fetcher.js +134 -0
- package/dist/phases/feature-analysis/http-fallback.d.ts +20 -0
- package/dist/phases/feature-analysis/http-fallback.js +95 -0
- package/dist/phases/feature-analysis/mcp-server.d.ts +1 -0
- package/dist/phases/feature-analysis/mcp-server.js +144 -0
- package/dist/phases/feature-analysis/prompts-improvement.d.ts +8 -0
- package/dist/phases/feature-analysis/prompts-improvement.js +109 -0
- package/dist/phases/feature-analysis-verification/verifier.d.ts +37 -0
- package/dist/phases/feature-analysis-verification/verifier.js +147 -0
- package/dist/phases/growth-analysis/context.d.ts +2 -2
- package/dist/phases/growth-analysis/context.js +18 -4
- package/dist/phases/growth-analysis/index.d.ts +2 -0
- package/dist/phases/growth-analysis/index.js +21 -13
- package/dist/phases/technical-design/analyzer-helpers.d.ts +25 -0
- package/dist/phases/technical-design/analyzer-helpers.js +39 -0
- package/dist/phases/technical-design/analyzer.d.ts +21 -0
- package/dist/phases/technical-design/analyzer.js +461 -0
- package/dist/phases/technical-design/context-fetcher.d.ts +12 -0
- package/dist/phases/technical-design/context-fetcher.js +39 -0
- package/dist/phases/technical-design/http-fallback.d.ts +17 -0
- package/dist/phases/technical-design/http-fallback.js +151 -0
- package/dist/phases/technical-design/mcp-server.d.ts +1 -0
- package/dist/phases/technical-design/mcp-server.js +157 -0
- package/dist/phases/technical-design/prompts-improvement.d.ts +5 -0
- package/dist/phases/technical-design/prompts-improvement.js +93 -0
- package/dist/phases/technical-design-verification/verifier.d.ts +53 -0
- package/dist/phases/technical-design-verification/verifier.js +170 -0
- package/dist/services/feature-branches.d.ts +77 -0
- package/dist/services/feature-branches.js +205 -0
- package/dist/services/video/device-frames.d.ts +1 -1
- package/dist/services/video/device-frames.js +81 -3
- package/dist/services/video/index.d.ts +1 -1
- package/dist/services/video/index.js +12 -6
- package/dist/services/video/screenshot-generator.js +5 -8
- package/dist/services/video/video-assembler.js +2 -6
- package/dist/types/index.d.ts +2 -0
- package/dist/utils/validation.d.ts +11 -2
- package/dist/utils/validation.js +93 -6
- package/dist/workflow-runner/config/phase-configs.d.ts +5 -0
- package/dist/workflow-runner/config/phase-configs.js +120 -0
- package/dist/workflow-runner/core/feature-filter.d.ts +16 -0
- package/dist/workflow-runner/core/feature-filter.js +46 -0
- package/dist/workflow-runner/core/index.d.ts +8 -0
- package/dist/workflow-runner/core/index.js +12 -0
- package/dist/workflow-runner/core/pipeline-evaluator.d.ts +24 -0
- package/dist/workflow-runner/core/pipeline-evaluator.js +32 -0
- package/dist/workflow-runner/core/state-manager.d.ts +24 -0
- package/dist/workflow-runner/core/state-manager.js +42 -0
- package/dist/workflow-runner/core/workflow-logger.d.ts +20 -0
- package/dist/workflow-runner/core/workflow-logger.js +65 -0
- package/dist/workflow-runner/executors/phase-executor.d.ts +8 -0
- package/dist/workflow-runner/executors/phase-executor.js +248 -0
- package/dist/workflow-runner/feature-workflow-runner.d.ts +26 -0
- package/dist/workflow-runner/feature-workflow-runner.js +119 -0
- package/dist/workflow-runner/index.d.ts +2 -0
- package/dist/workflow-runner/index.js +2 -0
- package/dist/workflow-runner/pipeline-runner.d.ts +17 -0
- package/dist/workflow-runner/pipeline-runner.js +393 -0
- package/dist/workflow-runner/workflow-processor.d.ts +54 -0
- package/dist/workflow-runner/workflow-processor.js +170 -0
- package/package.json +2 -2
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +0 -4
- package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +0 -133
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +0 -4
- package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +0 -336
- package/dist/services/lifecycle-agent/index.d.ts +0 -24
- package/dist/services/lifecycle-agent/index.js +0 -25
- package/dist/services/lifecycle-agent/phase-criteria.d.ts +0 -57
- package/dist/services/lifecycle-agent/phase-criteria.js +0 -335
- package/dist/services/lifecycle-agent/transition-rules.d.ts +0 -60
- package/dist/services/lifecycle-agent/transition-rules.js +0 -184
- package/dist/services/lifecycle-agent/types.d.ts +0 -190
- package/dist/services/lifecycle-agent/types.js +0 -12
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Feature Branches service for pipeline integration
|
|
3
|
+
* Allows phases to manage feature branches via MCP
|
|
4
|
+
*/
|
|
5
|
+
import { callMcpEndpoint } from '../api/mcp-client.js';
|
|
6
|
+
/**
|
|
7
|
+
* List all branches for a feature
|
|
8
|
+
*/
|
|
9
|
+
export async function getFeatureBranches(options) {
|
|
10
|
+
const { featureId, verbose } = options;
|
|
11
|
+
if (verbose) {
|
|
12
|
+
console.log(`📋 Fetching feature branches for feature: ${featureId}`);
|
|
13
|
+
}
|
|
14
|
+
const result = (await callMcpEndpoint('feature_branches/list', {
|
|
15
|
+
feature_id: featureId,
|
|
16
|
+
}));
|
|
17
|
+
const branches = result?.feature_branches || [];
|
|
18
|
+
if (verbose) {
|
|
19
|
+
console.log(`✅ Found ${branches.length} feature branches`);
|
|
20
|
+
}
|
|
21
|
+
return branches;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get the current active branch for a feature
|
|
25
|
+
*/
|
|
26
|
+
export async function getCurrentBranch(options) {
|
|
27
|
+
const { featureId, verbose } = options;
|
|
28
|
+
if (verbose) {
|
|
29
|
+
console.log(`📋 Getting current branch for feature: ${featureId}`);
|
|
30
|
+
}
|
|
31
|
+
const result = (await callMcpEndpoint('feature_branches/current', {
|
|
32
|
+
feature_id: featureId,
|
|
33
|
+
}));
|
|
34
|
+
return result?.current_branch || null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create feature branches
|
|
38
|
+
*/
|
|
39
|
+
export async function createFeatureBranches(options, branches) {
|
|
40
|
+
const { featureId, verbose } = options;
|
|
41
|
+
if (verbose) {
|
|
42
|
+
console.log(`📋 Creating ${branches.length} feature branches for feature: ${featureId}`);
|
|
43
|
+
}
|
|
44
|
+
const result = (await callMcpEndpoint('feature_branches/create', {
|
|
45
|
+
feature_id: featureId,
|
|
46
|
+
branches: branches,
|
|
47
|
+
}));
|
|
48
|
+
const createdBranches = result?.created_branches || [];
|
|
49
|
+
if (verbose) {
|
|
50
|
+
console.log(`✅ Created ${createdBranches.length} feature branches`);
|
|
51
|
+
createdBranches.forEach((b, idx) => {
|
|
52
|
+
console.log(` ${idx + 1}. ${b.name} (status: ${b.status})`);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return createdBranches;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Update a feature branch
|
|
59
|
+
*/
|
|
60
|
+
export async function updateFeatureBranch(branchId, updates, verbose) {
|
|
61
|
+
if (verbose) {
|
|
62
|
+
console.log(`📋 Updating feature branch: ${branchId}`);
|
|
63
|
+
}
|
|
64
|
+
const result = (await callMcpEndpoint('feature_branches/update', {
|
|
65
|
+
branch_id: branchId,
|
|
66
|
+
...updates,
|
|
67
|
+
}));
|
|
68
|
+
if (verbose) {
|
|
69
|
+
console.log(`✅ Feature branch updated successfully`);
|
|
70
|
+
}
|
|
71
|
+
return result?.feature_branch;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Clear all branches for a feature (used before re-planning)
|
|
75
|
+
*/
|
|
76
|
+
export async function clearFeatureBranches(options, force = false) {
|
|
77
|
+
const { featureId, verbose } = options;
|
|
78
|
+
if (verbose) {
|
|
79
|
+
console.log(`📋 Clearing feature branches for feature: ${featureId}`);
|
|
80
|
+
}
|
|
81
|
+
const result = (await callMcpEndpoint('feature_branches/clear', {
|
|
82
|
+
feature_id: featureId,
|
|
83
|
+
force: force,
|
|
84
|
+
}));
|
|
85
|
+
const deletedCount = result?.deleted_count || 0;
|
|
86
|
+
if (verbose) {
|
|
87
|
+
console.log(`✅ Cleared ${deletedCount} feature branches`);
|
|
88
|
+
}
|
|
89
|
+
return deletedCount;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Format feature branches for context (to include in prompts)
|
|
93
|
+
*/
|
|
94
|
+
export function formatBranchesForContext(branches) {
|
|
95
|
+
if (!branches || branches.length === 0) {
|
|
96
|
+
return 'No feature branches defined yet.';
|
|
97
|
+
}
|
|
98
|
+
const branchList = branches
|
|
99
|
+
.map((b, idx) => {
|
|
100
|
+
const statusEmoji = b.status === 'merged'
|
|
101
|
+
? '✅'
|
|
102
|
+
: b.status === 'in_progress'
|
|
103
|
+
? '🔄'
|
|
104
|
+
: b.status === 'ready_for_review'
|
|
105
|
+
? '👀'
|
|
106
|
+
: b.status === 'closed'
|
|
107
|
+
? '❌'
|
|
108
|
+
: '⏳';
|
|
109
|
+
return `${idx + 1}. **${b.name}** ${statusEmoji}
|
|
110
|
+
- Status: ${b.status}
|
|
111
|
+
- Branch: ${b.branch_name || 'Not created'}
|
|
112
|
+
- PR: ${b.pull_request_url || 'Not created'}
|
|
113
|
+
- Description: ${b.description || 'No description'}`;
|
|
114
|
+
})
|
|
115
|
+
.join('\n\n');
|
|
116
|
+
return `# Feature Branches
|
|
117
|
+
|
|
118
|
+
${branchList}
|
|
119
|
+
|
|
120
|
+
Total: ${branches.length} branches
|
|
121
|
+
Merged: ${branches.filter((b) => b.status === 'merged').length}
|
|
122
|
+
In Progress: ${branches.filter((b) => b.status === 'in_progress').length}
|
|
123
|
+
Pending: ${branches.filter((b) => b.status === 'pending').length}`;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Check if feature has multiple branches planned
|
|
127
|
+
*/
|
|
128
|
+
export async function hasMultipleBranches(options) {
|
|
129
|
+
const branches = await getFeatureBranches(options);
|
|
130
|
+
return branches.length > 1;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Get next pending branch to work on
|
|
134
|
+
*/
|
|
135
|
+
export async function getNextPendingBranch(options) {
|
|
136
|
+
const branches = await getFeatureBranches(options);
|
|
137
|
+
return branches.find((b) => b.status === 'pending') || null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Check if all branches are completed
|
|
141
|
+
*/
|
|
142
|
+
export async function allBranchesCompleted(options) {
|
|
143
|
+
const branches = await getFeatureBranches(options);
|
|
144
|
+
if (branches.length === 0)
|
|
145
|
+
return true;
|
|
146
|
+
return branches.every((b) => b.status === 'merged' || b.status === 'closed');
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Get the base branch information for a feature branch.
|
|
150
|
+
* Returns:
|
|
151
|
+
* - If base_branch_id is set and that branch is merged: use main
|
|
152
|
+
* - If base_branch_id is set and that branch is not merged: use that branch's branch_name
|
|
153
|
+
* - If base_branch_id is null: use main
|
|
154
|
+
*/
|
|
155
|
+
export async function getBaseBranchInfo(branch, allBranches, mainBranch = 'main') {
|
|
156
|
+
// No base branch - start from main
|
|
157
|
+
if (!branch.base_branch_id) {
|
|
158
|
+
return {
|
|
159
|
+
baseBranch: mainBranch,
|
|
160
|
+
needsRebase: false,
|
|
161
|
+
baseBranchMerged: true,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// Find the base branch
|
|
165
|
+
const baseBranch = allBranches.find((b) => b.id === branch.base_branch_id);
|
|
166
|
+
if (!baseBranch) {
|
|
167
|
+
// Base branch not found - fall back to main
|
|
168
|
+
return {
|
|
169
|
+
baseBranch: mainBranch,
|
|
170
|
+
needsRebase: false,
|
|
171
|
+
baseBranchMerged: true,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
// Check if base branch is merged
|
|
175
|
+
if (baseBranch.status === 'merged') {
|
|
176
|
+
// Base branch is merged to main - we should base on main and rebase if needed
|
|
177
|
+
return { baseBranch: mainBranch, needsRebase: true, baseBranchMerged: true };
|
|
178
|
+
}
|
|
179
|
+
// Base branch is not merged - we should base on that branch
|
|
180
|
+
if (!baseBranch.branch_name) {
|
|
181
|
+
// Base branch doesn't have a git branch yet - fall back to main
|
|
182
|
+
return {
|
|
183
|
+
baseBranch: mainBranch,
|
|
184
|
+
needsRebase: false,
|
|
185
|
+
baseBranchMerged: false,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
baseBranch: baseBranch.branch_name,
|
|
190
|
+
needsRebase: false,
|
|
191
|
+
baseBranchMerged: false,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get branch by ID
|
|
196
|
+
*/
|
|
197
|
+
export async function getBranchById(branchId, verbose) {
|
|
198
|
+
if (verbose) {
|
|
199
|
+
console.log(`📋 Fetching feature branch: ${branchId}`);
|
|
200
|
+
}
|
|
201
|
+
const result = (await callMcpEndpoint('feature_branches/get', {
|
|
202
|
+
branch_id: branchId,
|
|
203
|
+
}));
|
|
204
|
+
return result?.feature_branch || null;
|
|
205
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Wraps raw screenshots in realistic device mockups (MacBook, iPhone, browser window).
|
|
4
4
|
*/
|
|
5
5
|
/** Supported device frame types */
|
|
6
|
-
export type DeviceFrameType = 'macbook' | 'iphone' | 'browser' | 'none';
|
|
6
|
+
export type DeviceFrameType = 'macbook' | 'iphone' | 'ipad' | 'browser' | 'none';
|
|
7
7
|
/** Device presets with recommended viewport sizes */
|
|
8
8
|
export declare const DEVICE_PRESETS: Record<Exclude<DeviceFrameType, 'none'>, {
|
|
9
9
|
viewportWidth: number;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
export const DEVICE_PRESETS = {
|
|
7
7
|
macbook: { viewportWidth: 1280, viewportHeight: 800, label: 'MacBook Pro' },
|
|
8
8
|
iphone: { viewportWidth: 390, viewportHeight: 844, label: 'iPhone 15 Pro' },
|
|
9
|
+
ipad: { viewportWidth: 1024, viewportHeight: 1366, label: 'iPad Pro 12.9"' },
|
|
9
10
|
browser: {
|
|
10
11
|
viewportWidth: 1280,
|
|
11
12
|
viewportHeight: 720,
|
|
@@ -27,6 +28,8 @@ export function generateDeviceFrameHtml(screenshotDataUrl, deviceType, options)
|
|
|
27
28
|
return generateMacBookFrame(screenshotDataUrl, background, shadow, canvasWidth ?? 1600, canvasHeight ?? 1000);
|
|
28
29
|
case 'iphone':
|
|
29
30
|
return generateIPhoneFrame(screenshotDataUrl, background, shadow, canvasWidth ?? 800, canvasHeight ?? 1200);
|
|
31
|
+
case 'ipad':
|
|
32
|
+
return generateIPadFrame(screenshotDataUrl, background, shadow, canvasWidth ?? 900, canvasHeight ?? 1200);
|
|
30
33
|
case 'browser':
|
|
31
34
|
return generateBrowserFrame({
|
|
32
35
|
screenshotDataUrl,
|
|
@@ -59,7 +62,7 @@ function generateMacBookFrame(screenshotDataUrl, background, shadow, canvasWidth
|
|
|
59
62
|
}
|
|
60
63
|
.macbook {
|
|
61
64
|
position: relative;
|
|
62
|
-
width:
|
|
65
|
+
width: 84%;
|
|
63
66
|
}
|
|
64
67
|
.screen {
|
|
65
68
|
background: #1a1a1a;
|
|
@@ -148,7 +151,7 @@ function generateIPhoneFrame(screenshotDataUrl, background, shadow, canvasWidth,
|
|
|
148
151
|
}
|
|
149
152
|
.iphone {
|
|
150
153
|
position: relative;
|
|
151
|
-
width:
|
|
154
|
+
width: 86%;
|
|
152
155
|
background: #1a1a1a;
|
|
153
156
|
border-radius: 54px;
|
|
154
157
|
padding: 16px;
|
|
@@ -246,6 +249,81 @@ function generateIPhoneFrame(screenshotDataUrl, background, shadow, canvasWidth,
|
|
|
246
249
|
</body>
|
|
247
250
|
</html>`;
|
|
248
251
|
}
|
|
252
|
+
function generateIPadFrame(screenshotDataUrl, background, shadow, canvasWidth, canvasHeight) {
|
|
253
|
+
return `<!DOCTYPE html>
|
|
254
|
+
<html>
|
|
255
|
+
<head>
|
|
256
|
+
<style>
|
|
257
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
258
|
+
body {
|
|
259
|
+
width: ${canvasWidth}px;
|
|
260
|
+
height: ${canvasHeight}px;
|
|
261
|
+
display: flex;
|
|
262
|
+
align-items: center;
|
|
263
|
+
justify-content: center;
|
|
264
|
+
background: ${background};
|
|
265
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
266
|
+
overflow: hidden;
|
|
267
|
+
}
|
|
268
|
+
.ipad {
|
|
269
|
+
position: relative;
|
|
270
|
+
width: 78%;
|
|
271
|
+
background: #1a1a1a;
|
|
272
|
+
border-radius: 28px;
|
|
273
|
+
padding: 14px;
|
|
274
|
+
${shadow ? 'box-shadow: 0 30px 80px rgba(0,0,0,0.5), inset 0 0 0 2px #333;' : ''}
|
|
275
|
+
}
|
|
276
|
+
.ipad-inner {
|
|
277
|
+
background: #000;
|
|
278
|
+
border-radius: 18px;
|
|
279
|
+
overflow: hidden;
|
|
280
|
+
position: relative;
|
|
281
|
+
}
|
|
282
|
+
.front-camera {
|
|
283
|
+
position: absolute;
|
|
284
|
+
top: 8px;
|
|
285
|
+
left: 50%;
|
|
286
|
+
transform: translateX(-50%);
|
|
287
|
+
width: 8px;
|
|
288
|
+
height: 8px;
|
|
289
|
+
background: #1a1a2a;
|
|
290
|
+
border-radius: 50%;
|
|
291
|
+
z-index: 10;
|
|
292
|
+
}
|
|
293
|
+
.screen-content {
|
|
294
|
+
position: relative;
|
|
295
|
+
}
|
|
296
|
+
.screen-content img {
|
|
297
|
+
width: 100%;
|
|
298
|
+
display: block;
|
|
299
|
+
border-radius: 18px;
|
|
300
|
+
}
|
|
301
|
+
.home-indicator {
|
|
302
|
+
position: absolute;
|
|
303
|
+
bottom: 6px;
|
|
304
|
+
left: 50%;
|
|
305
|
+
transform: translateX(-50%);
|
|
306
|
+
width: 160px;
|
|
307
|
+
height: 5px;
|
|
308
|
+
background: rgba(255,255,255,0.3);
|
|
309
|
+
border-radius: 3px;
|
|
310
|
+
z-index: 10;
|
|
311
|
+
}
|
|
312
|
+
</style>
|
|
313
|
+
</head>
|
|
314
|
+
<body>
|
|
315
|
+
<div class="ipad">
|
|
316
|
+
<div class="ipad-inner">
|
|
317
|
+
<div class="front-camera"></div>
|
|
318
|
+
<div class="screen-content">
|
|
319
|
+
<img src="${screenshotDataUrl}" alt="screenshot" />
|
|
320
|
+
</div>
|
|
321
|
+
<div class="home-indicator"></div>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
</body>
|
|
325
|
+
</html>`;
|
|
326
|
+
}
|
|
249
327
|
function generateBrowserFrame(opts) {
|
|
250
328
|
const { screenshotDataUrl, background, shadow, title, url, canvasWidth, canvasHeight, } = opts;
|
|
251
329
|
return `<!DOCTYPE html>
|
|
@@ -264,7 +342,7 @@ function generateBrowserFrame(opts) {
|
|
|
264
342
|
overflow: hidden;
|
|
265
343
|
}
|
|
266
344
|
.browser {
|
|
267
|
-
width:
|
|
345
|
+
width: 88%;
|
|
268
346
|
background: #fff;
|
|
269
347
|
border-radius: 12px;
|
|
270
348
|
overflow: hidden;
|
|
@@ -46,7 +46,7 @@ export interface VideoGenerationOptions {
|
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* Check system dependencies for video generation.
|
|
49
|
-
* Returns a list of missing dependencies.
|
|
49
|
+
* Auto-installs Playwright if missing. Returns a list of still-missing dependencies.
|
|
50
50
|
*/
|
|
51
51
|
export declare function checkVideoDependencies(): Promise<string[]>;
|
|
52
52
|
/**
|
|
@@ -5,22 +5,28 @@
|
|
|
5
5
|
import { mkdir, rm } from 'fs/promises';
|
|
6
6
|
import { join } from 'path';
|
|
7
7
|
import { logError, logInfo, logSuccess, logWarning, } from '../../utils/logger.js';
|
|
8
|
+
import { ensureFFmpeg, ensurePlaywright } from '../../utils/validation.js';
|
|
8
9
|
import { captureStaticScreenshots, getVideoTempDir, isPlaywrightAvailable, } from './screenshot-generator.js';
|
|
9
10
|
import { generateNarration } from './tts-generator.js';
|
|
10
11
|
import { assembleVideo, isFfmpegAvailable } from './video-assembler.js';
|
|
11
12
|
import { saveVideoMetadata, uploadGrowthVideo } from './video-uploader.js';
|
|
12
13
|
/**
|
|
13
14
|
* Check system dependencies for video generation.
|
|
14
|
-
* Returns a list of missing dependencies.
|
|
15
|
+
* Auto-installs Playwright if missing. Returns a list of still-missing dependencies.
|
|
15
16
|
*/
|
|
16
17
|
export async function checkVideoDependencies() {
|
|
17
18
|
const missing = [];
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
try {
|
|
20
|
+
ensureFFmpeg(isFfmpegAvailable);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
missing.push('ffmpeg (auto-install failed — install manually: brew install ffmpeg / apt install ffmpeg)');
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
await ensurePlaywright(isPlaywrightAvailable);
|
|
20
27
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
missing.push('playwright (install: npm install playwright && npx playwright install chromium)');
|
|
28
|
+
catch {
|
|
29
|
+
missing.push('playwright (auto-install failed — run: npm install playwright && npx playwright install chromium)');
|
|
24
30
|
}
|
|
25
31
|
return missing;
|
|
26
32
|
}
|
|
@@ -7,6 +7,7 @@ import { mkdir, readFile } from 'fs/promises';
|
|
|
7
7
|
import { tmpdir } from 'os';
|
|
8
8
|
import { join } from 'path';
|
|
9
9
|
import { logError, logInfo, logWarning } from '../../utils/logger.js';
|
|
10
|
+
import { ensurePlaywright } from '../../utils/validation.js';
|
|
10
11
|
import { generateDeviceFrameHtml, } from './device-frames.js';
|
|
11
12
|
/**
|
|
12
13
|
* Dynamically load Playwright's chromium launcher.
|
|
@@ -101,10 +102,8 @@ async function applyDeviceFrame(screenshotPath, scene, browser, verbose) {
|
|
|
101
102
|
* Requires Playwright to be installed: npm install playwright
|
|
102
103
|
*/
|
|
103
104
|
export async function captureScreenshots(scenes, options, verbose) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
throw new Error('Playwright is not installed. Install it with: npm install playwright && npx playwright install chromium');
|
|
107
|
-
}
|
|
105
|
+
await ensurePlaywright(isPlaywrightAvailable);
|
|
106
|
+
const chromium = (await loadChromium());
|
|
108
107
|
const { baseUrl, outputDir, viewportWidth = 1280, viewportHeight = 720, fullPage = false, } = options;
|
|
109
108
|
// Ensure output directory exists
|
|
110
109
|
await mkdir(outputDir, { recursive: true });
|
|
@@ -194,10 +193,8 @@ export async function captureScreenshots(scenes, options, verbose) {
|
|
|
194
193
|
* Creates screenshots from pre-built HTML templates with mock data.
|
|
195
194
|
*/
|
|
196
195
|
export async function captureStaticScreenshots(scenes, outputDir, htmlTemplates, verbose) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
throw new Error('Playwright is not installed. Install it with: npm install playwright && npx playwright install chromium');
|
|
200
|
-
}
|
|
196
|
+
await ensurePlaywright(isPlaywrightAvailable);
|
|
197
|
+
const chromium = (await loadChromium());
|
|
201
198
|
await mkdir(outputDir, { recursive: true });
|
|
202
199
|
if (verbose) {
|
|
203
200
|
logInfo(`Rendering ${scenes.length} static HTML scenes`);
|
|
@@ -6,6 +6,7 @@ import { execFileSync, execSync } from 'child_process';
|
|
|
6
6
|
import { mkdir, writeFile } from 'fs/promises';
|
|
7
7
|
import { join } from 'path';
|
|
8
8
|
import { logError, logInfo, logWarning } from '../../utils/logger.js';
|
|
9
|
+
import { ensureFFmpeg } from '../../utils/validation.js';
|
|
9
10
|
import { getAudioDuration } from './tts-generator.js';
|
|
10
11
|
/**
|
|
11
12
|
* Check if ffmpeg is available on the system
|
|
@@ -146,12 +147,7 @@ async function simpleConcatenate(segmentPaths, outputPath, tempDir, verbose) {
|
|
|
146
147
|
*/
|
|
147
148
|
// eslint-disable-next-line complexity -- sequential video assembly with scene processing and ffmpeg orchestration
|
|
148
149
|
export async function assembleVideo(scenes, audioFiles, options, verbose) {
|
|
149
|
-
|
|
150
|
-
throw new Error('ffmpeg is not installed. Install it:\n' +
|
|
151
|
-
' macOS: brew install ffmpeg\n' +
|
|
152
|
-
' Ubuntu: sudo apt install ffmpeg\n' +
|
|
153
|
-
' Windows: choco install ffmpeg');
|
|
154
|
-
}
|
|
150
|
+
ensureFFmpeg(isFfmpegAvailable);
|
|
155
151
|
const { outputPath, width: optWidth, height: optHeight, fps = 30, codec = 'libx264', preset = 'medium', crossfadeDuration = 0.5, } = options;
|
|
156
152
|
// Ensure output directory exists
|
|
157
153
|
const outputDir = join(outputPath, '..');
|
package/dist/types/index.d.ts
CHANGED
|
@@ -21,6 +21,15 @@ export declare const validateMCPEnvironment: () => {
|
|
|
21
21
|
*/
|
|
22
22
|
export declare const validateCommandEnvironment: (options: CliOptions) => ValidationResult;
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* Ensure Playwright is installed, auto-installing if necessary.
|
|
25
|
+
* Installs the npm package and Chromium browser when missing.
|
|
25
26
|
*/
|
|
26
|
-
export declare const
|
|
27
|
+
export declare const ensurePlaywright: (checkFunction: () => Promise<boolean>) => Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Ensure an npm package is installed, auto-installing if necessary.
|
|
30
|
+
*/
|
|
31
|
+
export declare const ensureNpmPackage: (checkFunction: () => Promise<boolean>, packageName: string) => Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Ensure ffmpeg is installed, auto-installing via Homebrew on macOS if possible.
|
|
34
|
+
*/
|
|
35
|
+
export declare const ensureFFmpeg: (checkFunction: () => boolean) => void;
|
package/dist/utils/validation.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
1
2
|
import { getMcpServerUrl, getMcpToken } from '../auth/auth-store.js';
|
|
2
3
|
import { loadConfig, validateConfig } from '../config.js';
|
|
3
|
-
import { logInfo } from './logger.js';
|
|
4
|
+
import { logInfo, logWarning } from './logger.js';
|
|
4
5
|
/**
|
|
5
6
|
* Common configuration validation for all CLI commands
|
|
6
7
|
*/
|
|
@@ -49,11 +50,97 @@ export const validateCommandEnvironment = (options) => {
|
|
|
49
50
|
};
|
|
50
51
|
};
|
|
51
52
|
/**
|
|
52
|
-
*
|
|
53
|
+
* Ensure Playwright is installed, auto-installing if necessary.
|
|
54
|
+
* Installs the npm package and Chromium browser when missing.
|
|
53
55
|
*/
|
|
54
|
-
export const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
throw new Error(errorMessage);
|
|
56
|
+
export const ensurePlaywright = async (checkFunction) => {
|
|
57
|
+
if (await checkFunction()) {
|
|
58
|
+
return;
|
|
58
59
|
}
|
|
60
|
+
logWarning('Playwright is not installed. Installing automatically (this may take a minute)...');
|
|
61
|
+
try {
|
|
62
|
+
logInfo(' Installing playwright package...');
|
|
63
|
+
execSync('npm install playwright', { stdio: 'inherit' });
|
|
64
|
+
logInfo(' Installing Chromium browser...');
|
|
65
|
+
execSync('npx playwright install chromium', { stdio: 'inherit' });
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
throw new Error('Failed to auto-install Playwright.\n\n' +
|
|
69
|
+
' Please install it manually:\n\n' +
|
|
70
|
+
' npm install playwright\n' +
|
|
71
|
+
' npx playwright install chromium\n\n' +
|
|
72
|
+
' Then re-run this command. Use --listings-only to skip screenshots.');
|
|
73
|
+
}
|
|
74
|
+
// Verify installation succeeded
|
|
75
|
+
if (!(await checkFunction())) {
|
|
76
|
+
throw new Error('Playwright was installed but is still not available.\n\n' +
|
|
77
|
+
' Try installing manually:\n\n' +
|
|
78
|
+
' npm install playwright\n' +
|
|
79
|
+
' npx playwright install chromium\n\n' +
|
|
80
|
+
' Then re-run this command. Use --listings-only to skip screenshots.');
|
|
81
|
+
}
|
|
82
|
+
logInfo(' Playwright installed successfully!');
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Ensure an npm package is installed, auto-installing if necessary.
|
|
86
|
+
*/
|
|
87
|
+
export const ensureNpmPackage = async (checkFunction, packageName) => {
|
|
88
|
+
if (await checkFunction()) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
logWarning(`${packageName} is not installed. Installing automatically...`);
|
|
92
|
+
try {
|
|
93
|
+
execSync(`npm install ${packageName}`, { stdio: 'inherit' });
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
throw new Error(`Failed to auto-install ${packageName}.\n\n` +
|
|
97
|
+
` Please install it manually:\n\n` +
|
|
98
|
+
` npm install ${packageName}`);
|
|
99
|
+
}
|
|
100
|
+
if (!(await checkFunction())) {
|
|
101
|
+
throw new Error(`${packageName} was installed but is still not available.\n\n` +
|
|
102
|
+
` Try installing manually:\n\n` +
|
|
103
|
+
` npm install ${packageName}`);
|
|
104
|
+
}
|
|
105
|
+
logInfo(` ${packageName} installed successfully!`);
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Ensure ffmpeg is installed, auto-installing via Homebrew on macOS if possible.
|
|
109
|
+
*/
|
|
110
|
+
export const ensureFFmpeg = (checkFunction) => {
|
|
111
|
+
if (checkFunction()) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// Attempt auto-install on macOS via Homebrew
|
|
115
|
+
if (process.platform === 'darwin') {
|
|
116
|
+
try {
|
|
117
|
+
execSync('which brew', { stdio: 'ignore' });
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
throw new Error('ffmpeg is not installed and Homebrew was not found.\n\n' +
|
|
121
|
+
' Install Homebrew first (https://brew.sh), then run:\n\n' +
|
|
122
|
+
' brew install ffmpeg');
|
|
123
|
+
}
|
|
124
|
+
logWarning('ffmpeg is not installed. Installing via Homebrew (this may take a few minutes)...');
|
|
125
|
+
try {
|
|
126
|
+
execSync('brew install ffmpeg', { stdio: 'inherit' });
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
throw new Error('Failed to auto-install ffmpeg via Homebrew.\n\n' +
|
|
130
|
+
' Please install it manually:\n\n' +
|
|
131
|
+
' brew install ffmpeg');
|
|
132
|
+
}
|
|
133
|
+
if (!checkFunction()) {
|
|
134
|
+
throw new Error('ffmpeg was installed but is still not available.\n\n' +
|
|
135
|
+
' Try installing manually:\n\n' +
|
|
136
|
+
' brew install ffmpeg');
|
|
137
|
+
}
|
|
138
|
+
logInfo(' ffmpeg installed successfully!');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Non-macOS: provide platform-specific instructions
|
|
142
|
+
throw new Error('ffmpeg is not installed. Install it:\n' +
|
|
143
|
+
' Ubuntu/Debian: sudo apt install ffmpeg\n' +
|
|
144
|
+
' Fedora: sudo dnf install ffmpeg\n' +
|
|
145
|
+
' Windows: choco install ffmpeg');
|
|
59
146
|
};
|