vibe-weaver 1.0.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/README.md +229 -0
- package/bin/vibe-weaver.js +8 -0
- package/dist/bin/vibe-weaver.js +8 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2018 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
- package/src/agent/deploy.ts +171 -0
- package/src/agent/swarm.ts +167 -0
- package/src/agent/tasks.ts +205 -0
- package/src/auth/index.ts +273 -0
- package/src/config/index.ts +86 -0
- package/src/index.ts +797 -0
- package/src/lib/code-parser.ts +150 -0
- package/src/lib/vibe-api.ts +197 -0
- package/src/mcp/index.ts +273 -0
- package/src/tools/files.ts +279 -0
- package/src/tools/git.ts +498 -0
- package/src/tools/shell.ts +248 -0
- package/tsconfig.json +25 -0
- package/tsup.config.ts +34 -0
- package/vibe-weaver-1.0.0.tgz +0 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deploy functionality for Vibe-Weaver CLI
|
|
3
|
+
* Deploy projects and verify against live URL
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { getApiKey } from '../auth/index.js';
|
|
8
|
+
import { getSupabaseUrl } from '../config/index.js';
|
|
9
|
+
import { runShell } from '../tools/shell.js';
|
|
10
|
+
|
|
11
|
+
export interface DeployOptions {
|
|
12
|
+
projectId?: string;
|
|
13
|
+
platform?: string;
|
|
14
|
+
siteUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DeployResult {
|
|
18
|
+
success: boolean;
|
|
19
|
+
url?: string;
|
|
20
|
+
projectId?: string;
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Submit deployment
|
|
26
|
+
*/
|
|
27
|
+
export async function submitDeploy(options: DeployOptions): Promise<DeployResult> {
|
|
28
|
+
const apiKey = await getApiKey();
|
|
29
|
+
if (!apiKey) {
|
|
30
|
+
throw new Error('Not logged in. Run "vibe-weaver login" first.');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const supabaseUrl = getSupabaseUrl();
|
|
34
|
+
|
|
35
|
+
console.log(chalk.cyan('\nš Submitting deployment...'));
|
|
36
|
+
|
|
37
|
+
const response = await fetch(`${supabaseUrl}/functions/v1/deploy-bridge`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
'Authorization': `Bearer ${apiKey}`
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify({
|
|
44
|
+
projectId: options.projectId,
|
|
45
|
+
platform: options.platform || 'web'
|
|
46
|
+
})
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: error.error || error.message
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const result = await response.json();
|
|
58
|
+
|
|
59
|
+
console.log(chalk.green(`ā Deployment submitted`));
|
|
60
|
+
console.log(chalk.gray(` URL: ${result.url}`));
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
success: true,
|
|
64
|
+
url: result.url,
|
|
65
|
+
projectId: result.projectId
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Verify deployment by checking the URL
|
|
71
|
+
*/
|
|
72
|
+
export async function verifyDeploy(url: string, options: {
|
|
73
|
+
timeout?: number;
|
|
74
|
+
expectedStatus?: number;
|
|
75
|
+
} = {}): Promise<{ success: boolean; status?: number; error?: string }> {
|
|
76
|
+
const timeout = options.timeout || 60000;
|
|
77
|
+
const expectedStatus = options.expectedStatus || 200;
|
|
78
|
+
|
|
79
|
+
console.log(chalk.gray(`\nVerifying deployment at ${url}...`));
|
|
80
|
+
|
|
81
|
+
const startTime = Date.now();
|
|
82
|
+
|
|
83
|
+
while (Date.now() - startTime < timeout) {
|
|
84
|
+
try {
|
|
85
|
+
const response = await fetch(url, { method: 'HEAD' });
|
|
86
|
+
|
|
87
|
+
if (response.status === expectedStatus) {
|
|
88
|
+
console.log(chalk.green(`ā Deployment verified (${response.status})`));
|
|
89
|
+
return { success: true, status: response.status };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// If we get 404, the site might still be deploying
|
|
93
|
+
if (response.status === 404) {
|
|
94
|
+
console.log(chalk.yellow(` Still deploying... (${response.status})`));
|
|
95
|
+
} else {
|
|
96
|
+
console.log(chalk.yellow(` Unexpected status: ${response.status}`));
|
|
97
|
+
}
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.log(chalk.yellow(` Waiting for deployment...`));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(chalk.red(`ā Deployment verification timed out`));
|
|
106
|
+
return {
|
|
107
|
+
success: false,
|
|
108
|
+
error: 'Deployment verification timed out'
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Full deploy and verify workflow
|
|
114
|
+
*/
|
|
115
|
+
export async function deploy(options: {
|
|
116
|
+
projectId?: string;
|
|
117
|
+
platform?: string;
|
|
118
|
+
verify?: boolean;
|
|
119
|
+
cwd?: string;
|
|
120
|
+
} = {}): Promise<DeployResult> {
|
|
121
|
+
const verify = options.verify !== false;
|
|
122
|
+
|
|
123
|
+
console.log(chalk.cyan('\nš Vibe-Weaver Deploy'));
|
|
124
|
+
|
|
125
|
+
// First build locally
|
|
126
|
+
if (options.cwd) {
|
|
127
|
+
console.log(chalk.gray('\nBuilding project locally...'));
|
|
128
|
+
const buildResult = await runShell('npm run build', { cwd: options.cwd });
|
|
129
|
+
|
|
130
|
+
if (!buildResult.success) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
error: `Build failed: ${buildResult.stderr}`
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
console.log(chalk.green('ā Build successful'));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Submit deployment
|
|
141
|
+
console.log(chalk.gray('\nSubmitting to Vibe-Weaver...'));
|
|
142
|
+
const deployResult = await submitDeploy({
|
|
143
|
+
projectId: options.projectId,
|
|
144
|
+
platform: options.platform
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (!deployResult.success) {
|
|
148
|
+
return deployResult;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Verify if requested
|
|
152
|
+
if (verify && deployResult.url) {
|
|
153
|
+
const verifyResult = await verifyDeploy(deployResult.url);
|
|
154
|
+
|
|
155
|
+
if (!verifyResult.success) {
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
url: deployResult.url,
|
|
159
|
+
error: verifyResult.error
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return deployResult;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export default {
|
|
168
|
+
submitDeploy,
|
|
169
|
+
verifyDeploy,
|
|
170
|
+
deploy
|
|
171
|
+
};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sub-agent Swarm for Vibe-Weaver CLI
|
|
3
|
+
* Runs multiple agents in parallel for complex tasks
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { generateCode } from '../lib/vibe-api.js';
|
|
8
|
+
import { writeCodeBlocks } from '../lib/code-parser.js';
|
|
9
|
+
import { runShell } from '../tools/shell.js';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
export interface AgentTask {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
goal: string;
|
|
16
|
+
status: 'pending' | 'running' | 'completed' | 'failed';
|
|
17
|
+
result?: string;
|
|
18
|
+
error?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SwarmResult {
|
|
22
|
+
success: boolean;
|
|
23
|
+
completed: number;
|
|
24
|
+
failed: number;
|
|
25
|
+
results: AgentTask[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Split a complex goal into sub-tasks for parallel execution
|
|
30
|
+
*/
|
|
31
|
+
export async function planSubTasks(goal: string): Promise<string[]> {
|
|
32
|
+
const result = await generateCode({
|
|
33
|
+
goal: `Break down this complex goal into 3-5 independent subtasks that can run in parallel. List only the subtask descriptions, one per line:\n\n${goal}`,
|
|
34
|
+
modelId: 'balanced'
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Parse subtasks from result
|
|
38
|
+
const lines = result.explanation.split('\n').filter(line => line.trim().length > 0);
|
|
39
|
+
return lines.slice(0, 5); // Limit to 5 subtasks
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Execute a single subtask
|
|
44
|
+
*/
|
|
45
|
+
async function executeSubTask(task: AgentTask, baseDir: string): Promise<AgentTask> {
|
|
46
|
+
task.status = 'running';
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
console.log(chalk.gray(`[${task.id}] Starting: ${task.name}`));
|
|
50
|
+
|
|
51
|
+
const result = await generateCode({
|
|
52
|
+
goal: task.goal,
|
|
53
|
+
modelId: 'balanced'
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Write generated files
|
|
57
|
+
if (result.parsed.blocks.length > 0) {
|
|
58
|
+
const writeResult = await writeCodeBlocks(result.parsed.blocks, baseDir);
|
|
59
|
+
|
|
60
|
+
if (writeResult.failed.length > 0) {
|
|
61
|
+
task.status = 'failed';
|
|
62
|
+
task.error = `Failed to write files: ${writeResult.failed.map(f => f.path).join(', ')}`;
|
|
63
|
+
} else {
|
|
64
|
+
task.status = 'completed';
|
|
65
|
+
task.result = `Generated ${writeResult.success.length} files: ${writeResult.success.join(', ')}`;
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
task.status = 'completed';
|
|
69
|
+
task.result = result.explanation;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log(chalk.green(`[${task.id}] Completed: ${task.name}`));
|
|
73
|
+
} catch (error) {
|
|
74
|
+
task.status = 'failed';
|
|
75
|
+
task.error = error instanceof Error ? error.message : String(error);
|
|
76
|
+
console.log(chalk.red(`[${task.id}] Failed: ${task.name} - ${task.error}`));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return task;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Run multiple agents in parallel (swarm)
|
|
84
|
+
*/
|
|
85
|
+
export async function runSwarm(goal: string, options: {
|
|
86
|
+
maxParallel?: number;
|
|
87
|
+
baseDir?: string;
|
|
88
|
+
} = {}): Promise<SwarmResult> {
|
|
89
|
+
const maxParallel = options.maxParallel || 3;
|
|
90
|
+
const baseDir = options.baseDir || process.cwd();
|
|
91
|
+
|
|
92
|
+
console.log(chalk.cyan('\nšÆ Vibe-Weaver Agent Swarm'));
|
|
93
|
+
console.log(chalk.gray(`Planning subtasks for: ${goal.substring(0, 50)}...\n`));
|
|
94
|
+
|
|
95
|
+
// Plan subtasks
|
|
96
|
+
const subtaskGoals = await planSubTasks(goal);
|
|
97
|
+
|
|
98
|
+
if (subtaskGoals.length === 0) {
|
|
99
|
+
console.log(chalk.yellow('Could not generate subtasks. Running as single task.'));
|
|
100
|
+
subtaskGoals.push(goal);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log(chalk.gray(`Found ${subtaskGoals.length} subtasks:\n`));
|
|
104
|
+
for (let i = 0; i < subtaskGoals.length; i++) {
|
|
105
|
+
console.log(chalk.gray(` ${i + 1}. ${subtaskGoals[i].substring(0, 60)}...`));
|
|
106
|
+
}
|
|
107
|
+
console.log();
|
|
108
|
+
|
|
109
|
+
// Create tasks
|
|
110
|
+
const tasks: AgentTask[] = subtaskGoals.map((subGoal, i) => ({
|
|
111
|
+
id: `agent-${i + 1}`,
|
|
112
|
+
name: `Agent ${i + 1}`,
|
|
113
|
+
goal: subGoal,
|
|
114
|
+
status: 'pending' as const
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
// Run tasks in batches
|
|
118
|
+
const results: AgentTask[] = [];
|
|
119
|
+
|
|
120
|
+
for (let i = 0; i < tasks.length; i += maxParallel) {
|
|
121
|
+
const batch = tasks.slice(i, i + maxParallel);
|
|
122
|
+
|
|
123
|
+
console.log(chalk.cyan(`\n--- Running batch ${Math.floor(i / maxParallel) + 1} (${batch.length} agents) ---\n`));
|
|
124
|
+
|
|
125
|
+
// Execute batch in parallel
|
|
126
|
+
const batchResults = await Promise.all(
|
|
127
|
+
batch.map(task => executeSubTask(task, baseDir))
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
results.push(...batchResults);
|
|
131
|
+
|
|
132
|
+
// Show progress
|
|
133
|
+
const completed = results.filter(t => t.status === 'completed').length;
|
|
134
|
+
const failed = results.filter(t => t.status === 'failed').length;
|
|
135
|
+
console.log(chalk.gray(`\nProgress: ${completed} completed, ${failed} failed\n`));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Summary
|
|
139
|
+
const completed = results.filter(t => t.status === 'completed').length;
|
|
140
|
+
const failed = results.filter(t => t.status === 'failed').length;
|
|
141
|
+
|
|
142
|
+
console.log(chalk.cyan('\n=== Swarm Results ==='));
|
|
143
|
+
console.log(chalk.green(`Completed: ${completed}`));
|
|
144
|
+
if (failed > 0) {
|
|
145
|
+
console.log(chalk.red(`Failed: ${failed}`));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
for (const task of results) {
|
|
149
|
+
if (task.status === 'completed') {
|
|
150
|
+
console.log(chalk.green(` ā ${task.name}: ${task.result?.substring(0, 50)}...`));
|
|
151
|
+
} else if (task.status === 'failed') {
|
|
152
|
+
console.log(chalk.red(` ā ${task.name}: ${task.error}`));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
success: failed === 0,
|
|
158
|
+
completed,
|
|
159
|
+
failed,
|
|
160
|
+
results
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export default {
|
|
165
|
+
runSwarm,
|
|
166
|
+
planSubTasks
|
|
167
|
+
};
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Background Tasks for Vibe-Weaver CLI
|
|
3
|
+
* Submit server-side runs and check status
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { getApiKey } from '../auth/index.js';
|
|
8
|
+
import { getSupabaseUrl } from '../config/index.js';
|
|
9
|
+
|
|
10
|
+
export interface TaskSubmission {
|
|
11
|
+
id?: string;
|
|
12
|
+
goal: string;
|
|
13
|
+
platform?: string;
|
|
14
|
+
language?: string;
|
|
15
|
+
modelId?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface TaskStatus {
|
|
19
|
+
id: string;
|
|
20
|
+
status: 'pending' | 'running' | 'completed' | 'failed';
|
|
21
|
+
progress?: number;
|
|
22
|
+
result?: string;
|
|
23
|
+
error?: string;
|
|
24
|
+
createdAt: string;
|
|
25
|
+
updatedAt: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Submit a background task to the server
|
|
30
|
+
*/
|
|
31
|
+
export async function submitTask(options: TaskSubmission): Promise<{ id: string; status: string }> {
|
|
32
|
+
const apiKey = await getApiKey();
|
|
33
|
+
if (!apiKey) {
|
|
34
|
+
throw new Error('Not logged in. Run "vibe-weaver login" first.');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const supabaseUrl = getSupabaseUrl();
|
|
38
|
+
|
|
39
|
+
console.log(chalk.gray(`\nSubmitting background task: ${options.goal.substring(0, 50)}...`));
|
|
40
|
+
|
|
41
|
+
const response = await fetch(`${supabaseUrl}/functions/v1/automation-executor`, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: {
|
|
44
|
+
'Content-Type': 'application/json',
|
|
45
|
+
'Authorization': `Bearer ${apiKey}`
|
|
46
|
+
},
|
|
47
|
+
body: JSON.stringify({
|
|
48
|
+
goal: options.goal,
|
|
49
|
+
platform: options.platform || 'web',
|
|
50
|
+
language: options.language || 'typescript',
|
|
51
|
+
modelId: options.modelId || 'balanced'
|
|
52
|
+
})
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
57
|
+
throw new Error(`Failed to submit task: ${error.error || error.message}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const result = await response.json();
|
|
61
|
+
|
|
62
|
+
console.log(chalk.green(`ā Task submitted: ${result.id}`));
|
|
63
|
+
console.log(chalk.gray(` Use "vibe-weaver task status ${result.id}" to check progress\n`));
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
id: result.id,
|
|
67
|
+
status: result.status || 'pending'
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get task status
|
|
73
|
+
*/
|
|
74
|
+
export async function getTaskStatus(taskId: string): Promise<TaskStatus> {
|
|
75
|
+
const apiKey = await getApiKey();
|
|
76
|
+
if (!apiKey) {
|
|
77
|
+
throw new Error('Not logged in. Run "vibe-weaver login" first.');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const supabaseUrl = getSupabaseUrl();
|
|
81
|
+
|
|
82
|
+
const response = await fetch(`${supabaseUrl}/functions/v1/automation-executor?id=${taskId}`, {
|
|
83
|
+
method: 'GET',
|
|
84
|
+
headers: {
|
|
85
|
+
'Authorization': `Bearer ${apiKey}`
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
const error = await response.json().catch(() => ({ error: 'Unknown error' }));
|
|
91
|
+
throw new Error(`Failed to get task status: ${error.error || error.message}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return response.json();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Stream task logs
|
|
99
|
+
*/
|
|
100
|
+
export async function* streamTaskLogs(taskId: string): AsyncGenerator<string> {
|
|
101
|
+
const apiKey = await getApiKey();
|
|
102
|
+
if (!apiKey) {
|
|
103
|
+
throw new Error('Not logged in. Run "vibe-weaver login" first.');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const supabaseUrl = getSupabaseUrl();
|
|
107
|
+
|
|
108
|
+
const response = await fetch(`${supabaseUrl}/functions/v1/automation-executor/logs?id=${taskId}`, {
|
|
109
|
+
method: 'GET',
|
|
110
|
+
headers: {
|
|
111
|
+
'Authorization': `Bearer ${apiKey}`
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
throw new Error(`Failed to get task logs: ${response.status}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const reader = response.body?.getReader();
|
|
120
|
+
const decoder = new TextDecoder();
|
|
121
|
+
|
|
122
|
+
if (!reader) {
|
|
123
|
+
throw new Error('Could not read response body');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
while (true) {
|
|
128
|
+
const { done, value } = await reader.read();
|
|
129
|
+
|
|
130
|
+
if (done) break;
|
|
131
|
+
|
|
132
|
+
yield decoder.decode(value);
|
|
133
|
+
}
|
|
134
|
+
} finally {
|
|
135
|
+
reader.releaseLock();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Poll task status until completion
|
|
141
|
+
*/
|
|
142
|
+
export async function waitForTask(taskId: string, options: {
|
|
143
|
+
pollInterval?: number;
|
|
144
|
+
onProgress?: (status: TaskStatus) => void;
|
|
145
|
+
} = {}): Promise<TaskStatus> {
|
|
146
|
+
const pollInterval = options.pollInterval || 5000;
|
|
147
|
+
|
|
148
|
+
while (true) {
|
|
149
|
+
const status = await getTaskStatus(taskId);
|
|
150
|
+
|
|
151
|
+
if (options.onProgress) {
|
|
152
|
+
options.onProgress(status);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (status.status === 'completed') {
|
|
156
|
+
return status;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (status.status === 'failed') {
|
|
160
|
+
throw new Error(`Task failed: ${status.error}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Wait before next poll
|
|
164
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Display task status nicely
|
|
170
|
+
*/
|
|
171
|
+
export function displayTaskStatus(status: TaskStatus): void {
|
|
172
|
+
const statusColor: Record<string, (s: string) => string> = {
|
|
173
|
+
pending: chalk.yellow,
|
|
174
|
+
running: chalk.blue,
|
|
175
|
+
completed: chalk.green,
|
|
176
|
+
failed: chalk.red
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
console.log(chalk.bold(`\nTask: ${status.id}`));
|
|
180
|
+
console.log(chalk.gray(`Status: ${statusColor[status.status] ? statusColor[status.status](status.status) : chalk.gray(status.status)}`));
|
|
181
|
+
|
|
182
|
+
if (status.progress !== undefined) {
|
|
183
|
+
console.log(chalk.gray(`Progress: ${status.progress}%`));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (status.result) {
|
|
187
|
+
console.log(chalk.gray(`\nResult:`));
|
|
188
|
+
console.log(status.result);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (status.error) {
|
|
192
|
+
console.log(chalk.red(`\nError: ${status.error}`));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
console.log(chalk.gray(`\nCreated: ${new Date(status.createdAt).toLocaleString()}`));
|
|
196
|
+
console.log(chalk.gray(`Updated: ${new Date(status.updatedAt).toLocaleString()}`));
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export default {
|
|
200
|
+
submitTask,
|
|
201
|
+
getTaskStatus,
|
|
202
|
+
streamTaskLogs,
|
|
203
|
+
waitForTask,
|
|
204
|
+
displayTaskStatus
|
|
205
|
+
};
|