edsger 0.31.1 → 0.32.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.
Files changed (122) hide show
  1. package/dist/api/app-store.d.ts +76 -0
  2. package/dist/api/app-store.js +148 -0
  3. package/dist/api/github.js +16 -23
  4. package/dist/api/mcp-client.js +5 -10
  5. package/dist/auth/login.js +18 -18
  6. package/dist/commands/agent-workflow/index.js +6 -6
  7. package/dist/commands/agent-workflow/processor.d.ts +1 -1
  8. package/dist/commands/agent-workflow/processor.js +34 -6
  9. package/dist/commands/analyze-logs/index.d.ts +7 -0
  10. package/dist/commands/analyze-logs/index.js +34 -0
  11. package/dist/commands/app-store/index.d.ts +12 -0
  12. package/dist/commands/app-store/index.js +60 -0
  13. package/dist/commands/code-review/reviewer.js +14 -13
  14. package/dist/commands/growth-analysis/index.js +14 -1
  15. package/dist/commands/init/index.js +4 -4
  16. package/dist/commands/refactor/refactor.js +4 -4
  17. package/dist/commands/workflow/core/__tests__/feature-filter.test.d.ts +5 -0
  18. package/dist/commands/workflow/core/__tests__/feature-filter.test.js +325 -0
  19. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.d.ts +4 -0
  20. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.js +406 -0
  21. package/dist/commands/workflow/core/__tests__/state-manager.test.d.ts +4 -0
  22. package/dist/commands/workflow/core/__tests__/state-manager.test.js +384 -0
  23. package/dist/commands/workflow/core/workflow-logger.js +6 -6
  24. package/dist/commands/workflow/executors/phase-executor.js +19 -18
  25. package/dist/config/__tests__/config.test.d.ts +4 -0
  26. package/dist/config/__tests__/config.test.js +286 -0
  27. package/dist/config.js +12 -0
  28. package/dist/errors/__tests__/index.test.d.ts +4 -0
  29. package/dist/errors/__tests__/index.test.js +349 -0
  30. package/dist/errors/index.d.ts +62 -0
  31. package/dist/errors/index.js +116 -0
  32. package/dist/index.js +48 -0
  33. package/dist/phases/analyze-logs/agent.d.ts +11 -0
  34. package/dist/phases/analyze-logs/agent.js +63 -0
  35. package/dist/phases/analyze-logs/index.d.ts +23 -0
  36. package/dist/phases/analyze-logs/index.js +131 -0
  37. package/dist/phases/analyze-logs/prompts.d.ts +3 -0
  38. package/dist/phases/analyze-logs/prompts.js +59 -0
  39. package/dist/phases/app-store-generation/__tests__/agent.test.d.ts +5 -0
  40. package/dist/phases/app-store-generation/__tests__/agent.test.js +144 -0
  41. package/dist/phases/app-store-generation/__tests__/context.test.d.ts +4 -0
  42. package/dist/phases/app-store-generation/__tests__/context.test.js +280 -0
  43. package/dist/phases/app-store-generation/__tests__/prompts.test.d.ts +4 -0
  44. package/dist/phases/app-store-generation/__tests__/prompts.test.js +165 -0
  45. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.d.ts +5 -0
  46. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +407 -0
  47. package/dist/phases/app-store-generation/agent.d.ts +2 -0
  48. package/dist/phases/app-store-generation/agent.js +97 -0
  49. package/dist/phases/app-store-generation/context.d.ts +21 -0
  50. package/dist/phases/app-store-generation/context.js +77 -0
  51. package/dist/phases/app-store-generation/index.d.ts +19 -0
  52. package/dist/phases/app-store-generation/index.js +119 -0
  53. package/dist/phases/app-store-generation/prompts.d.ts +43 -0
  54. package/dist/phases/app-store-generation/prompts.js +134 -0
  55. package/dist/phases/app-store-generation/screenshot-composer.d.ts +42 -0
  56. package/dist/phases/app-store-generation/screenshot-composer.js +319 -0
  57. package/dist/phases/app-store-generation/uploader.d.ts +18 -0
  58. package/dist/phases/app-store-generation/uploader.js +119 -0
  59. package/dist/phases/autonomous/index.js +3 -7
  60. package/dist/phases/branch-planning/index.js +3 -7
  61. package/dist/phases/bug-fixing/analyzer.js +3 -7
  62. package/dist/phases/bug-fixing/mcp-server.js +2 -1
  63. package/dist/phases/chat-processor/index.js +5 -13
  64. package/dist/phases/code-implementation/index.d.ts +2 -2
  65. package/dist/phases/code-implementation/index.js +63 -86
  66. package/dist/phases/code-implementation-verification/agent.js +3 -7
  67. package/dist/phases/code-refine/context.js +19 -52
  68. package/dist/phases/code-refine/index.js +3 -7
  69. package/dist/phases/code-refine/retry-handler.js +9 -15
  70. package/dist/phases/code-review/context.js +16 -45
  71. package/dist/phases/code-review/index.js +2 -4
  72. package/dist/phases/code-testing/analyzer.js +7 -11
  73. package/dist/phases/feature-analysis/agent.js +3 -7
  74. package/dist/phases/feature-analysis-verification/agent.js +2 -4
  75. package/dist/phases/functional-testing/analyzer.js +3 -7
  76. package/dist/phases/functional-testing/mcp-server.js +3 -2
  77. package/dist/phases/functional-testing/test-retry-handler.js +8 -15
  78. package/dist/phases/growth-analysis/agent.js +3 -7
  79. package/dist/phases/growth-analysis/index.d.ts +6 -0
  80. package/dist/phases/growth-analysis/index.js +142 -2
  81. package/dist/phases/growth-analysis/prompts.js +70 -3
  82. package/dist/phases/pr-execution/index.js +3 -7
  83. package/dist/phases/pr-splitting/index.js +3 -7
  84. package/dist/phases/pull-request/creator.js +23 -66
  85. package/dist/phases/pull-request/handler.js +13 -32
  86. package/dist/phases/task/agent.js +3 -7
  87. package/dist/phases/technical-design/index.d.ts +4 -4
  88. package/dist/phases/technical-design/index.js +3 -7
  89. package/dist/phases/technical-design-verification/agent.js +2 -4
  90. package/dist/phases/test-cases-analysis/agent.js +3 -7
  91. package/dist/phases/user-stories-analysis/agent.js +3 -7
  92. package/dist/services/branches.js +12 -11
  93. package/dist/services/checklist.js +18 -17
  94. package/dist/services/feedbacks.js +11 -10
  95. package/dist/services/product-logs.d.ts +31 -0
  96. package/dist/services/product-logs.js +33 -0
  97. package/dist/services/pull-requests.js +10 -9
  98. package/dist/services/video/__tests__/video-pipeline.test.d.ts +6 -0
  99. package/dist/services/video/__tests__/video-pipeline.test.js +231 -0
  100. package/dist/services/video/device-frames.d.ts +30 -0
  101. package/dist/services/video/device-frames.js +422 -0
  102. package/dist/services/video/index.d.ts +66 -0
  103. package/dist/services/video/index.js +226 -0
  104. package/dist/services/video/retry.d.ts +20 -0
  105. package/dist/services/video/retry.js +73 -0
  106. package/dist/services/video/screenshot-generator.d.ts +67 -0
  107. package/dist/services/video/screenshot-generator.js +254 -0
  108. package/dist/services/video/tts-generator.d.ts +40 -0
  109. package/dist/services/video/tts-generator.js +121 -0
  110. package/dist/services/video/video-assembler.d.ts +45 -0
  111. package/dist/services/video/video-assembler.js +308 -0
  112. package/dist/services/video/video-uploader.d.ts +28 -0
  113. package/dist/services/video/video-uploader.js +105 -0
  114. package/dist/types/features.d.ts +2 -2
  115. package/dist/types/index.d.ts +48 -28
  116. package/dist/types/llm-responses.d.ts +127 -0
  117. package/dist/types/llm-responses.js +9 -0
  118. package/dist/types/pipeline.d.ts +1 -1
  119. package/dist/utils/logger.d.ts +4 -0
  120. package/dist/utils/logger.js +11 -0
  121. package/dist/utils/pipeline-logger.js +11 -8
  122. package/package.json +1 -1
