@outputai/cli 0.15.1-next.59eb401.0 → 0.15.1-next.69acd83.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/dist/assets/docker/docker-compose-dev.yml +1 -1
- package/dist/commands/fix.js +10 -1
- package/dist/commands/fix.spec.js +30 -0
- package/dist/commands/update.js +2 -2
- package/dist/commands/update.spec.js +1 -1
- package/dist/commands/workflow/test.d.ts +1 -1
- package/dist/commands/workflow/test.js +3 -1
- package/dist/commands/workflow/test.spec.js +2 -1
- package/dist/generated/framework_version.json +1 -1
- package/dist/services/cost_calculator.js +1 -1
- package/dist/services/datasets.d.ts +1 -1
- package/dist/services/datasets.js +1 -1
- package/dist/services/fix_package.d.ts +12 -1
- package/dist/services/fix_package.js +35 -0
- package/dist/services/fix_package.spec.js +40 -0
- package/dist/services/workflow_planner.js +1 -1
- package/dist/services/workflow_planner.spec.js +2 -2
- package/dist/templates/agent_instructions/CLAUDE.md.template +3 -3
- package/dist/templates/project/README.md.template +1 -1
- package/dist/templates/project/package.json.template +4 -1
- package/dist/templates/project/src/clients/jina.ts.template +1 -1
- package/dist/templates/project/src/workflows/blog_evaluator/evaluators.ts.template +1 -1
- package/dist/templates/workflow/README.md.template +2 -2
- package/dist/templates/workflow/evaluators.ts.template +1 -1
- package/dist/templates/workflow/steps.ts.template +1 -1
- package/dist/test_helpers/mocks.d.ts +1 -1
- package/dist/test_helpers/mocks.js +1 -1
- package/dist/types/cost.d.ts +1 -1
- package/dist/utils/eval_diagnostics.js +1 -1
- package/dist/utils/eval_naming.d.ts +1 -0
- package/dist/utils/eval_naming.js +2 -0
- package/dist/utils/eval_naming.spec.d.ts +1 -0
- package/dist/utils/eval_naming.spec.js +10 -0
- package/dist/utils/eval_render.d.ts +3 -0
- package/dist/utils/eval_render.js +75 -0
- package/dist/utils/eval_render.spec.d.ts +1 -0
- package/dist/utils/eval_render.spec.js +171 -0
- package/oclif.manifest.json +2 -2
- package/package.json +5 -6
package/dist/commands/fix.js
CHANGED
|
@@ -9,7 +9,8 @@ const Ansi = {
|
|
|
9
9
|
RESET: '\x1b[0m'
|
|
10
10
|
};
|
|
11
11
|
export default class Fix extends Command {
|
|
12
|
-
static description = 'Fix Output scripts in the package.json (reset overwrites, add missing and remove deprecated)
|
|
12
|
+
static description = 'Fix Output scripts in the package.json (reset overwrites, add missing and remove deprecated), ' +
|
|
13
|
+
'replace the @outputai/output package with @outputai/core and @outputai/cli, and remove stale hook files';
|
|
13
14
|
static examples = [
|
|
14
15
|
'<%= config.bin %> <%= command.id %>'
|
|
15
16
|
];
|
|
@@ -30,6 +31,11 @@ export default class Fix extends Command {
|
|
|
30
31
|
this.log('\n Scripts to remove:');
|
|
31
32
|
plan.scriptsToRemove.forEach(({ key }) => this.log(` ${Ansi.RED}-${Ansi.RESET} "${key}"`));
|
|
32
33
|
}
|
|
34
|
+
if (plan.dependenciesToRemove.length > 0 || plan.dependenciesToAdd.length > 0) {
|
|
35
|
+
this.log('\n Dependencies to replace:');
|
|
36
|
+
plan.dependenciesToRemove.forEach(({ section, name, version }) => this.log(` ${Ansi.RED}-${Ansi.RESET} "${name}": "${version}" (${section})`));
|
|
37
|
+
plan.dependenciesToAdd.forEach(({ section, name, version }) => this.log(` ${Ansi.GREEN}+${Ansi.RESET} "${name}": "${version}" (${section})`));
|
|
38
|
+
}
|
|
33
39
|
if (plan.hookFilesToRemove.length > 0) {
|
|
34
40
|
this.log('\n Hook files to remove:');
|
|
35
41
|
plan.hookFilesToRemove.forEach(file => this.log(` ${Ansi.RED}-${Ansi.RESET} "${file}"`));
|
|
@@ -51,6 +57,9 @@ export default class Fix extends Command {
|
|
|
51
57
|
}
|
|
52
58
|
applyFix(plan);
|
|
53
59
|
this.log('Done, package.json is properly configured.');
|
|
60
|
+
if (plan.dependenciesToRemove.length > 0) {
|
|
61
|
+
this.log('Dependencies changed: run your package manager install to update node_modules and the lockfile.');
|
|
62
|
+
}
|
|
54
63
|
}
|
|
55
64
|
catch (error) {
|
|
56
65
|
// ExitPromptError means Ctrl+C
|
|
@@ -17,6 +17,8 @@ const basePlan = () => ({
|
|
|
17
17
|
scriptsToRemove: [{ key: 'dev', value: 'old' }],
|
|
18
18
|
scriptsToAdd: [{ key: 'output:new', value: 'echo new' }],
|
|
19
19
|
scriptsToReplace: [{ key: 'output:dev', before: 'old dev', after: 'output dev' }],
|
|
20
|
+
dependenciesToRemove: [],
|
|
21
|
+
dependenciesToAdd: [],
|
|
20
22
|
hookFilesToRemove: []
|
|
21
23
|
});
|
|
22
24
|
describe('fix command', () => {
|
|
@@ -71,6 +73,34 @@ describe('fix command', () => {
|
|
|
71
73
|
expect(cmd.log).toHaveBeenCalledWith('\n Hook files to remove:');
|
|
72
74
|
expect(cmd.log).toHaveBeenCalledWith(expect.stringContaining('"node_modules/@outputai/credentials/dist/hooks.js"'));
|
|
73
75
|
});
|
|
76
|
+
it('should list dependencies to replace and hint to reinstall after applying', async () => {
|
|
77
|
+
vi.mocked(fixService.planFix).mockReturnValue({
|
|
78
|
+
...basePlan(),
|
|
79
|
+
dependenciesToRemove: [{ section: 'dependencies', name: '@outputai/output', version: '^0.15.0' }],
|
|
80
|
+
dependenciesToAdd: [
|
|
81
|
+
{ section: 'dependencies', name: '@outputai/core', version: '^0.15.0' },
|
|
82
|
+
{ section: 'devDependencies', name: '@outputai/cli', version: '^0.15.0' }
|
|
83
|
+
]
|
|
84
|
+
});
|
|
85
|
+
vi.mocked(confirm).mockResolvedValue(true);
|
|
86
|
+
vi.mocked(fixService.applyFix).mockImplementation(() => { });
|
|
87
|
+
const cmd = createTestCommand();
|
|
88
|
+
await cmd.run();
|
|
89
|
+
expect(cmd.log).toHaveBeenCalledWith('\n Dependencies to replace:');
|
|
90
|
+
expect(cmd.log).toHaveBeenCalledWith(expect.stringContaining('"@outputai/output": "^0.15.0" (dependencies)'));
|
|
91
|
+
expect(cmd.log).toHaveBeenCalledWith(expect.stringContaining('"@outputai/core": "^0.15.0" (dependencies)'));
|
|
92
|
+
expect(cmd.log).toHaveBeenCalledWith(expect.stringContaining('"@outputai/cli": "^0.15.0" (devDependencies)'));
|
|
93
|
+
expect(cmd.log).toHaveBeenCalledWith('Dependencies changed: run your package manager install to update node_modules and the lockfile.');
|
|
94
|
+
});
|
|
95
|
+
it('should not hint to reinstall when dependencies did not change', async () => {
|
|
96
|
+
vi.mocked(fixService.planFix).mockReturnValue(basePlan());
|
|
97
|
+
vi.mocked(confirm).mockResolvedValue(true);
|
|
98
|
+
vi.mocked(fixService.applyFix).mockImplementation(() => { });
|
|
99
|
+
const cmd = createTestCommand();
|
|
100
|
+
await cmd.run();
|
|
101
|
+
expect(cmd.log).not.toHaveBeenCalledWith('\n Dependencies to replace:');
|
|
102
|
+
expect(cmd.log).not.toHaveBeenCalledWith(expect.stringContaining('Dependencies changed'));
|
|
103
|
+
});
|
|
74
104
|
it('should not apply when user declines', async () => {
|
|
75
105
|
vi.mocked(fixService.planFix).mockReturnValue(basePlan());
|
|
76
106
|
vi.mocked(confirm).mockResolvedValue(false);
|
package/dist/commands/update.js
CHANGED
|
@@ -103,8 +103,8 @@ export default class Update extends Command {
|
|
|
103
103
|
if (newLocalVersion) {
|
|
104
104
|
this.log(`\nLocal install updated to v${newLocalVersion}`);
|
|
105
105
|
if (isOutdated(newLocalVersion, latest)) {
|
|
106
|
-
this.warn(`Your package.json
|
|
107
|
-
'Update the
|
|
106
|
+
this.warn(`Your package.json version range limits @outputai/cli to v${newLocalVersion}. ` +
|
|
107
|
+
'Update the range in package.json to get the latest CLI.');
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
else {
|
|
@@ -172,7 +172,7 @@ describe('update command', () => {
|
|
|
172
172
|
.mockReturnValueOnce(true);
|
|
173
173
|
const cmd = createTestCommand({ cli: true });
|
|
174
174
|
await cmd.run();
|
|
175
|
-
expect(cmd.warn).toHaveBeenCalledWith(expect.stringContaining('package.json
|
|
175
|
+
expect(cmd.warn).toHaveBeenCalledWith(expect.stringContaining('package.json version range limits'));
|
|
176
176
|
});
|
|
177
177
|
});
|
|
178
178
|
});
|
|
@@ -5,7 +5,9 @@ import { readAllDatasets, writeDataset } from '#services/datasets.js';
|
|
|
5
5
|
import { fetchWorkflowCatalog } from '#api/workflow_catalog.js';
|
|
6
6
|
import { diagnoseMissingEvalWorkflow } from '#utils/eval_diagnostics.js';
|
|
7
7
|
import { handleApiError } from '#utils/error_handler.js';
|
|
8
|
-
import { getEvalWorkflowName
|
|
8
|
+
import { getEvalWorkflowName } from '#utils/eval_naming.js';
|
|
9
|
+
import { renderEvalOutput, computeExitCode } from '#utils/eval_render.js';
|
|
10
|
+
import { EvalOutputSchema } from '@outputai/core/evals';
|
|
9
11
|
export default class WorkflowTest extends Command {
|
|
10
12
|
static description = 'Run evaluations against a workflow using its datasets';
|
|
11
13
|
static enableJsonFlag = true;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
3
|
-
import { getEvalWorkflowName
|
|
3
|
+
import { getEvalWorkflowName } from '#utils/eval_naming.js';
|
|
4
|
+
import { renderEvalOutput } from '#utils/eval_render.js';
|
|
4
5
|
vi.mock('#api/generated/api.js', () => ({
|
|
5
6
|
postWorkflowRun: vi.fn()
|
|
6
7
|
}));
|
|
@@ -32,7 +32,7 @@ function priceLines(lines, pricing) {
|
|
|
32
32
|
}, 0);
|
|
33
33
|
}
|
|
34
34
|
// Token counts for display. The producer's 'input' line excludes cached tokens
|
|
35
|
-
// (
|
|
35
|
+
// (core/llm emits input − cached), while AI SDK reports inputTokens as the
|
|
36
36
|
// total — add cached back so the columns match what providers report.
|
|
37
37
|
function eventTokenUsage(lines) {
|
|
38
38
|
const sumOf = (type) => lines.filter(l => l.type === type).reduce((s, l) => s + l.amount, 0);
|
|
@@ -2,7 +2,7 @@ import { readFile, writeFile, readdir, mkdir } from 'node:fs/promises';
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { resolve, join } from 'node:path';
|
|
4
4
|
import { load as parseYaml, dump as stringifyYaml } from 'js-yaml';
|
|
5
|
-
import { DatasetSchema } from '@outputai/evals';
|
|
5
|
+
import { DatasetSchema } from '@outputai/core/evals';
|
|
6
6
|
import { getTrace } from '#services/trace_reader.js';
|
|
7
7
|
import { sanitizeSecrets } from '#utils/secret_sanitizer.js';
|
|
8
8
|
import { resolveWorkflowDir, WORKFLOWS_PATHS } from '#utils/workflow_dir.js';
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** Legacy script names from older versions of the project */
|
|
2
2
|
export declare const legacyScripts: readonly ["dev"];
|
|
3
|
+
declare const dependencySections: readonly ["dependencies", "devDependencies"];
|
|
4
|
+
type DependencySection = typeof dependencySections[number];
|
|
3
5
|
export interface ScriptToRemove {
|
|
4
6
|
key: string;
|
|
5
7
|
value: string;
|
|
@@ -13,8 +15,14 @@ export interface ScriptToReplace {
|
|
|
13
15
|
before: string;
|
|
14
16
|
after: string;
|
|
15
17
|
}
|
|
18
|
+
export interface DependencyChange {
|
|
19
|
+
section: DependencySection;
|
|
20
|
+
name: string;
|
|
21
|
+
version: string;
|
|
22
|
+
}
|
|
16
23
|
/**
|
|
17
|
-
* Plan for aligning `scripts` with the scaffold template
|
|
24
|
+
* Plan for aligning `scripts` with the scaffold template, replacing the `@outputai/output` umbrella package
|
|
25
|
+
* and dropping stale hook files (reads only; no write until apply).
|
|
18
26
|
*/
|
|
19
27
|
export interface FixPlan {
|
|
20
28
|
packageJsonPath: string;
|
|
@@ -23,6 +31,8 @@ export interface FixPlan {
|
|
|
23
31
|
scriptsToRemove: ScriptToRemove[];
|
|
24
32
|
scriptsToReplace: ScriptToReplace[];
|
|
25
33
|
scriptsToAdd: ScriptToAdd[];
|
|
34
|
+
dependenciesToRemove: DependencyChange[];
|
|
35
|
+
dependenciesToAdd: DependencyChange[];
|
|
26
36
|
hookFilesToRemove: string[];
|
|
27
37
|
}
|
|
28
38
|
/**
|
|
@@ -33,3 +43,4 @@ export declare function planFix(projectRoot: string): FixPlan;
|
|
|
33
43
|
* Writes the planned package.json. No-op when {@link FixPlan.hasChanges} is false.
|
|
34
44
|
*/
|
|
35
45
|
export declare function applyFix(plan: FixPlan): void;
|
|
46
|
+
export {};
|
|
@@ -8,6 +8,13 @@ const templateRelativePath = path.join('templates', 'project', 'package.json.tem
|
|
|
8
8
|
export const legacyScripts = ['dev'];
|
|
9
9
|
/** Hook files from the @outputai/credentials package, which was folded into @outputai/core */
|
|
10
10
|
const credentialsHookFilePattern = /@outputai[\\/]credentials([\\/]|$)/;
|
|
11
|
+
/** The @outputai/output umbrella package is replaced by its parts: core at runtime, the CLI for development */
|
|
12
|
+
const umbrellaPackage = '@outputai/output';
|
|
13
|
+
const umbrellaReplacements = [
|
|
14
|
+
{ name: '@outputai/core', section: 'dependencies' },
|
|
15
|
+
{ name: '@outputai/cli', section: 'devDependencies' }
|
|
16
|
+
];
|
|
17
|
+
const dependencySections = ['dependencies', 'devDependencies'];
|
|
11
18
|
function getTemplatesPackageJsonPath() {
|
|
12
19
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
20
|
const __dirname = path.dirname(__filename);
|
|
@@ -46,6 +53,31 @@ function removeCredentialsHookFiles(pkg) {
|
|
|
46
53
|
section.hookFiles = hookFiles.filter(file => !removed.includes(file));
|
|
47
54
|
return removed;
|
|
48
55
|
}
|
|
56
|
+
function getDependencySection(pkg, section) {
|
|
57
|
+
const deps = pkg[section];
|
|
58
|
+
return typeof deps === 'object' && deps !== null && !Array.isArray(deps) ? deps : null;
|
|
59
|
+
}
|
|
60
|
+
function replaceUmbrellaPackage(pkg) {
|
|
61
|
+
const dependenciesToRemove = [];
|
|
62
|
+
const dependenciesToAdd = [];
|
|
63
|
+
const section = dependencySections.find(s => typeof getDependencySection(pkg, s)?.[umbrellaPackage] === 'string');
|
|
64
|
+
if (!section) {
|
|
65
|
+
return { dependenciesToRemove, dependenciesToAdd };
|
|
66
|
+
}
|
|
67
|
+
const deps = getDependencySection(pkg, section);
|
|
68
|
+
const version = deps[umbrellaPackage];
|
|
69
|
+
delete deps[umbrellaPackage];
|
|
70
|
+
dependenciesToRemove.push({ section, name: umbrellaPackage, version });
|
|
71
|
+
const isDeclared = (name) => dependencySections.some(s => Object.hasOwn(getDependencySection(pkg, s) ?? {}, name));
|
|
72
|
+
for (const { name, section: target } of umbrellaReplacements) {
|
|
73
|
+
if (isDeclared(name)) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
pkg[target] = { ...getDependencySection(pkg, target), [name]: version };
|
|
77
|
+
dependenciesToAdd.push({ section: target, name, version });
|
|
78
|
+
}
|
|
79
|
+
return { dependenciesToRemove, dependenciesToAdd };
|
|
80
|
+
}
|
|
49
81
|
function parseTemplatePackageJson(processed) {
|
|
50
82
|
try {
|
|
51
83
|
return JSON.parse(processed);
|
|
@@ -95,6 +127,7 @@ export function planFix(projectRoot) {
|
|
|
95
127
|
scripts[key] = after;
|
|
96
128
|
}
|
|
97
129
|
pkg.scripts = scripts;
|
|
130
|
+
const { dependenciesToRemove, dependenciesToAdd } = replaceUmbrellaPackage(pkg);
|
|
98
131
|
const hookFilesToRemove = removeCredentialsHookFiles(pkg);
|
|
99
132
|
const packageJsonUpdatedContent = `${JSON.stringify(pkg, null, 2)}\n`;
|
|
100
133
|
const hasChanges = packageJsonUpdatedContent !== raw;
|
|
@@ -105,6 +138,8 @@ export function planFix(projectRoot) {
|
|
|
105
138
|
scriptsToRemove,
|
|
106
139
|
scriptsToReplace,
|
|
107
140
|
scriptsToAdd,
|
|
141
|
+
dependenciesToRemove,
|
|
142
|
+
dependenciesToAdd,
|
|
108
143
|
hookFilesToRemove
|
|
109
144
|
};
|
|
110
145
|
}
|
|
@@ -84,6 +84,46 @@ describe('fix package', () => {
|
|
|
84
84
|
expect(malformedPlan.hookFilesToRemove).toEqual([]);
|
|
85
85
|
expect(JSON.parse(malformedPlan.packageJsonUpdatedContent).outputai).toEqual(malformed.outputai);
|
|
86
86
|
});
|
|
87
|
+
it('should replace @outputai/output in dependencies with core and a cli dev dependency at the same version', async () => {
|
|
88
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fix-test-'));
|
|
89
|
+
const pkg = { name: 'my-proj', dependencies: { '@outputai/output': '^0.15.0', zod: '4.0.0' } };
|
|
90
|
+
await fs.writeFile(path.join(tmpDir, 'package.json'), JSON.stringify(pkg, null, 2), 'utf-8');
|
|
91
|
+
const plan = planFix(tmpDir);
|
|
92
|
+
expect(plan.dependenciesToRemove).toEqual([{ section: 'dependencies', name: '@outputai/output', version: '^0.15.0' }]);
|
|
93
|
+
expect(plan.dependenciesToAdd).toEqual([
|
|
94
|
+
{ section: 'dependencies', name: '@outputai/core', version: '^0.15.0' },
|
|
95
|
+
{ section: 'devDependencies', name: '@outputai/cli', version: '^0.15.0' }
|
|
96
|
+
]);
|
|
97
|
+
applyFix(plan);
|
|
98
|
+
const next = JSON.parse(await fs.readFile(path.join(tmpDir, 'package.json'), 'utf-8'));
|
|
99
|
+
expect(next.dependencies).toEqual({ zod: '4.0.0', '@outputai/core': '^0.15.0' });
|
|
100
|
+
expect(next.devDependencies).toEqual({ '@outputai/cli': '^0.15.0' });
|
|
101
|
+
});
|
|
102
|
+
it('should replace @outputai/output in devDependencies and keep already declared replacements', async () => {
|
|
103
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fix-test-'));
|
|
104
|
+
const pkg = {
|
|
105
|
+
name: 'my-proj',
|
|
106
|
+
dependencies: { '@outputai/core': '0.15.0' },
|
|
107
|
+
devDependencies: { '@outputai/output': '^0.15.0' }
|
|
108
|
+
};
|
|
109
|
+
await fs.writeFile(path.join(tmpDir, 'package.json'), JSON.stringify(pkg, null, 2), 'utf-8');
|
|
110
|
+
const plan = planFix(tmpDir);
|
|
111
|
+
expect(plan.dependenciesToRemove).toEqual([{ section: 'devDependencies', name: '@outputai/output', version: '^0.15.0' }]);
|
|
112
|
+
expect(plan.dependenciesToAdd).toEqual([{ section: 'devDependencies', name: '@outputai/cli', version: '^0.15.0' }]);
|
|
113
|
+
const next = JSON.parse(plan.packageJsonUpdatedContent);
|
|
114
|
+
expect(next.dependencies).toEqual({ '@outputai/core': '0.15.0' });
|
|
115
|
+
expect(next.devDependencies).toEqual({ '@outputai/cli': '^0.15.0' });
|
|
116
|
+
});
|
|
117
|
+
it('should leave dependencies untouched when @outputai/output is not declared', async () => {
|
|
118
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fix-test-'));
|
|
119
|
+
const pkg = { name: 'my-proj', dependencies: { '@outputai/core': '0.15.0' }, devDependencies: { '@outputai/cli': '0.15.0' } };
|
|
120
|
+
await fs.writeFile(path.join(tmpDir, 'package.json'), JSON.stringify(pkg, null, 2), 'utf-8');
|
|
121
|
+
const plan = planFix(tmpDir);
|
|
122
|
+
expect(plan.dependenciesToRemove).toEqual([]);
|
|
123
|
+
expect(plan.dependenciesToAdd).toEqual([]);
|
|
124
|
+
expect(JSON.parse(plan.packageJsonUpdatedContent).dependencies).toEqual(pkg.dependencies);
|
|
125
|
+
expect(JSON.parse(plan.packageJsonUpdatedContent).devDependencies).toEqual(pkg.devDependencies);
|
|
126
|
+
});
|
|
87
127
|
it('should throw when package.json is missing', async () => {
|
|
88
128
|
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'fix-test-'));
|
|
89
129
|
expect(() => planFix(tmpDir)).toThrow(/No package\.json found/);
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
2
|
import { generatePlanName, writePlanFile, updateAgentTemplates } from './workflow_planner.js';
|
|
3
3
|
import { initializeAgentConfig } from './coding_agents.js';
|
|
4
|
-
import { generateText } from '@outputai/llm';
|
|
4
|
+
import { generateText } from '@outputai/core/llm';
|
|
5
5
|
import fs from 'node:fs/promises';
|
|
6
6
|
vi.mock('./coding_agents.js');
|
|
7
|
-
vi.mock('@outputai/llm');
|
|
7
|
+
vi.mock('@outputai/core/llm');
|
|
8
8
|
vi.mock('node:fs/promises');
|
|
9
9
|
const mockGenerateTextResult = (text) => ({
|
|
10
10
|
text,
|
|
@@ -35,7 +35,7 @@ src/
|
|
|
35
35
|
types.ts # Zod schemas and TypeScript types
|
|
36
36
|
prompts/ # .prompt files (Liquid.js templates with YAML frontmatter)
|
|
37
37
|
scenarios/ # Test scenario JSON files
|
|
38
|
-
clients/ # Shared HTTP clients (use @outputai/http, not fetch/axios)
|
|
38
|
+
clients/ # Shared HTTP clients (use @outputai/core/http, not fetch/axios)
|
|
39
39
|
shared/ # Shared utilities across workflows
|
|
40
40
|
config/
|
|
41
41
|
costs.yml # Token/API pricing overrides
|
|
@@ -46,8 +46,8 @@ config/
|
|
|
46
46
|
## Key Conventions
|
|
47
47
|
|
|
48
48
|
- **Workflows are deterministic**: No I/O, no `Date.now()`, no `Math.random()` in `workflow.ts`. All side effects go in steps or evaluators.
|
|
49
|
-
- **HTTP clients**: Use `outputFetch` or `createKyClient` from `@outputai/http` instead of raw `fetch` or `axios`. Requests are automatically traced; for paid APIs, attach spend via `addRequestCost` in an `afterResponse` hook (`output-dev-http-client-create`) and forward cost events with `output-dev-cost-hooks`.
|
|
50
|
-
- **LLM calls**: Use `generateText` from `@outputai/llm` with `.prompt` files. Never call LLM APIs directly.
|
|
49
|
+
- **HTTP clients**: Use `outputFetch` or `createKyClient` from `@outputai/core/http` instead of raw `fetch` or `axios`. Requests are automatically traced; for paid APIs, attach spend via `addRequestCost` in an `afterResponse` hook (`output-dev-http-client-create`) and forward cost events with `output-dev-cost-hooks`.
|
|
50
|
+
- **LLM calls**: Use `generateText` from `@outputai/core/llm` with `.prompt` files. Never call LLM APIs directly.
|
|
51
51
|
|
|
52
52
|
---
|
|
53
53
|
|
|
@@ -27,7 +27,7 @@ src/
|
|
|
27
27
|
|
|
28
28
|
### Clients Directory
|
|
29
29
|
|
|
30
|
-
The `src/clients/` directory contains API clients using `@outputai/http` for external services.
|
|
30
|
+
The `src/clients/` directory contains API clients using `@outputai/core/http` for external services.
|
|
31
31
|
|
|
32
32
|
### Shared Directory
|
|
33
33
|
|
|
@@ -14,9 +14,12 @@
|
|
|
14
14
|
"output:dev": "output dev"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@
|
|
17
|
+
"@ai-sdk/anthropic": "4.0.61",
|
|
18
|
+
"@ai-sdk/openai": "4.0.73",
|
|
19
|
+
"@outputai/core": "{{frameworkVersion}}"
|
|
18
20
|
},
|
|
19
21
|
"devDependencies": {
|
|
22
|
+
"@outputai/cli": "{{frameworkVersion}}",
|
|
20
23
|
"@types/node": "24.5.2",
|
|
21
24
|
"nodemon": "3.1.14",
|
|
22
25
|
"typescript": "5.9.3"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { evaluator, EvaluationNumberResult } from '@outputai/core';
|
|
2
2
|
import type { EvaluationResultArgs } from '@outputai/core';
|
|
3
|
-
import { generateText, aiSdk } from '@outputai/llm';
|
|
3
|
+
import { generateText, aiSdk } from '@outputai/core/llm';
|
|
4
4
|
import { blogContentSchema } from './types.js';
|
|
5
5
|
import type { BlogContent } from './types.js';
|
|
6
6
|
|
|
@@ -123,7 +123,7 @@ Define steps in `steps.ts` with schemas:
|
|
|
123
123
|
|
|
124
124
|
```typescript
|
|
125
125
|
import { step, z } from '@outputai/core';
|
|
126
|
-
import { generateText, aiSdk } from '@outputai/llm';
|
|
126
|
+
import { generateText, aiSdk } from '@outputai/core/llm';
|
|
127
127
|
|
|
128
128
|
export const myStep = step( {
|
|
129
129
|
name: 'my_step',
|
|
@@ -153,7 +153,7 @@ Define evaluators in `evaluators.ts`:
|
|
|
153
153
|
|
|
154
154
|
```typescript
|
|
155
155
|
import { evaluator, z, EvaluationVerdictResult } from '@outputai/core';
|
|
156
|
-
import { generateText, aiSdk } from '@outputai/llm';
|
|
156
|
+
import { generateText, aiSdk } from '@outputai/core/llm';
|
|
157
157
|
|
|
158
158
|
export const evaluateQuality = evaluator( {
|
|
159
159
|
name: 'evaluate_quality',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { evaluator, z, EvaluationVerdictResult } from '@outputai/core';
|
|
2
|
-
import { generateText, aiSdk } from '@outputai/llm';
|
|
2
|
+
import { generateText, aiSdk } from '@outputai/core/llm';
|
|
3
3
|
|
|
4
4
|
// TODO: Update the evaluator to assess your workflow's output quality
|
|
5
5
|
export const evaluate{{WorkflowName}} = evaluator( {
|
package/dist/types/cost.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface TokenUsage {
|
|
|
13
13
|
cachedInputTokens?: number;
|
|
14
14
|
reasoningTokens?: number;
|
|
15
15
|
}
|
|
16
|
-
import type { LLMGenerationCost, LLMGenerationUsage, LLMUsageEvent } from '@outputai/llm';
|
|
16
|
+
import type { LLMGenerationCost, LLMGenerationUsage, LLMUsageEvent } from '@outputai/core/llm';
|
|
17
17
|
export type LLMUsageLine = LLMUsageEvent['usage'][number];
|
|
18
18
|
export interface HTTPCostEvent {
|
|
19
19
|
type: 'http:request:cost';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import { getEvalWorkflowName } from '@outputai/evals';
|
|
4
3
|
import { resolveWorkflowDir } from '#utils/workflow_dir.js';
|
|
4
|
+
import { getEvalWorkflowName } from '#utils/eval_naming.js';
|
|
5
5
|
const EVAL_WORKFLOW_FILES = ['tests/evals/workflow.ts', 'tests/evals/workflow.js'];
|
|
6
6
|
/**
|
|
7
7
|
* Explain why a `<wf>_eval` workflow isn't registered. When the eval source
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getEvalWorkflowName: (workflowName: string) => string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { getEvalWorkflowName } from './eval_naming.js';
|
|
3
|
+
describe('naming', () => {
|
|
4
|
+
describe('getEvalWorkflowName', () => {
|
|
5
|
+
it('appends _eval suffix', () => {
|
|
6
|
+
expect(getEvalWorkflowName('simple')).toBe('simple_eval');
|
|
7
|
+
expect(getEvalWorkflowName('my_workflow')).toBe('my_workflow_eval');
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { VERDICT, CRITICALITY } from '@outputai/core/evals';
|
|
2
|
+
const COLORS = {
|
|
3
|
+
reset: '\x1b[0m',
|
|
4
|
+
green: '\x1b[32m',
|
|
5
|
+
yellow: '\x1b[33m',
|
|
6
|
+
red: '\x1b[31m',
|
|
7
|
+
dim: '\x1b[2m',
|
|
8
|
+
bold: '\x1b[1m',
|
|
9
|
+
cyan: '\x1b[36m'
|
|
10
|
+
};
|
|
11
|
+
const VERDICT_STYLE = {
|
|
12
|
+
[VERDICT.PASS]: { label: 'PASS', color: COLORS.green },
|
|
13
|
+
[VERDICT.PARTIAL]: { label: 'PARTIAL', color: COLORS.yellow },
|
|
14
|
+
[VERDICT.FAIL]: { label: 'FAIL', color: COLORS.red }
|
|
15
|
+
};
|
|
16
|
+
function colorize(text, color) {
|
|
17
|
+
return `${color}${text}${COLORS.reset}`;
|
|
18
|
+
}
|
|
19
|
+
function padWithDots(label, width = 30) {
|
|
20
|
+
const dotCount = Math.max(2, width - label.length);
|
|
21
|
+
return ` ${'.'.repeat(dotCount)} `;
|
|
22
|
+
}
|
|
23
|
+
function renderEvaluator(evaluator) {
|
|
24
|
+
const lines = [];
|
|
25
|
+
const style = VERDICT_STYLE[evaluator.verdict];
|
|
26
|
+
const prefix = evaluator.criticality === CRITICALITY.INFORMATIONAL ?
|
|
27
|
+
`${COLORS.dim}(info)${COLORS.reset} ` :
|
|
28
|
+
'';
|
|
29
|
+
lines.push(` ${prefix}${evaluator.name}${padWithDots(evaluator.name, 24)}${colorize(evaluator.verdict, style.color)}`);
|
|
30
|
+
if (evaluator.reasoning && evaluator.verdict !== VERDICT.PASS) {
|
|
31
|
+
lines.push(` ${COLORS.dim}-> ${evaluator.reasoning}${COLORS.reset}`);
|
|
32
|
+
}
|
|
33
|
+
if (evaluator.feedback) {
|
|
34
|
+
for (const fb of evaluator.feedback) {
|
|
35
|
+
const item = fb;
|
|
36
|
+
if (item.issue) {
|
|
37
|
+
lines.push(` ${COLORS.dim}-> ${item.issue}${COLORS.reset}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return lines;
|
|
42
|
+
}
|
|
43
|
+
function renderCase(evalCase) {
|
|
44
|
+
const caseStyle = VERDICT_STYLE[evalCase.verdict];
|
|
45
|
+
const lines = [
|
|
46
|
+
` ${evalCase.datasetName}${padWithDots(evalCase.datasetName)}${colorize(caseStyle.label, caseStyle.color)}`
|
|
47
|
+
];
|
|
48
|
+
evalCase.evaluators.forEach(evaluator => lines.push(...renderEvaluator(evaluator)));
|
|
49
|
+
lines.push('');
|
|
50
|
+
return lines;
|
|
51
|
+
}
|
|
52
|
+
export function renderEvalOutput(evalOutput, evalName) {
|
|
53
|
+
const lines = [];
|
|
54
|
+
if (evalName) {
|
|
55
|
+
lines.push(colorize(evalName, COLORS.bold));
|
|
56
|
+
lines.push('');
|
|
57
|
+
}
|
|
58
|
+
evalOutput.cases.forEach(evalCase => lines.push(...renderCase(evalCase)));
|
|
59
|
+
const { summary } = evalOutput;
|
|
60
|
+
const parts = [];
|
|
61
|
+
if (summary.passed > 0) {
|
|
62
|
+
parts.push(colorize(`${summary.passed} passed`, COLORS.green));
|
|
63
|
+
}
|
|
64
|
+
if (summary.partial > 0) {
|
|
65
|
+
parts.push(colorize(`${summary.partial} partial`, COLORS.yellow));
|
|
66
|
+
}
|
|
67
|
+
if (summary.failed > 0) {
|
|
68
|
+
parts.push(colorize(`${summary.failed} failed`, COLORS.red));
|
|
69
|
+
}
|
|
70
|
+
lines.push(`${parts.join(', ')} (${Math.round(summary.acceptableRate * 100)}% acceptable)`);
|
|
71
|
+
return lines.join('\n');
|
|
72
|
+
}
|
|
73
|
+
export function computeExitCode(evalOutput) {
|
|
74
|
+
return evalOutput.cases.some(c => c.verdict === VERDICT.FAIL) ? 1 : 0;
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { renderEvalOutput, computeExitCode } from './eval_render.js';
|
|
3
|
+
const makeOutput = (cases, overrides) => {
|
|
4
|
+
const passed = cases.filter(c => c.verdict === 'pass').length;
|
|
5
|
+
const partial = cases.filter(c => c.verdict === 'partial').length;
|
|
6
|
+
const failed = cases.filter(c => c.verdict === 'fail').length;
|
|
7
|
+
const total = cases.length;
|
|
8
|
+
return {
|
|
9
|
+
cases,
|
|
10
|
+
summary: {
|
|
11
|
+
total,
|
|
12
|
+
passed,
|
|
13
|
+
partial,
|
|
14
|
+
failed,
|
|
15
|
+
acceptableRate: total > 0 ? (passed + partial) / total : 0,
|
|
16
|
+
...overrides
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
describe('computeExitCode', () => {
|
|
21
|
+
it('returns 0 when all cases pass', () => {
|
|
22
|
+
const output = makeOutput([
|
|
23
|
+
{ datasetName: 'a', verdict: 'pass', evaluators: [] }
|
|
24
|
+
]);
|
|
25
|
+
expect(computeExitCode(output)).toBe(0);
|
|
26
|
+
});
|
|
27
|
+
it('returns 0 when cases are partial but none fail', () => {
|
|
28
|
+
const output = makeOutput([
|
|
29
|
+
{ datasetName: 'a', verdict: 'partial', evaluators: [] }
|
|
30
|
+
]);
|
|
31
|
+
expect(computeExitCode(output)).toBe(0);
|
|
32
|
+
});
|
|
33
|
+
it('returns 1 when any case fails', () => {
|
|
34
|
+
const output = makeOutput([
|
|
35
|
+
{ datasetName: 'a', verdict: 'pass', evaluators: [] },
|
|
36
|
+
{ datasetName: 'b', verdict: 'fail', evaluators: [] }
|
|
37
|
+
]);
|
|
38
|
+
expect(computeExitCode(output)).toBe(1);
|
|
39
|
+
});
|
|
40
|
+
it('returns 0 for empty cases', () => {
|
|
41
|
+
const output = makeOutput([]);
|
|
42
|
+
expect(computeExitCode(output)).toBe(0);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
describe('renderEvalOutput', () => {
|
|
46
|
+
it('includes eval name when provided', () => {
|
|
47
|
+
const output = makeOutput([
|
|
48
|
+
{ datasetName: 'test', verdict: 'pass', evaluators: [] }
|
|
49
|
+
]);
|
|
50
|
+
const rendered = renderEvalOutput(output, 'my_eval');
|
|
51
|
+
expect(rendered).toContain('my_eval');
|
|
52
|
+
});
|
|
53
|
+
it('includes dataset names', () => {
|
|
54
|
+
const output = makeOutput([
|
|
55
|
+
{ datasetName: 'basic_input', verdict: 'pass', evaluators: [] },
|
|
56
|
+
{ datasetName: 'edge_case', verdict: 'fail', evaluators: [] }
|
|
57
|
+
]);
|
|
58
|
+
const rendered = renderEvalOutput(output);
|
|
59
|
+
expect(rendered).toContain('basic_input');
|
|
60
|
+
expect(rendered).toContain('edge_case');
|
|
61
|
+
});
|
|
62
|
+
it('includes verdict labels', () => {
|
|
63
|
+
const output = makeOutput([
|
|
64
|
+
{ datasetName: 'a', verdict: 'pass', evaluators: [] },
|
|
65
|
+
{ datasetName: 'b', verdict: 'fail', evaluators: [] }
|
|
66
|
+
]);
|
|
67
|
+
const rendered = renderEvalOutput(output);
|
|
68
|
+
expect(rendered).toContain('PASS');
|
|
69
|
+
expect(rendered).toContain('FAIL');
|
|
70
|
+
});
|
|
71
|
+
it('shows reasoning for failed evaluators', () => {
|
|
72
|
+
const output = makeOutput([
|
|
73
|
+
{
|
|
74
|
+
datasetName: 'test',
|
|
75
|
+
verdict: 'fail',
|
|
76
|
+
evaluators: [{
|
|
77
|
+
name: 'check',
|
|
78
|
+
verdict: 'fail',
|
|
79
|
+
criticality: 'required',
|
|
80
|
+
reasoning: 'Expected 15, got null'
|
|
81
|
+
}]
|
|
82
|
+
}
|
|
83
|
+
]);
|
|
84
|
+
const rendered = renderEvalOutput(output);
|
|
85
|
+
expect(rendered).toContain('Expected 15, got null');
|
|
86
|
+
});
|
|
87
|
+
it('hides reasoning for passing evaluators', () => {
|
|
88
|
+
const output = makeOutput([
|
|
89
|
+
{
|
|
90
|
+
datasetName: 'test',
|
|
91
|
+
verdict: 'pass',
|
|
92
|
+
evaluators: [{
|
|
93
|
+
name: 'check',
|
|
94
|
+
verdict: 'pass',
|
|
95
|
+
criticality: 'required',
|
|
96
|
+
reasoning: 'All good'
|
|
97
|
+
}]
|
|
98
|
+
}
|
|
99
|
+
]);
|
|
100
|
+
const rendered = renderEvalOutput(output);
|
|
101
|
+
expect(rendered).not.toContain('All good');
|
|
102
|
+
});
|
|
103
|
+
it('shows summary with acceptable rate', () => {
|
|
104
|
+
const output = makeOutput([
|
|
105
|
+
{ datasetName: 'a', verdict: 'pass', evaluators: [] },
|
|
106
|
+
{ datasetName: 'b', verdict: 'fail', evaluators: [] }
|
|
107
|
+
]);
|
|
108
|
+
const rendered = renderEvalOutput(output);
|
|
109
|
+
expect(rendered).toContain('1 passed');
|
|
110
|
+
expect(rendered).toContain('1 failed');
|
|
111
|
+
expect(rendered).toContain('50% acceptable');
|
|
112
|
+
});
|
|
113
|
+
it('includes PARTIAL verdict label', () => {
|
|
114
|
+
const output = makeOutput([
|
|
115
|
+
{ datasetName: 'a', verdict: 'partial', evaluators: [] }
|
|
116
|
+
]);
|
|
117
|
+
const rendered = renderEvalOutput(output);
|
|
118
|
+
expect(rendered).toContain('PARTIAL');
|
|
119
|
+
});
|
|
120
|
+
it('shows reasoning for partial evaluators', () => {
|
|
121
|
+
const output = makeOutput([
|
|
122
|
+
{
|
|
123
|
+
datasetName: 'test',
|
|
124
|
+
verdict: 'partial',
|
|
125
|
+
evaluators: [{
|
|
126
|
+
name: 'check',
|
|
127
|
+
verdict: 'partial',
|
|
128
|
+
criticality: 'required',
|
|
129
|
+
reasoning: 'Close but not quite'
|
|
130
|
+
}]
|
|
131
|
+
}
|
|
132
|
+
]);
|
|
133
|
+
const rendered = renderEvalOutput(output);
|
|
134
|
+
expect(rendered).toContain('Close but not quite');
|
|
135
|
+
});
|
|
136
|
+
it('renders feedback issues', () => {
|
|
137
|
+
const output = makeOutput([
|
|
138
|
+
{
|
|
139
|
+
datasetName: 'test',
|
|
140
|
+
verdict: 'fail',
|
|
141
|
+
evaluators: [{
|
|
142
|
+
name: 'check',
|
|
143
|
+
verdict: 'fail',
|
|
144
|
+
criticality: 'required',
|
|
145
|
+
feedback: [
|
|
146
|
+
{ issue: 'Missing required field' },
|
|
147
|
+
{ issue: 'Invalid format' }
|
|
148
|
+
]
|
|
149
|
+
}]
|
|
150
|
+
}
|
|
151
|
+
]);
|
|
152
|
+
const rendered = renderEvalOutput(output);
|
|
153
|
+
expect(rendered).toContain('Missing required field');
|
|
154
|
+
expect(rendered).toContain('Invalid format');
|
|
155
|
+
});
|
|
156
|
+
it('marks informational evaluators with (info) prefix', () => {
|
|
157
|
+
const output = makeOutput([
|
|
158
|
+
{
|
|
159
|
+
datasetName: 'test',
|
|
160
|
+
verdict: 'pass',
|
|
161
|
+
evaluators: [{
|
|
162
|
+
name: 'info_check',
|
|
163
|
+
verdict: 'fail',
|
|
164
|
+
criticality: 'informational'
|
|
165
|
+
}]
|
|
166
|
+
}
|
|
167
|
+
]);
|
|
168
|
+
const rendered = renderEvalOutput(output);
|
|
169
|
+
expect(rendered).toContain('(info)');
|
|
170
|
+
});
|
|
171
|
+
});
|
package/oclif.manifest.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"fix": {
|
|
4
4
|
"aliases": [],
|
|
5
5
|
"args": {},
|
|
6
|
-
"description": "Fix Output scripts in the package.json (reset overwrites, add missing and remove deprecated) and remove stale hook files",
|
|
6
|
+
"description": "Fix Output scripts in the package.json (reset overwrites, add missing and remove deprecated), replace the @outputai/output package with @outputai/core and @outputai/cli, and remove stale hook files",
|
|
7
7
|
"examples": [
|
|
8
8
|
"<%= config.bin %> <%= command.id %>"
|
|
9
9
|
],
|
|
@@ -1772,5 +1772,5 @@
|
|
|
1772
1772
|
]
|
|
1773
1773
|
}
|
|
1774
1774
|
},
|
|
1775
|
-
"version": "0.15.1-next.
|
|
1775
|
+
"version": "0.15.1-next.69acd83.0"
|
|
1776
1776
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@outputai/cli",
|
|
3
|
-
"version": "0.15.1-next.
|
|
3
|
+
"version": "0.15.1-next.69acd83.0",
|
|
4
4
|
"description": "CLI for Output.ai workflow generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"output": "./bin/run.js"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"@ai-sdk/anthropic": "4.0.61",
|
|
20
21
|
"@anthropic-ai/claude-agent-sdk": "0.3.281",
|
|
21
22
|
"@hackylabs/deep-redact": "3.0.5",
|
|
22
23
|
"@inquirer/prompts": "8.7.2",
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
"cli-table3": "0.6.5",
|
|
28
29
|
"date-fns": "4.4.0",
|
|
29
30
|
"debug": "4.4.3",
|
|
30
|
-
"handlebars": "4.7.
|
|
31
|
+
"handlebars": "4.7.10",
|
|
31
32
|
"ink": "7.0.1",
|
|
32
33
|
"ink-spinner": "5.0.0",
|
|
33
34
|
"js-yaml": "5.4.2",
|
|
@@ -35,10 +36,8 @@
|
|
|
35
36
|
"ky": "2.0.2",
|
|
36
37
|
"react": "19.3.0",
|
|
37
38
|
"semver": "7.8.5",
|
|
38
|
-
"undici": "8.
|
|
39
|
-
"@outputai/core": "0.15.1-next.
|
|
40
|
-
"@outputai/llm": "0.15.1-next.59eb401.0",
|
|
41
|
-
"@outputai/evals": "0.15.1-next.59eb401.0"
|
|
39
|
+
"undici": "8.10.2",
|
|
40
|
+
"@outputai/core": "0.15.1-next.69acd83.0"
|
|
42
41
|
},
|
|
43
42
|
"devDependencies": {
|
|
44
43
|
"@types/cli-progress": "3.11.6",
|