@output.ai/cli 0.3.1-dev.pr156.0 → 0.4.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 +2 -28
- package/dist/api/generated/api.d.ts +35 -59
- package/dist/api/generated/api.js +4 -13
- package/dist/assets/docker/docker-compose-dev.yml +2 -2
- package/dist/commands/workflow/debug.d.ts +2 -8
- package/dist/commands/workflow/debug.js +24 -164
- package/dist/commands/workflow/debug.spec.js +36 -0
- package/dist/commands/workflow/generate.js +3 -10
- package/dist/commands/workflow/generate.spec.js +6 -4
- package/dist/commands/workflow/{output.d.ts → result.d.ts} +1 -1
- package/dist/commands/workflow/{output.js → result.js} +8 -8
- package/dist/commands/workflow/result.test.js +23 -0
- package/dist/commands/workflow/start.js +1 -1
- package/dist/services/coding_agents.js +30 -0
- package/dist/services/coding_agents.spec.js +36 -61
- package/dist/services/messages.d.ts +1 -0
- package/dist/services/messages.js +65 -1
- package/dist/services/trace_reader.d.ts +14 -0
- package/dist/services/trace_reader.js +67 -0
- package/dist/services/trace_reader.spec.d.ts +1 -0
- package/dist/services/trace_reader.spec.js +164 -0
- package/dist/services/workflow_generator.spec.d.ts +1 -0
- package/dist/services/workflow_generator.spec.js +77 -0
- package/dist/templates/agent_instructions/AGENTS.md.template +209 -19
- package/dist/templates/agent_instructions/agents/context_fetcher.md.template +82 -0
- package/dist/templates/agent_instructions/agents/prompt_writer.md.template +595 -0
- package/dist/templates/agent_instructions/agents/workflow_planner.md.template +13 -4
- package/dist/templates/agent_instructions/agents/workflow_quality.md.template +244 -0
- package/dist/templates/agent_instructions/commands/build_workflow.md.template +52 -9
- package/dist/templates/agent_instructions/commands/plan_workflow.md.template +4 -4
- package/dist/templates/project/package.json.template +2 -2
- package/dist/templates/project/src/simple/scenarios/question_ada_lovelace.json.template +3 -0
- package/dist/templates/workflow/scenarios/test_input.json.template +7 -0
- package/dist/types/trace.d.ts +161 -0
- package/dist/types/trace.js +18 -0
- package/dist/utils/date_formatter.d.ts +8 -0
- package/dist/utils/date_formatter.js +19 -0
- package/dist/utils/template.spec.js +6 -0
- package/dist/utils/trace_formatter.d.ts +11 -61
- package/dist/utils/trace_formatter.js +384 -239
- package/package.json +2 -2
- package/dist/commands/workflow/debug.test.js +0 -107
- package/dist/commands/workflow/output.test.js +0 -23
- package/dist/utils/s3_downloader.d.ts +0 -49
- package/dist/utils/s3_downloader.js +0 -154
- /package/dist/commands/workflow/{debug.test.d.ts → debug.spec.d.ts} +0 -0
- /package/dist/commands/workflow/{output.test.d.ts → result.test.d.ts} +0 -0
- /package/dist/templates/workflow/{prompt@v1.prompt.template → prompts/prompt@v1.prompt.template} +0 -0
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
// Mock the API
|
|
3
|
-
vi.mock('../../api/generated/api.js', () => ({
|
|
4
|
-
getWorkflowIdOutput: vi.fn()
|
|
5
|
-
}));
|
|
6
|
-
// Mock the utilities
|
|
7
|
-
vi.mock('../../utils/s3_downloader.js', () => ({
|
|
8
|
-
s3Downloader: {
|
|
9
|
-
isAvailable: vi.fn(),
|
|
10
|
-
download: vi.fn(),
|
|
11
|
-
clearCache: vi.fn(),
|
|
12
|
-
getCacheSize: vi.fn()
|
|
13
|
-
},
|
|
14
|
-
S3Downloader: vi.fn()
|
|
15
|
-
}));
|
|
16
|
-
vi.mock('../../utils/trace_formatter.js', () => ({
|
|
17
|
-
traceFormatter: {
|
|
18
|
-
format: vi.fn(),
|
|
19
|
-
getSummary: vi.fn()
|
|
20
|
-
},
|
|
21
|
-
TraceFormatter: vi.fn()
|
|
22
|
-
}));
|
|
23
|
-
// Note: fs operations are mocked but not used in simplified tests
|
|
24
|
-
// Real OCLIF testing would require complex framework initialization
|
|
25
|
-
describe('workflow debug command', () => {
|
|
26
|
-
beforeEach(() => {
|
|
27
|
-
vi.clearAllMocks();
|
|
28
|
-
});
|
|
29
|
-
describe('command definition', () => {
|
|
30
|
-
it('should export a valid OCLIF command', async () => {
|
|
31
|
-
const WorkflowDebug = (await import('./debug.js')).default;
|
|
32
|
-
expect(WorkflowDebug).toBeDefined();
|
|
33
|
-
expect(WorkflowDebug.description).toContain('Get and display workflow execution trace for debugging');
|
|
34
|
-
expect(WorkflowDebug.args).toHaveProperty('workflowId');
|
|
35
|
-
expect(WorkflowDebug.flags).toHaveProperty('format');
|
|
36
|
-
expect(WorkflowDebug.flags).toHaveProperty('remote');
|
|
37
|
-
expect(WorkflowDebug.flags).toHaveProperty('download-dir');
|
|
38
|
-
expect(WorkflowDebug.flags).toHaveProperty('open');
|
|
39
|
-
expect(WorkflowDebug.flags).toHaveProperty('force-download');
|
|
40
|
-
});
|
|
41
|
-
it('should have correct flag configuration', async () => {
|
|
42
|
-
const WorkflowDebug = (await import('./debug.js')).default;
|
|
43
|
-
// Format flag
|
|
44
|
-
expect(WorkflowDebug.flags.format.options).toEqual(['json', 'text']);
|
|
45
|
-
expect(WorkflowDebug.flags.format.default).toBe('text');
|
|
46
|
-
// Remote flag
|
|
47
|
-
expect(WorkflowDebug.flags.remote.default).toBe(false);
|
|
48
|
-
// Download dir flag
|
|
49
|
-
expect(WorkflowDebug.flags['download-dir'].default).toContain('.output/traces');
|
|
50
|
-
// Open flag
|
|
51
|
-
expect(WorkflowDebug.flags.open.default).toBe(false);
|
|
52
|
-
// Force download flag
|
|
53
|
-
expect(WorkflowDebug.flags['force-download'].default).toBe(false);
|
|
54
|
-
});
|
|
55
|
-
it('should have correct examples', async () => {
|
|
56
|
-
const WorkflowDebug = (await import('./debug.js')).default;
|
|
57
|
-
expect(WorkflowDebug.examples).toBeDefined();
|
|
58
|
-
expect(WorkflowDebug.examples.length).toBeGreaterThan(0);
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
describe('run method', () => {
|
|
62
|
-
// Variables will be initialized in beforeEach
|
|
63
|
-
// eslint-disable-next-line init-declarations, no-restricted-syntax, @typescript-eslint/no-explicit-any
|
|
64
|
-
let WorkflowDebug;
|
|
65
|
-
// eslint-disable-next-line init-declarations, no-restricted-syntax, @typescript-eslint/no-explicit-any
|
|
66
|
-
let getWorkflowIdOutput;
|
|
67
|
-
beforeEach(async () => {
|
|
68
|
-
const apiModule = await import('../../api/generated/api.js');
|
|
69
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
-
getWorkflowIdOutput = apiModule.getWorkflowIdOutput;
|
|
71
|
-
WorkflowDebug = (await import('./debug.js')).default;
|
|
72
|
-
});
|
|
73
|
-
it('should fetch and display local trace when available', async () => {
|
|
74
|
-
// This test requires OCLIF framework initialization which is complex to mock
|
|
75
|
-
// The functionality is tested through manual testing and the build process
|
|
76
|
-
expect(true).toBe(true);
|
|
77
|
-
});
|
|
78
|
-
it('should download and display remote trace when local not available', async () => {
|
|
79
|
-
// This test requires OCLIF framework initialization which is complex to mock
|
|
80
|
-
// The functionality is tested through manual testing and the build process
|
|
81
|
-
expect(true).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
it('should prefer remote trace when --remote flag is set', async () => {
|
|
84
|
-
// This test requires OCLIF framework initialization which is complex to mock
|
|
85
|
-
// The functionality is tested through manual testing and the build process
|
|
86
|
-
expect(true).toBe(true);
|
|
87
|
-
});
|
|
88
|
-
it('should handle workflow not found error', async () => {
|
|
89
|
-
getWorkflowIdOutput.mockRejectedValue({ response: { status: 404 } });
|
|
90
|
-
const cmd = new WorkflowDebug(['non-existent-workflow'], {});
|
|
91
|
-
cmd.error = vi.fn();
|
|
92
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
93
|
-
await cmd.catch({ response: { status: 404 } });
|
|
94
|
-
expect(cmd.error).toHaveBeenCalledWith(expect.stringContaining('Workflow not found'), expect.any(Object));
|
|
95
|
-
});
|
|
96
|
-
it('should handle missing AWS credentials', async () => {
|
|
97
|
-
// This test requires OCLIF framework initialization which is complex to mock
|
|
98
|
-
// The functionality is tested through manual testing and the build process
|
|
99
|
-
expect(true).toBe(true);
|
|
100
|
-
});
|
|
101
|
-
it('should save trace file when --open flag is set', async () => {
|
|
102
|
-
// This test requires OCLIF framework initialization which is complex to mock
|
|
103
|
-
// The functionality is tested through manual testing and the build process
|
|
104
|
-
expect(true).toBe(true);
|
|
105
|
-
});
|
|
106
|
-
});
|
|
107
|
-
});
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
vi.mock('../../api/generated/api.js', () => ({
|
|
3
|
-
getWorkflowIdOutput: vi.fn()
|
|
4
|
-
}));
|
|
5
|
-
describe('workflow output command', () => {
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
vi.clearAllMocks();
|
|
8
|
-
});
|
|
9
|
-
describe('command definition', () => {
|
|
10
|
-
it('should export a valid OCLIF command', async () => {
|
|
11
|
-
const WorkflowOutput = (await import('./output.js')).default;
|
|
12
|
-
expect(WorkflowOutput).toBeDefined();
|
|
13
|
-
expect(WorkflowOutput.description).toContain('Get workflow execution output');
|
|
14
|
-
expect(WorkflowOutput.args).toHaveProperty('workflowId');
|
|
15
|
-
expect(WorkflowOutput.flags).toHaveProperty('format');
|
|
16
|
-
});
|
|
17
|
-
it('should have correct flag configuration', async () => {
|
|
18
|
-
const WorkflowOutput = (await import('./output.js')).default;
|
|
19
|
-
expect(WorkflowOutput.flags.format.options).toEqual(['json', 'text']);
|
|
20
|
-
expect(WorkflowOutput.flags.format.default).toBe('text');
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
interface S3DownloadOptions {
|
|
2
|
-
cacheDir?: string;
|
|
3
|
-
forceDownload?: boolean;
|
|
4
|
-
}
|
|
5
|
-
export declare class S3Downloader {
|
|
6
|
-
private s3Client;
|
|
7
|
-
private cacheDir;
|
|
8
|
-
constructor(options?: S3DownloadOptions);
|
|
9
|
-
private initializeS3Client;
|
|
10
|
-
/**
|
|
11
|
-
* Parse S3 URL to extract bucket and key
|
|
12
|
-
* Supports formats:
|
|
13
|
-
* - https://bucket.s3.amazonaws.com/path/to/file
|
|
14
|
-
* - https://bucket.s3.region.amazonaws.com/path/to/file
|
|
15
|
-
* - s3://bucket/path/to/file
|
|
16
|
-
*/
|
|
17
|
-
private parseS3Url;
|
|
18
|
-
/**
|
|
19
|
-
* Get cached file path for a given S3 URL
|
|
20
|
-
*/
|
|
21
|
-
private getCachePath;
|
|
22
|
-
/**
|
|
23
|
-
* Download a file from S3
|
|
24
|
-
*/
|
|
25
|
-
download(s3Url: string, options?: {
|
|
26
|
-
forceDownload?: boolean;
|
|
27
|
-
}): Promise<string>;
|
|
28
|
-
/**
|
|
29
|
-
* Check if S3 client is available (credentials configured)
|
|
30
|
-
*/
|
|
31
|
-
isAvailable(): boolean;
|
|
32
|
-
/**
|
|
33
|
-
* Ensure cache directory exists
|
|
34
|
-
*/
|
|
35
|
-
private ensureCacheDir;
|
|
36
|
-
/**
|
|
37
|
-
* Clear the cache directory
|
|
38
|
-
*/
|
|
39
|
-
clearCache(): Promise<void>;
|
|
40
|
-
/**
|
|
41
|
-
* Get the size of the cache directory in bytes
|
|
42
|
-
*/
|
|
43
|
-
getCacheSize(): Promise<number>;
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Create a singleton instance for convenience
|
|
47
|
-
*/
|
|
48
|
-
export declare const s3Downloader: S3Downloader;
|
|
49
|
-
export {};
|
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
|
|
2
|
-
import * as fs from 'node:fs/promises';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
|
-
import { existsSync } from 'node:fs';
|
|
5
|
-
export class S3Downloader {
|
|
6
|
-
s3Client = null;
|
|
7
|
-
cacheDir;
|
|
8
|
-
constructor(options = {}) {
|
|
9
|
-
this.cacheDir = options.cacheDir || path.join(process.cwd(), '.output', 'traces');
|
|
10
|
-
this.initializeS3Client();
|
|
11
|
-
}
|
|
12
|
-
initializeS3Client() {
|
|
13
|
-
const region = process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || 'us-east-1';
|
|
14
|
-
const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
|
|
15
|
-
const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
|
|
16
|
-
if (!accessKeyId || !secretAccessKey) {
|
|
17
|
-
// S3 client not available without credentials
|
|
18
|
-
this.s3Client = null;
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
this.s3Client = new S3Client({
|
|
22
|
-
region,
|
|
23
|
-
credentials: {
|
|
24
|
-
accessKeyId,
|
|
25
|
-
secretAccessKey
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Parse S3 URL to extract bucket and key
|
|
31
|
-
* Supports formats:
|
|
32
|
-
* - https://bucket.s3.amazonaws.com/path/to/file
|
|
33
|
-
* - https://bucket.s3.region.amazonaws.com/path/to/file
|
|
34
|
-
* - s3://bucket/path/to/file
|
|
35
|
-
*/
|
|
36
|
-
parseS3Url(url) {
|
|
37
|
-
// Handle s3:// protocol
|
|
38
|
-
if (url.startsWith('s3://')) {
|
|
39
|
-
const withoutProtocol = url.slice(5);
|
|
40
|
-
const firstSlash = withoutProtocol.indexOf('/');
|
|
41
|
-
if (firstSlash === -1) {
|
|
42
|
-
throw new Error(`Invalid S3 URL format: ${url}`);
|
|
43
|
-
}
|
|
44
|
-
return {
|
|
45
|
-
bucket: withoutProtocol.substring(0, firstSlash),
|
|
46
|
-
key: withoutProtocol.substring(firstSlash + 1)
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
// Handle https:// URLs
|
|
50
|
-
if (url.startsWith('https://')) {
|
|
51
|
-
const urlObj = new URL(url);
|
|
52
|
-
const hostname = urlObj.hostname;
|
|
53
|
-
// Extract bucket from hostname
|
|
54
|
-
// Format: bucket.s3.amazonaws.com or bucket.s3.region.amazonaws.com
|
|
55
|
-
const s3Match = hostname.match(/^([^.]+)\.s3(?:\.[^.]+)?\.amazonaws\.com$/);
|
|
56
|
-
if (!s3Match) {
|
|
57
|
-
throw new Error(`Invalid S3 URL format: ${url}`);
|
|
58
|
-
}
|
|
59
|
-
const bucket = s3Match[1];
|
|
60
|
-
const key = urlObj.pathname.startsWith('/') ? urlObj.pathname.slice(1) : urlObj.pathname;
|
|
61
|
-
return { bucket, key };
|
|
62
|
-
}
|
|
63
|
-
throw new Error(`Unsupported S3 URL format: ${url}`);
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Get cached file path for a given S3 URL
|
|
67
|
-
*/
|
|
68
|
-
getCachePath(url) {
|
|
69
|
-
const { key } = this.parseS3Url(url);
|
|
70
|
-
// Use the last part of the key as filename to preserve the original name
|
|
71
|
-
const filename = path.basename(key);
|
|
72
|
-
return path.join(this.cacheDir, filename);
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Download a file from S3
|
|
76
|
-
*/
|
|
77
|
-
async download(s3Url, options = {}) {
|
|
78
|
-
if (!this.s3Client) {
|
|
79
|
-
throw new Error('AWS credentials not configured. Please set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.');
|
|
80
|
-
}
|
|
81
|
-
// Check cache first
|
|
82
|
-
const cachePath = this.getCachePath(s3Url);
|
|
83
|
-
if (!options.forceDownload && existsSync(cachePath)) {
|
|
84
|
-
const content = await fs.readFile(cachePath, 'utf-8');
|
|
85
|
-
return content;
|
|
86
|
-
}
|
|
87
|
-
// Parse S3 URL
|
|
88
|
-
const { bucket, key } = this.parseS3Url(s3Url);
|
|
89
|
-
try {
|
|
90
|
-
// Download from S3
|
|
91
|
-
const command = new GetObjectCommand({
|
|
92
|
-
Bucket: bucket,
|
|
93
|
-
Key: key
|
|
94
|
-
});
|
|
95
|
-
const response = await this.s3Client.send(command);
|
|
96
|
-
if (!response.Body) {
|
|
97
|
-
throw new Error('Empty response from S3');
|
|
98
|
-
}
|
|
99
|
-
// Convert stream to string
|
|
100
|
-
const bodyContents = await response.Body.transformToString();
|
|
101
|
-
// Cache the file
|
|
102
|
-
await this.ensureCacheDir();
|
|
103
|
-
await fs.writeFile(cachePath, bodyContents, 'utf-8');
|
|
104
|
-
return bodyContents;
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
if (error instanceof Error) {
|
|
108
|
-
throw new Error(`Failed to download from S3: ${error.message}`);
|
|
109
|
-
}
|
|
110
|
-
throw error;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Check if S3 client is available (credentials configured)
|
|
115
|
-
*/
|
|
116
|
-
isAvailable() {
|
|
117
|
-
return this.s3Client !== null;
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Ensure cache directory exists
|
|
121
|
-
*/
|
|
122
|
-
async ensureCacheDir() {
|
|
123
|
-
await fs.mkdir(this.cacheDir, { recursive: true });
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Clear the cache directory
|
|
127
|
-
*/
|
|
128
|
-
async clearCache() {
|
|
129
|
-
if (existsSync(this.cacheDir)) {
|
|
130
|
-
await fs.rm(this.cacheDir, { recursive: true, force: true });
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Get the size of the cache directory in bytes
|
|
135
|
-
*/
|
|
136
|
-
async getCacheSize() {
|
|
137
|
-
if (!existsSync(this.cacheDir)) {
|
|
138
|
-
return 0;
|
|
139
|
-
}
|
|
140
|
-
const files = await fs.readdir(this.cacheDir);
|
|
141
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
142
|
-
let totalSize = 0;
|
|
143
|
-
for (const file of files) {
|
|
144
|
-
const filePath = path.join(this.cacheDir, file);
|
|
145
|
-
const stats = await fs.stat(filePath);
|
|
146
|
-
totalSize += stats.size;
|
|
147
|
-
}
|
|
148
|
-
return totalSize;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Create a singleton instance for convenience
|
|
153
|
-
*/
|
|
154
|
-
export const s3Downloader = new S3Downloader();
|
|
File without changes
|
|
File without changes
|
/package/dist/templates/workflow/{prompt@v1.prompt.template → prompts/prompt@v1.prompt.template}
RENAMED
|
File without changes
|