@@ -0,0 +1,76 @@
1
+ export interface AppStoreConfig {
2
+ id: string;
3
+ product_id: string;
4
+ store_type: 'apple_app_store' | 'google_play';
5
+ credentials: Record<string, unknown>;
6
+ app_identifier: string | null;
7
+ listings: Record<string, AppStoreListing>;
8
+ screenshots: AppStoreScreenshot[];
9
+ current_version: string | null;
10
+ submission_status: string;
11
+ submitted_at: string | null;
12
+ released_at: string | null;
13
+ rejection_reason: string | null;
14
+ is_active: boolean;
15
+ created_by: string;
16
+ created_at: string;
17
+ updated_at: string;
18
+ }
19
+ export interface AppStoreListing {
20
+ app_name: string;
21
+ subtitle?: string;
22
+ short_description?: string;
23
+ description: string;
24
+ keywords?: string;
25
+ whats_new?: string;
26
+ privacy_policy_url?: string;
27
+ support_url?: string;
28
+ marketing_url?: string;
29
+ primary_category?: string;
30
+ secondary_category?: string;
31
+ }
32
+ export interface AppStoreScreenshot {
33
+ device_type: string;
34
+ display_order: number;
35
+ storage_url: string;
36
+ storage_path: string;
37
+ width: number;
38
+ height: number;
39
+ caption: string;
40
+ background_gradient: string;
41
+ device_frame: string;
42
+ locale: string;
43
+ }
44
+ /**
45
+ * Get app store configs for a product via MCP
46
+ */
47
+ export declare function getAppStoreConfigs(productId: string, verbose?: boolean): Promise<AppStoreConfig[]>;
48
+ /**
49
+ * Get a single app store config via MCP
50
+ */
51
+ export declare function getAppStoreConfig(configId: string, verbose?: boolean): Promise<AppStoreConfig | null>;
52
+ /**
53
+ * Create or update an app store config via MCP
54
+ */
55
+ export declare function upsertAppStoreConfig(params: {
56
+ product_id: string;
57
+ store_type: string;
58
+ credentials?: Record<string, unknown>;
59
+ app_identifier?: string;
60
+ }, verbose?: boolean): Promise<AppStoreConfig | null>;
61
+ /**
62
+ * Save listings to an app store config via MCP
63
+ */
64
+ export declare function saveAppStoreListings(configId: string, listings: Record<string, AppStoreListing>, verbose?: boolean): Promise<AppStoreConfig | null>;
65
+ /**
66
+ * Save screenshots to an app store config via MCP
67
+ */
68
+ export declare function saveAppStoreScreenshots(configId: string, screenshots: AppStoreScreenshot[], verbose?: boolean): Promise<AppStoreConfig | null>;
69
+ /**
70
+ * Update submission status via MCP
71
+ */
72
+ export declare function updateAppStoreStatus(configId: string, updates: {
73
+ submission_status?: string;
74
+ current_version?: string;
75
+ rejection_reason?: string;
76
+ }, verbose?: boolean): Promise<AppStoreConfig | null>;
@@ -0,0 +1,148 @@
1
+ import { logInfo, logError } from '../utils/logger.js';
2
+ import { callMcpEndpoint } from './mcp-client.js';
3
+ /**
4
+ * Get app store configs for a product via MCP
5
+ */
6
+ export async function getAppStoreConfigs(productId, verbose) {
7
+ if (verbose) {
8
+ logInfo(`Fetching app store configs for product: ${productId}`);
9
+ }
10
+ try {
11
+ const result = (await callMcpEndpoint('app_store/configs/list', {
12
+ product_id: productId,
13
+ }));
14
+ const text = result.content?.[0]?.text || '[]';
15
+ try {
16
+ return JSON.parse(text);
17
+ }
18
+ catch {
19
+ return [];
20
+ }
21
+ }
22
+ catch (error) {
23
+ if (verbose) {
24
+ logError(`Failed to fetch app store configs: ${error instanceof Error ? error.message : String(error)}`);
25
+ }
26
+ return [];
27
+ }
28
+ }
29
+ /**
30
+ * Get a single app store config via MCP
31
+ */
32
+ export async function getAppStoreConfig(configId, verbose) {
33
+ try {
34
+ const result = (await callMcpEndpoint('app_store/configs/get', {
35
+ config_id: configId,
36
+ }));
37
+ const text = result.content?.[0]?.text || 'null';
38
+ try {
39
+ return JSON.parse(text);
40
+ }
41
+ catch {
42
+ return null;
43
+ }
44
+ }
45
+ catch (error) {
46
+ if (verbose) {
47
+ logError(`Failed to fetch app store config: ${error instanceof Error ? error.message : String(error)}`);
48
+ }
49
+ return null;
50
+ }
51
+ }
52
+ /**
53
+ * Create or update an app store config via MCP
54
+ */
55
+ export async function upsertAppStoreConfig(params, verbose) {
56
+ if (verbose) {
57
+ logInfo(`Upserting app store config for ${params.store_type}`);
58
+ }
59
+ try {
60
+ const result = (await callMcpEndpoint('app_store/configs/upsert', params));
61
+ const text = result.content?.[0]?.text || 'null';
62
+ try {
63
+ return JSON.parse(text);
64
+ }
65
+ catch {
66
+ return null;
67
+ }
68
+ }
69
+ catch (error) {
70
+ logError(`Failed to upsert app store config: ${error instanceof Error ? error.message : String(error)}`);
71
+ return null;
72
+ }
73
+ }
74
+ /**
75
+ * Save listings to an app store config via MCP
76
+ */
77
+ export async function saveAppStoreListings(configId, listings, verbose) {
78
+ if (verbose) {
79
+ logInfo(`Saving listings for config: ${configId}`);
80
+ }
81
+ try {
82
+ const result = (await callMcpEndpoint('app_store/configs/save_listings', {
83
+ config_id: configId,
84
+ listings,
85
+ }));
86
+ const text = result.content?.[0]?.text || 'null';
87
+ try {
88
+ return JSON.parse(text);
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ }
94
+ catch (error) {
95
+ logError(`Failed to save listings: ${error instanceof Error ? error.message : String(error)}`);
96
+ return null;
97
+ }
98
+ }
99
+ /**
100
+ * Save screenshots to an app store config via MCP
101
+ */
102
+ export async function saveAppStoreScreenshots(configId, screenshots, verbose) {
103
+ if (verbose) {
104
+ logInfo(`Saving ${screenshots.length} screenshots for config: ${configId}`);
105
+ }
106
+ try {
107
+ const result = (await callMcpEndpoint('app_store/configs/save_screenshots', {
108
+ config_id: configId,
109
+ screenshots,
110
+ }));
111
+ const text = result.content?.[0]?.text || 'null';
112
+ try {
113
+ return JSON.parse(text);
114
+ }
115
+ catch {
116
+ return null;
117
+ }
118
+ }
119
+ catch (error) {
120
+ logError(`Failed to save screenshots: ${error instanceof Error ? error.message : String(error)}`);
121
+ return null;
122
+ }
123
+ }
124
+ /**
125
+ * Update submission status via MCP
126
+ */
127
+ export async function updateAppStoreStatus(configId, updates, verbose) {
128
+ if (verbose) {
129
+ logInfo(`Updating status for config: ${configId}`);
130
+ }
131
+ try {
132
+ const result = (await callMcpEndpoint('app_store/configs/update_status', {
133
+ config_id: configId,
134
+ ...updates,
135
+ }));
136
+ const text = result.content?.[0]?.text || 'null';
137
+ try {
138
+ return JSON.parse(text);
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ catch (error) {
145
+ logError(`Failed to update status: ${error instanceof Error ? error.message : String(error)}`);
146
+ return null;
147
+ }
148
+ }
@@ -3,31 +3,30 @@
3
3
  * Uses product developer configuration from the database
4
4
  */
5
5
  import { callMcpEndpoint } from './mcp-client.js';
6
+ import { logDebug, logSuccess, logWarning, logError } from '../utils/logger.js';
6
7
  /**
7
8
  * Get GitHub developer configuration for a feature
8
9
  * Returns installation_id, repository info from product_developers
9
10
  */
10
11
  export async function getGitHubDeveloperConfig(featureId, verbose) {
11
- if (verbose) {
12
- console.log(`🔍 Fetching GitHub developer config for feature: ${featureId}`);
13
- }
12
+ logDebug(`Fetching GitHub developer config for feature: ${featureId}`, verbose);
14
13
  try {
15
14
  const result = (await callMcpEndpoint('github/developer_config', {
16
15
  feature_id: featureId,
17
16
  }));
18
17
  if (verbose) {
19
18
  if (result.configured) {
20
- console.log(`✅ GitHub configured: ${result.repository_full_name}`);
19
+ logSuccess(`GitHub configured: ${result.repository_full_name}`);
21
20
  }
22
21
  else {
23
- console.log(`⚠️ GitHub not configured: ${result.message}`);
22
+ logWarning(`GitHub not configured: ${result.message}`);
24
23
  }
25
24
  }
26
25
  return result;
27
26
  }
28
27
  catch (error) {
29
28
  if (verbose) {
30
- console.error('❌ Failed to get GitHub developer config:', error);
29
+ logError(`Failed to get GitHub developer config: ${error instanceof Error ? error.message : String(error)}`);
31
30
  }
32
31
  return {
33
32
  configured: false,
@@ -39,22 +38,20 @@ export async function getGitHubDeveloperConfig(featureId, verbose) {
39
38
  * Get installation access token for GitHub API operations
40
39
  */
41
40
  export async function getGitHubInstallationToken(installationId, featureId, verbose) {
42
- if (verbose) {
43
- console.log(`🔑 Fetching GitHub installation token for: ${installationId}`);
44
- }
41
+ logDebug(`Fetching GitHub installation token for: ${installationId}`, verbose);
45
42
  try {
46
43
  const result = (await callMcpEndpoint('github/installation_token', {
47
44
  installation_id: installationId,
48
45
  feature_id: featureId,
49
46
  }));
50
47
  if (verbose) {
51
- console.log(`✅ GitHub token obtained, expires: ${result.expires_at}`);
48
+ logSuccess(`GitHub token obtained, expires: ${result.expires_at}`);
52
49
  }
53
50
  return result;
54
51
  }
55
52
  catch (error) {
56
53
  if (verbose) {
57
- console.error('❌ Failed to get GitHub installation token:', error);
54
+ logError(`Failed to get GitHub installation token: ${error instanceof Error ? error.message : String(error)}`);
58
55
  }
59
56
  return null;
60
57
  }
@@ -64,19 +61,17 @@ export async function getGitHubInstallationToken(installationId, featureId, verb
64
61
  * Used for product-level operations like growth analysis.
65
62
  */
66
63
  export async function getGitHubConfigByProduct(productId, verbose) {
67
- if (verbose) {
68
- console.log(`🔍 Fetching GitHub config for product: ${productId}`);
69
- }
64
+ logDebug(`Fetching GitHub config for product: ${productId}`, verbose);
70
65
  try {
71
66
  const result = (await callMcpEndpoint('github/config_and_token_by_product', {
72
67
  product_id: productId,
73
68
  }));
74
69
  if (verbose) {
75
70
  if (result.configured) {
76
- console.log(`✅ GitHub ready: ${result.repository_full_name}`);
71
+ logSuccess(`GitHub ready: ${result.repository_full_name}`);
77
72
  }
78
73
  else {
79
- console.log(`⚠️ GitHub not configured: ${result.message}`);
74
+ logWarning(`GitHub not configured: ${result.message}`);
80
75
  }
81
76
  }
82
77
  if (result.configured && result.token && result.owner && result.repo) {
@@ -95,7 +90,7 @@ export async function getGitHubConfigByProduct(productId, verbose) {
95
90
  }
96
91
  catch (error) {
97
92
  if (verbose) {
98
- console.error('❌ Failed to get GitHub config for product:', error);
93
+ logError(`Failed to get GitHub config for product: ${error instanceof Error ? error.message : String(error)}`);
99
94
  }
100
95
  return {
101
96
  configured: false,
@@ -108,19 +103,17 @@ export async function getGitHubConfigByProduct(productId, verbose) {
108
103
  * This is the main entry point for getting GitHub config
109
104
  */
110
105
  export async function getGitHubConfig(featureId, verbose) {
111
- if (verbose) {
112
- console.log(`🔍 Fetching GitHub config for feature: ${featureId}`);
113
- }
106
+ logDebug(`Fetching GitHub config for feature: ${featureId}`, verbose);
114
107
  try {
115
108
  const result = (await callMcpEndpoint('github/config_and_token', {
116
109
  feature_id: featureId,
117
110
  }));
118
111
  if (verbose) {
119
112
  if (result.configured) {
120
- console.log(`✅ GitHub ready: ${result.repository_full_name}`);
113
+ logSuccess(`GitHub ready: ${result.repository_full_name}`);
121
114
  }
122
115
  else {
123
- console.log(`⚠️ GitHub not configured: ${result.message}`);
116
+ logWarning(`GitHub not configured: ${result.message}`);
124
117
  }
125
118
  }
126
119
  if (result.configured && result.token && result.owner && result.repo) {
@@ -139,7 +132,7 @@ export async function getGitHubConfig(featureId, verbose) {
139
132
  }
140
133
  catch (error) {
141
134
  if (verbose) {
142
- console.error('❌ Failed to get GitHub config:', error);
135
+ logError(`Failed to get GitHub config: ${error instanceof Error ? error.message : String(error)}`);
143
136
  }
144
137
  return {
145
138
  configured: false,
@@ -7,6 +7,7 @@
7
7
  * Environment variables take precedence over stored auth.
8
8
  */
9
9
  import { getMcpServerUrl, getMcpToken } from '../auth/auth-store.js';
10
+ import { logDebug, logError } from '../utils/logger.js';
10
11
  /**
11
12
  * Helper function to make HTTP requests to the MCP server
12
13
  * Uses stored auth from `edsger login` or environment variables
@@ -44,7 +45,7 @@ export async function callMcpEndpoint(method, params) {
44
45
  return data.result;
45
46
  }
46
47
  catch (error) {
47
- console.error(`MCP call failed for ${method}:`, error instanceof Error ? error.message : String(error));
48
+ logError(`MCP call failed for ${method}: ${error instanceof Error ? error.message : String(error)}`);
48
49
  throw error;
49
50
  }
50
51
  }
@@ -53,21 +54,15 @@ export async function callMcpEndpoint(method, params) {
53
54
  */
54
55
  export async function makeMcpRequest(options) {
55
56
  const { method, params, verbose } = options;
56
- if (verbose) {
57
- console.log(`Making MCP request: ${method}`);
58
- }
57
+ logDebug(`Making MCP request: ${method}`, verbose);
59
58
  try {
60
59
  const result = await callMcpEndpoint(method, params);
61
- if (verbose) {
62
- console.log(`MCP request successful: ${method}`);
63
- }
60
+ logDebug(`MCP request successful: ${method}`, verbose);
64
61
  return result;
65
62
  }
66
63
  catch (error) {
67
64
  const errorMessage = error instanceof Error ? error.message : String(error);
68
- if (verbose) {
69
- console.error(`MCP request failed: ${method} - ${errorMessage}`);
70
- }
65
+ logDebug(`MCP request failed: ${method} - ${errorMessage}`, verbose);
71
66
  throw error;
72
67
  }
73
68
  }
@@ -8,7 +8,7 @@ import { createInterface } from 'readline';
8
8
  import { execSync } from 'child_process';
9
9
  import { platform } from 'os';
10
10
  import { saveAuth, loadAuth, clearAuth, getEdsgerBaseUrl, getAuthFilePath } from './auth-store.js';
11
- import { logInfo, logSuccess, logError, logWarning } from '../utils/logger.js';
11
+ import { logInfo, logSuccess, logError, logWarning, logRaw } from '../utils/logger.js';
12
12
  /**
13
13
  * Open a URL in the user's default browser
14
14
  */
@@ -65,7 +65,7 @@ export async function runLogin() {
65
65
  const baseUrl = getEdsgerBaseUrl();
66
66
  const authUrl = `${baseUrl}/cli/auth`;
67
67
  logInfo('Starting Edsger CLI login...');
68
- console.log();
68
+ logRaw('');
69
69
  // Check if already logged in
70
70
  const existingAuth = loadAuth();
71
71
  if (existingAuth) {
@@ -77,19 +77,19 @@ export async function runLogin() {
77
77
  }
78
78
  }
79
79
  // Open browser
80
- console.log(` Opening browser to: ${authUrl}`);
81
- console.log();
80
+ logInfo(`Opening browser to: ${authUrl}`);
81
+ logRaw('');
82
82
  const opened = openBrowser(authUrl);
83
83
  if (!opened) {
84
84
  logWarning(`Could not open browser automatically.`);
85
- console.log(` Please open this URL manually:`);
86
- console.log(` ${authUrl}`);
87
- console.log();
85
+ logInfo('Please open this URL manually:');
86
+ logRaw(` ${authUrl}`);
87
+ logRaw('');
88
88
  }
89
- console.log(' 1. Sign in to edsger.ai (if not already signed in)');
90
- console.log(' 2. Click "Generate CLI Token" on the page');
91
- console.log(' 3. Copy the CLI Token and paste it below');
92
- console.log();
89
+ logInfo('1. Sign in to edsger.ai (if not already signed in)');
90
+ logInfo('2. Click "Generate CLI Token" on the page');
91
+ logInfo('3. Copy the CLI Token and paste it below');
92
+ logRaw('');
93
93
  const mcpServerUrl = process.env.EDSGER_MCP_SERVER_URL || MCP_SERVER_URL;
94
94
  // Read token
95
95
  const mcpToken = await readLine('CLI Token: ');
@@ -130,19 +130,19 @@ export async function runLogin() {
130
130
  loggedInAt: new Date().toISOString(),
131
131
  edsgerBaseUrl: baseUrl,
132
132
  });
133
- console.log();
133
+ logRaw('');
134
134
  logSuccess('Login successful!');
135
135
  logInfo(`Credentials saved to ${getAuthFilePath()}`);
136
136
  // Show available products
137
137
  const products = data.result?.products || [];
138
138
  if (products.length > 0) {
139
- console.log();
139
+ logRaw('');
140
140
  logInfo(`You have access to ${products.length} product(s):`);
141
141
  products.forEach((p, i) => {
142
- console.log(` ${i + 1}. ${p.name} (${p.id})`);
142
+ logRaw(` ${i + 1}. ${p.name} (${p.id})`);
143
143
  });
144
144
  }
145
- console.log();
145
+ logRaw('');
146
146
  logInfo('You can now run `edsger` to start processing features.');
147
147
  }
148
148
  catch (error) {
@@ -173,7 +173,7 @@ export async function runStatus() {
173
173
  return;
174
174
  }
175
175
  logInfo('Login status:');
176
- console.log(` Server: ${auth.mcpServerUrl}`);
177
- console.log(` Logged in at: ${auth.loggedInAt}`);
178
- console.log(` Token: ${auth.mcpToken.substring(0, 8)}...${auth.mcpToken.substring(auth.mcpToken.length - 4)}`);
176
+ logRaw(` Server: ${auth.mcpServerUrl}`);
177
+ logRaw(` Logged in at: ${auth.loggedInAt}`);
178
+ logRaw(` Token: ${auth.mcpToken.substring(0, 8)}...${auth.mcpToken.substring(auth.mcpToken.length - 4)}`);
179
179
  }
@@ -10,7 +10,7 @@
10
10
  * 6. Responds to web dashboard commands (pause/resume/stop)
11
11
  */
12
12
  import { AgentWorkflowProcessor, MAX_CONCURRENCY } from './processor.js';
13
- import { logInfo, logError, logSuccess, logWarning } from '../../utils/logger.js';
13
+ import { logInfo, logError, logSuccess, logWarning, logRaw } from '../../utils/logger.js';
14
14
  import { isLoggedIn } from '../../auth/auth-store.js';
15
15
  import { startAutoUpdateChecker, stopAutoUpdateChecker } from '../../updater/auto-updater.js';
16
16
  import { startSleepPrevention, stopSleepPrevention } from '../../system/sleep-prevention.js';
@@ -27,10 +27,10 @@ export async function runAgentWorkflow(options) {
27
27
  }
28
28
  const config = validateConfiguration(options);
29
29
  const version = getVersion();
30
- console.log();
30
+ logRaw('');
31
31
  logInfo(`Edsger Agent v${version}`);
32
32
  logInfo('Cross-product workflow mode');
33
- console.log();
33
+ logRaw('');
34
34
  // Set up workspace directory
35
35
  const workspaceRoot = ensureWorkspaceDir();
36
36
  logInfo(`Workspace: ${workspaceRoot}`);
@@ -44,9 +44,9 @@ export async function runAgentWorkflow(options) {
44
44
  startSleepMonitor();
45
45
  // Start heartbeat
46
46
  startHeartbeat();
47
- console.log();
47
+ logRaw('');
48
48
  logSuccess('Agent started. Watching for features...');
49
- console.log();
49
+ logRaw('');
50
50
  // Create and start the processor
51
51
  let maxConcurrent = 3;
52
52
  if (options.concurrency && options.concurrency > 0) {
@@ -71,7 +71,7 @@ export async function runAgentWorkflow(options) {
71
71
  processor.stop();
72
72
  // Show stats
73
73
  const stats = processor.getStats();
74
- console.log();
74
+ logRaw('');
75
75
  logInfo('Session Statistics:');
76
76
  logInfo(` Total processed: ${stats.totalProcessed}`);
77
77
  logInfo(` Completed: ${stats.completed}`);
@@ -10,7 +10,7 @@
10
10
  import { EdsgerConfig } from '../../types/index.js';
11
11
  import { type WorkflowStats } from '../workflow/core/state-manager.js';
12
12
  /**
13
- * Maximum allowed concurrency.
13
+ * Maximum allowed concurrency (hard cap).
14
14
  * Limits are due to:
15
15
  * - MCP API rate limits (per-minute/per-hour per token)
16
16
  * - AI API rate limits (Claude/OpenAI RPM/TPM)
@@ -15,9 +15,10 @@ import { getGitHubConfig } from '../../api/github.js';
15
15
  import { logInfo, logWarning, logError, logSuccess } from '../../utils/logger.js';
16
16
  import { shouldProcess, sendHeartbeat } from '../../system/session-manager.js';
17
17
  import { cloneFeatureRepo } from '../../workspace/workspace-manager.js';
18
+ import { WorkerTimeoutError } from '../../errors/index.js';
18
19
  import { createInitialState, updateFeatureState, createProcessingState, createCompletedState, createFailedState, calculateStats, } from '../workflow/core/state-manager.js';
19
20
  /**
20
- * Maximum allowed concurrency.
21
+ * Maximum allowed concurrency (hard cap).
21
22
  * Limits are due to:
22
23
  * - MCP API rate limits (per-minute/per-hour per token)
23
24
  * - AI API rate limits (Claude/OpenAI RPM/TPM)
@@ -41,10 +42,11 @@ export class AgentWorkflowProcessor {
41
42
  /** Chat worker subprocess — runs in parallel, handles chat messages and phase events */
42
43
  chatWorker;
43
44
  constructor(options, config) {
45
+ const wf = config.workflow;
44
46
  this.options = {
45
- pollInterval: 30_000,
46
- maxConcurrent: 3,
47
- maxRetries: 3,
47
+ pollInterval: options.pollInterval ?? wf.pollInterval,
48
+ maxConcurrent: Math.min(options.maxConcurrent ?? wf.maxConcurrency, MAX_CONCURRENCY),
49
+ maxRetries: options.maxRetries ?? wf.maxRetries,
48
50
  verbose: false,
49
51
  ...options,
50
52
  };
@@ -152,6 +154,8 @@ export class AgentWorkflowProcessor {
152
154
  // Kill all active feature workers gracefully
153
155
  for (const [featureId, worker] of this.activeWorkers) {
154
156
  logInfo(`Stopping worker for feature: ${featureId}`);
157
+ if (worker.timeoutTimer)
158
+ clearTimeout(worker.timeoutTimer);
155
159
  try {
156
160
  worker.process.kill('SIGTERM');
157
161
  }
@@ -186,7 +190,7 @@ export class AgentWorkflowProcessor {
186
190
  return;
187
191
  }
188
192
  // Filter out features already being processed or recently failed
189
- const COOLDOWN_MS = 5 * 60 * 1000; // 5 minutes between retries
193
+ const cooldownMs = this.config.workflow.retryCooldown;
190
194
  const newFeatures = features.filter((f) => {
191
195
  if (this.activeWorkers.has(f.id))
192
196
  return false;
@@ -199,7 +203,7 @@ export class AgentWorkflowProcessor {
199
203
  if (state.retryCount >= this.options.maxRetries)
200
204
  return false;
201
205
  // Skip if within cooldown period
202
- if (Date.now() - state.lastAttempt.getTime() < COOLDOWN_MS)
206
+ if (Date.now() - state.lastAttempt.getTime() < cooldownMs)
203
207
  return false;
204
208
  }
205
209
  return true;
@@ -319,6 +323,10 @@ export class AgentWorkflowProcessor {
319
323
  });
320
324
  // Handle worker exit (crash, killed, etc.)
321
325
  worker.on('exit', (code) => {
326
+ // Clear timeout timer
327
+ const w = this.activeWorkers.get(featureId);
328
+ if (w?.timeoutTimer)
329
+ clearTimeout(w.timeoutTimer);
322
330
  this.activeWorkers.delete(featureId);
323
331
  // If the worker exited without sending a result message, treat as failure
324
332
  if (!resultReceived) {
@@ -328,6 +336,26 @@ export class AgentWorkflowProcessor {
328
336
  this.handleWorkerResult(featureId, feature.name, false, `Worker exited with code ${code}`);
329
337
  }
330
338
  });
339
+ // Set up worker timeout
340
+ const workerTimeout = this.config.workflow.workerTimeout;
341
+ activeWorker.timeoutTimer = setTimeout(() => {
342
+ logError(`Worker for ${feature.name} timed out after ${Math.round(workerTimeout / 1000)}s, killing...`);
343
+ try {
344
+ worker.kill('SIGTERM');
345
+ // Force kill after 10s if SIGTERM didn't work
346
+ setTimeout(() => {
347
+ try {
348
+ worker.kill('SIGKILL');
349
+ }
350
+ catch { /* ignore */ }
351
+ }, 10_000);
352
+ }
353
+ catch {
354
+ // Worker may already be dead
355
+ }
356
+ const err = new WorkerTimeoutError(featureId, workerTimeout);
357
+ this.handleWorkerResult(featureId, feature.name, false, err.message);
358
+ }, workerTimeout);
331
359
  // Send start message to worker
332
360
  worker.send({
333
361
  type: 'start',
@@ -0,0 +1,7 @@
1
+ import { CliOptions } from '../../types/index.js';
2
+ /**
3
+ * Run AI-powered log analysis for a product.
4
+ * Analyzes pending user logs (inactive >1 hour) and creates
5
+ * product feedback for any issues or improvements found.
6
+ */
7
+ export declare const runAnalyzeLogs: (options: CliOptions) => Promise<void>;
@@ -0,0 +1,34 @@
1
+ import { logInfo, logError, logSuccess } from '../../utils/logger.js';
2
+ import { analyzeLogs } from '../../phases/analyze-logs/index.js';
3
+ /**
4
+ * Run AI-powered log analysis for a product.
5
+ * Analyzes pending user logs (inactive >1 hour) and creates
6
+ * product feedback for any issues or improvements found.
7
+ */
8
+ export const runAnalyzeLogs = async (options) => {
9
+ const productId = options.analyzeLogs;
10
+ if (!productId) {
11
+ throw new Error('Product ID is required for log analysis');
12
+ }
13
+ logInfo(`Starting log analysis for product: ${productId}`);
14
+ try {
15
+ const result = await analyzeLogs({
16
+ productId,
17
+ verbose: options.verbose,
18
+ });
19
+ if (result.status === 'success') {
20
+ logSuccess('Log analysis completed!');
21
+ logInfo(` Groups processed: ${result.groupsProcessed}`);
22
+ logInfo(` Feedbacks created: ${result.feedbacksCreated}`);
23
+ logInfo(` Logs analyzed: ${result.logsAnalyzed}`);
24
+ }
25
+ else {
26
+ logError(`Log analysis failed: ${result.summary}`);
27
+ process.exit(1);
28
+ }
29
+ }
30
+ catch (error) {
31
+ logError(`Log analysis failed: ${error instanceof Error ? error.message : String(error)}`);
32
+ process.exit(1);
33
+ }
34
+ };
@@ -0,0 +1,12 @@
1
+ import { CliOptions } from '../../types/index.js';
2
+ /**
3
+ * Run AI-powered app store asset generation for a product.
4
+ * Generates professional screenshots and optimized listing content.
5
+ */
6
+ export declare const runAppStoreGeneration: (options: CliOptions & {
7
+ appStoreProductId: string;
8
+ store?: string;
9
+ locale?: string;
10
+ screenshotsOnly?: boolean;
11
+ listingsOnly?: boolean;
12
+ }) => Promise<void>;