@outputai/cli 0.7.1-next.d67ad85.0 → 0.7.1-next.de30052.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.
@@ -0,0 +1,7 @@
1
+ import { type Workflow } from './generated/api.js';
2
+ /**
3
+ * Resolve the workflows in a catalog. When `catalog` is provided (e.g. from
4
+ * `--catalog`/`OUTPUT_CATALOG_ID`) it resolves that specific catalog, otherwise
5
+ * the API server's default catalog. Returns `[]` when the catalog has no workflows.
6
+ */
7
+ export declare function fetchWorkflowCatalog(catalog?: string): Promise<Workflow[]>;
@@ -0,0 +1,11 @@
1
+ import { getWorkflowCatalog, getWorkflowCatalogId } from './generated/api.js';
2
+ /**
3
+ * Resolve the workflows in a catalog. When `catalog` is provided (e.g. from
4
+ * `--catalog`/`OUTPUT_CATALOG_ID`) it resolves that specific catalog, otherwise
5
+ * the API server's default catalog. Returns `[]` when the catalog has no workflows.
6
+ */
7
+ export async function fetchWorkflowCatalog(catalog) {
8
+ const response = catalog ? await getWorkflowCatalogId(catalog) : await getWorkflowCatalog();
9
+ const data = response?.data;
10
+ return data?.workflows ?? [];
11
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import * as api from './generated/api.js';
3
+ import { fetchWorkflowCatalog } from './workflow_catalog.js';
4
+ vi.mock('./generated/api.js', () => ({
5
+ getWorkflowCatalog: vi.fn(),
6
+ getWorkflowCatalogId: vi.fn()
7
+ }));
8
+ describe('fetchWorkflowCatalog', () => {
9
+ beforeEach(() => {
10
+ vi.clearAllMocks();
11
+ });
12
+ it('fetches the default catalog when no catalog id is provided', async () => {
13
+ vi.mocked(api.getWorkflowCatalog).mockResolvedValue({ data: { workflows: [{ name: 'a' }] } });
14
+ const result = await fetchWorkflowCatalog();
15
+ expect(api.getWorkflowCatalog).toHaveBeenCalledTimes(1);
16
+ expect(api.getWorkflowCatalogId).not.toHaveBeenCalled();
17
+ expect(result).toEqual([{ name: 'a' }]);
18
+ });
19
+ it('fetches a specific catalog by id when one is provided', async () => {
20
+ vi.mocked(api.getWorkflowCatalogId).mockResolvedValue({ data: { workflows: [{ name: 'b' }] } });
21
+ const result = await fetchWorkflowCatalog('my-catalog');
22
+ expect(api.getWorkflowCatalogId).toHaveBeenCalledWith('my-catalog');
23
+ expect(api.getWorkflowCatalog).not.toHaveBeenCalled();
24
+ expect(result).toEqual([{ name: 'b' }]);
25
+ });
26
+ it('returns an empty array when the catalog response has no workflows', async () => {
27
+ vi.mocked(api.getWorkflowCatalog).mockResolvedValue({ data: {} });
28
+ expect(await fetchWorkflowCatalog()).toEqual([]);
29
+ });
30
+ });
@@ -80,7 +80,7 @@ services:
80
80
  condition: service_healthy
81
81
  worker:
82
82
  condition: service_healthy
83
- image: outputai/api:${OUTPUT_API_VERSION:-0.7.1-next.d67ad85.0}
83
+ image: outputai/api:${OUTPUT_API_VERSION:-0.7.1-next.de30052.0}
84
84
  init: true
85
85
  networks:
86
86
  - main
@@ -9,10 +9,12 @@ interface WorkflowDisplay {
9
9
  aliases: string;
10
10
  }
11
11
  export declare function parseWorkflowForDisplay(workflow: Workflow): WorkflowDisplay;
12
+ export declare function formatWorkflowsAsList(workflows: Workflow[]): string;
12
13
  export default class WorkflowList extends Command {
13
14
  static description: string;
14
15
  static examples: string[];
15
16
  static flags: {
17
+ catalog: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
18
  format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
17
19
  detailed: import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
20
  filter: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,6 +1,6 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
2
  import Table from 'cli-table3';
3
- import { getWorkflowCatalog } from '#api/generated/api.js';
3
+ import { fetchWorkflowCatalog } from '#api/workflow_catalog.js';
4
4
  import { parseWorkflowDefinition, formatParameters } from '#api/parser.js';
5
5
  import { handleApiError } from '#utils/error_handler.js';
6
6
  import { listScenariosForWorkflow } from '#utils/scenario_resolver.js';
@@ -65,10 +65,13 @@ function createWorkflowTable(workflows, detailed) {
65
65
  });
66
66
  return table.toString();
67
67
  }
68
- function formatWorkflowsAsList(workflows) {
69
- const sortedWorkflows = sortWorkflowsByName(workflows);
70
- const names = sortedWorkflows.map(w => parseWorkflowForDisplay(w).name);
71
- return `\nWorkflows:\n\n${names.map(name => `- ${name}`).join('\n')}`;
68
+ function formatWorkflowAsListItem(workflow) {
69
+ const { name, aliases } = parseWorkflowForDisplay(workflow);
70
+ return aliases === 'none' ? `- ${name}` : `- ${name} (aliases: ${aliases})`;
71
+ }
72
+ export function formatWorkflowsAsList(workflows) {
73
+ const lines = sortWorkflowsByName(workflows).map(formatWorkflowAsListItem);
74
+ return `\nWorkflows:\n\n${lines.join('\n')}`;
72
75
  }
73
76
  function formatWorkflowsAsJson(workflows) {
74
77
  const output = {
@@ -103,9 +106,18 @@ export default class WorkflowList extends Command {
103
106
  '<%= config.bin %> <%= command.id %> --format table',
104
107
  '<%= config.bin %> <%= command.id %> --format json',
105
108
  '<%= config.bin %> <%= command.id %> --detailed',
106
- '<%= config.bin %> <%= command.id %> --filter simple'
109
+ '<%= config.bin %> <%= command.id %> --filter simple',
110
+ '<%= config.bin %> <%= command.id %> --catalog my-catalog'
107
111
  ];
108
112
  static flags = {
113
+ catalog: Flags.string({
114
+ char: 'c',
115
+ aliases: ['task-queue'],
116
+ charAliases: ['q'],
117
+ deprecateAliases: true,
118
+ description: 'Catalog to list workflows from (defaults to OUTPUT_CATALOG_ID)',
119
+ env: 'OUTPUT_CATALOG_ID'
120
+ }),
109
121
  format: Flags.string({
110
122
  char: 'f',
111
123
  description: 'Output format',
@@ -123,25 +135,15 @@ export default class WorkflowList extends Command {
123
135
  };
124
136
  async run() {
125
137
  const { flags } = await this.parse(WorkflowList);
126
- this.log('Fetching workflow catalog...');
127
- const response = await getWorkflowCatalog();
128
- if (!response) {
129
- this.error('Failed to connect to API server. Is it running?', { exit: 1 });
130
- }
131
- if (!response.data) {
132
- this.error('API returned invalid response (missing data)', { exit: 1 });
133
- }
134
- const data = response.data;
135
- if (!data.workflows) {
136
- this.error('API returned invalid response (missing workflows)', { exit: 1 });
137
- }
138
- if (data.workflows.length === 0) {
138
+ this.log(flags.catalog ? `Fetching workflow catalog: ${flags.catalog}...` : 'Fetching workflow catalog...');
139
+ const catalogWorkflows = await fetchWorkflowCatalog(flags.catalog);
140
+ if (catalogWorkflows.length === 0) {
139
141
  this.log('No workflows found in catalog.');
140
142
  return;
141
143
  }
142
144
  const workflows = flags.filter ?
143
- data.workflows.filter(matchName(flags.filter)) :
144
- data.workflows;
145
+ catalogWorkflows.filter(matchName(flags.filter)) :
146
+ catalogWorkflows;
145
147
  if (workflows.length === 0 && flags.filter) {
146
148
  this.log(`No workflows matching filter: ${flags.filter}`);
147
149
  return;
@@ -1,8 +1,12 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
1
2
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
3
  const mockListScenarios = vi.fn().mockReturnValue([]);
3
4
  vi.mock('#utils/scenario_resolver.js', () => ({
4
5
  listScenariosForWorkflow: mockListScenarios
5
6
  }));
7
+ vi.mock('#api/workflow_catalog.js', () => ({
8
+ fetchWorkflowCatalog: vi.fn()
9
+ }));
6
10
  describe('workflow list command', () => {
7
11
  beforeEach(() => {
8
12
  vi.clearAllMocks();
@@ -15,6 +19,12 @@ describe('workflow list command', () => {
15
19
  expect(WorkflowList.flags).toHaveProperty('format');
16
20
  expect(WorkflowList.flags).toHaveProperty('detailed');
17
21
  expect(WorkflowList.flags).toHaveProperty('filter');
22
+ expect(WorkflowList.flags).toHaveProperty('catalog');
23
+ });
24
+ it('reads the catalog flag from OUTPUT_CATALOG_ID', async () => {
25
+ const WorkflowList = (await import('./list.js')).default;
26
+ expect(WorkflowList.flags.catalog.env).toBe('OUTPUT_CATALOG_ID');
27
+ expect(WorkflowList.flags.catalog.char).toBe('c');
18
28
  });
19
29
  it('should have correct flag configuration', async () => {
20
30
  const WorkflowList = (await import('./list.js')).default;
@@ -118,3 +128,50 @@ describe('workflow list parsing', () => {
118
128
  expect(parsed.inputs).toContain('user.email: string');
119
129
  });
120
130
  });
131
+ describe('formatWorkflowsAsList', () => {
132
+ it('appends aliases to the default list when present', async () => {
133
+ const { formatWorkflowsAsList } = await import('./list.js');
134
+ const output = formatWorkflowsAsList([
135
+ { name: 'galileoExtractKeyword', aliases: ['seoContentExtractKeywordWorkflow'] }
136
+ ]);
137
+ expect(output).toContain('- galileoExtractKeyword (aliases: seoContentExtractKeywordWorkflow)');
138
+ });
139
+ it('omits the aliases segment when a workflow has none', async () => {
140
+ const { formatWorkflowsAsList } = await import('./list.js');
141
+ const output = formatWorkflowsAsList([{ name: 'simple' }]);
142
+ expect(output).toContain('- simple');
143
+ expect(output).not.toContain('aliases:');
144
+ });
145
+ });
146
+ describe('run() catalog resolution', () => {
147
+ beforeEach(() => {
148
+ vi.clearAllMocks();
149
+ });
150
+ const catalogWorkflows = [{ name: 'simple' }];
151
+ const createCommand = async (flagOverrides) => {
152
+ const WorkflowList = (await import('./list.js')).default;
153
+ const cmd = new WorkflowList([], {});
154
+ cmd.log = vi.fn();
155
+ cmd.error = vi.fn(() => {
156
+ throw new Error('error called');
157
+ });
158
+ cmd.parse = vi.fn().mockResolvedValue({
159
+ flags: { format: 'list', detailed: false, filter: undefined, catalog: undefined, ...flagOverrides }
160
+ });
161
+ return cmd;
162
+ };
163
+ it('fetches a specific catalog by id when a catalog is provided', async () => {
164
+ const { fetchWorkflowCatalog } = await import('#api/workflow_catalog.js');
165
+ vi.mocked(fetchWorkflowCatalog).mockResolvedValue(catalogWorkflows);
166
+ const cmd = await createCommand({ catalog: 'my-catalog' });
167
+ await cmd.run();
168
+ expect(fetchWorkflowCatalog).toHaveBeenCalledWith('my-catalog');
169
+ });
170
+ it('falls back to the default catalog when no catalog is provided', async () => {
171
+ const { fetchWorkflowCatalog } = await import('#api/workflow_catalog.js');
172
+ vi.mocked(fetchWorkflowCatalog).mockResolvedValue(catalogWorkflows);
173
+ const cmd = await createCommand({ catalog: undefined });
174
+ await cmd.run();
175
+ expect(fetchWorkflowCatalog).toHaveBeenCalledWith(undefined);
176
+ });
177
+ });
@@ -1,3 +1,3 @@
1
1
  {
2
- "framework": "0.7.1-next.d67ad85.0"
2
+ "framework": "0.7.1-next.de30052.0"
3
3
  }
@@ -1,12 +1,8 @@
1
- import type { TraceNode, LLMCall, HTTPCall, TokenUsage, PricingConfig, ModelPricing, ServiceConfig, ServiceCostResult, CostReport } from '#types/cost.js';
1
+ import type { TraceNode, LLMCall, HTTPCall, PricingConfig, ServiceConfig, ServiceCostResult, CostReport } from '#types/cost.js';
2
2
  export declare function extractValue(obj: unknown, path: string): unknown;
3
3
  export declare function loadPricingConfig(configPath?: string): PricingConfig;
4
4
  export declare function findLLMCalls(node: TraceNode, parentStepName?: string | null, seenIds?: Set<string>): LLMCall[];
5
5
  export declare function findHTTPCalls(node: TraceNode, parentStepName?: string | null, seenIds?: Set<string>): HTTPCall[];
6
- export declare function calculateLLMCallCost(usage: TokenUsage, modelPricing: ModelPricing | undefined): {
7
- cost: number;
8
- warning?: string;
9
- };
10
6
  export declare function identifyService(httpCall: HTTPCall, services: Record<string, ServiceConfig>): {
11
7
  serviceName: string;
12
8
  config: ServiceConfig;