@outputai/cli 0.1.13-next.c93cdb4.0 → 0.1.13-next.cc1ead7.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 (52) hide show
  1. package/dist/api/generated/api.d.ts +322 -84
  2. package/dist/api/generated/api.js +67 -9
  3. package/dist/assets/docker/docker-compose-dev.yml +2 -2
  4. package/dist/commands/migrate.d.ts +11 -0
  5. package/dist/commands/migrate.js +40 -0
  6. package/dist/commands/workflow/generate.js +3 -1
  7. package/dist/commands/workflow/generate.spec.js +12 -0
  8. package/dist/commands/workflow/list.d.ts +1 -0
  9. package/dist/commands/workflow/list.js +12 -6
  10. package/dist/commands/workflow/list.spec.js +21 -0
  11. package/dist/commands/workflow/plan.js +1 -1
  12. package/dist/commands/workflow/run.js +2 -1
  13. package/dist/commands/workflow/run.spec.js +1 -0
  14. package/dist/commands/workflow/start.js +2 -1
  15. package/dist/commands/workflow/start.spec.js +1 -0
  16. package/dist/commands/workflow/test_eval.js +2 -2
  17. package/dist/components/command_footer.d.ts +8 -0
  18. package/dist/components/command_footer.js +4 -0
  19. package/dist/components/status_icon.d.ts +11 -0
  20. package/dist/components/status_icon.js +25 -0
  21. package/dist/components/workflow_summary.d.ts +10 -0
  22. package/dist/components/workflow_summary.js +4 -0
  23. package/dist/generated/framework_version.json +1 -1
  24. package/dist/services/claude_client.d.ts +14 -2
  25. package/dist/services/claude_client.integration.test.js +2 -2
  26. package/dist/services/claude_client.js +42 -6
  27. package/dist/services/claude_client.spec.js +3 -3
  28. package/dist/services/datasets.d.ts +1 -1
  29. package/dist/services/datasets.js +41 -37
  30. package/dist/services/datasets.test.d.ts +1 -0
  31. package/dist/services/datasets.test.js +202 -0
  32. package/dist/services/docker.d.ts +1 -3
  33. package/dist/services/docker.js +38 -13
  34. package/dist/services/messages.d.ts +1 -1
  35. package/dist/services/messages.js +2 -2
  36. package/dist/services/workflow_builder.d.ts +1 -1
  37. package/dist/services/workflow_builder.js +1 -1
  38. package/dist/templates/agent_instructions/CLAUDE.md.template +36 -2
  39. package/dist/templates/agent_instructions/dotclaude/settings.json.template +2 -2
  40. package/dist/utils/date_formatter.d.ts +11 -1
  41. package/dist/utils/date_formatter.js +26 -1
  42. package/dist/utils/format_workflow_result.d.ts +3 -3
  43. package/dist/utils/open_url.d.ts +1 -0
  44. package/dist/utils/open_url.js +12 -0
  45. package/dist/utils/workflow_dir_parser.d.ts +5 -0
  46. package/dist/utils/workflow_dir_parser.js +39 -0
  47. package/dist/utils/workflow_dir_parser.spec.d.ts +1 -0
  48. package/dist/utils/workflow_dir_parser.spec.js +74 -0
  49. package/dist/views/dev.js +62 -26
  50. package/dist/views/workflow/list.d.ts +6 -0
  51. package/dist/views/workflow/list.js +127 -0
  52. package/package.json +14 -14
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Generated by orval v8.6.2 🍺
2
+ * Generated by orval v8.8.0 🍺
3
3
  * Do not edit manually.
4
4
  * Output.ai API
5
5
  * API for managing and executing Output.ai workflows
@@ -24,11 +24,7 @@ export const WorkflowRunInfoStatus = {
24
24
  timed_out: 'timed_out',
25
25
  continued: 'continued',
26
26
  };
27
- export const PostWorkflowRun200Status = {
28
- completed: 'completed',
29
- failed: 'failed',
30
- };
31
- export const GetWorkflowIdStatus200Status = {
27
+ export const WorkflowStatusResponseStatus = {
32
28
  canceled: 'canceled',
33
29
  completed: 'completed',
34
30
  continued_as_new: 'continued_as_new',
@@ -38,7 +34,7 @@ export const GetWorkflowIdStatus200Status = {
38
34
  timed_out: 'timed_out',
39
35
  unspecified: 'unspecified',
40
36
  };
41
- export const GetWorkflowIdResult200Status = {
37
+ export const WorkflowResultResponseStatus = {
42
38
  completed: 'completed',
43
39
  failed: 'failed',
44
40
  canceled: 'canceled',
@@ -46,6 +42,10 @@ export const GetWorkflowIdResult200Status = {
46
42
  timed_out: 'timed_out',
47
43
  continued: 'continued',
48
44
  };
45
+ export const PostWorkflowRun200Status = {
46
+ completed: 'completed',
47
+ failed: 'failed',
48
+ };
49
49
  ;
50
50
  export const getGetHealthUrl = () => {
51
51
  return `/health`;
@@ -87,6 +87,24 @@ export const getWorkflowIdStatus = async (id, options) => {
87
87
  method: 'GET'
88
88
  });
89
89
  };
90
+ export const getGetWorkflowIdRunsRidStatusUrl = (id, rid) => {
91
+ return `/workflow/${id}/runs/${rid}/status`;
92
+ };
93
+ export const getWorkflowIdRunsRidStatus = async (id, rid, options) => {
94
+ return customFetchInstance(getGetWorkflowIdRunsRidStatusUrl(id, rid), {
95
+ ...options,
96
+ method: 'GET'
97
+ });
98
+ };
99
+ export const getPatchWorkflowIdRunsRidStopUrl = (id, rid) => {
100
+ return `/workflow/${id}/runs/${rid}/stop`;
101
+ };
102
+ export const patchWorkflowIdRunsRidStop = async (id, rid, options) => {
103
+ return customFetchInstance(getPatchWorkflowIdRunsRidStopUrl(id, rid), {
104
+ ...options,
105
+ method: 'PATCH'
106
+ });
107
+ };
90
108
  export const getPatchWorkflowIdStopUrl = (id) => {
91
109
  return `/workflow/${id}/stop`;
92
110
  };
@@ -96,6 +114,17 @@ export const patchWorkflowIdStop = async (id, options) => {
96
114
  method: 'PATCH'
97
115
  });
98
116
  };
117
+ export const getPostWorkflowIdRunsRidTerminateUrl = (id, rid) => {
118
+ return `/workflow/${id}/runs/${rid}/terminate`;
119
+ };
120
+ export const postWorkflowIdRunsRidTerminate = async (id, rid, postWorkflowIdRunsRidTerminateBody, options) => {
121
+ return customFetchInstance(getPostWorkflowIdRunsRidTerminateUrl(id, rid), {
122
+ ...options,
123
+ method: 'POST',
124
+ headers: { 'Content-Type': 'application/json', ...options?.headers },
125
+ body: JSON.stringify(postWorkflowIdRunsRidTerminateBody)
126
+ });
127
+ };
99
128
  export const getPostWorkflowIdTerminateUrl = (id) => {
100
129
  return `/workflow/${id}/terminate`;
101
130
  };
@@ -107,15 +136,26 @@ export const postWorkflowIdTerminate = async (id, postWorkflowIdTerminateBody, o
107
136
  body: JSON.stringify(postWorkflowIdTerminateBody)
108
137
  });
109
138
  };
139
+ export const getPostWorkflowIdRunsRidResetUrl = (id, rid) => {
140
+ return `/workflow/${id}/runs/${rid}/reset`;
141
+ };
142
+ export const postWorkflowIdRunsRidReset = async (id, rid, resetWorkflowRequest, options) => {
143
+ return customFetchInstance(getPostWorkflowIdRunsRidResetUrl(id, rid), {
144
+ ...options,
145
+ method: 'POST',
146
+ headers: { 'Content-Type': 'application/json', ...options?.headers },
147
+ body: JSON.stringify(resetWorkflowRequest)
148
+ });
149
+ };
110
150
  export const getPostWorkflowIdResetUrl = (id) => {
111
151
  return `/workflow/${id}/reset`;
112
152
  };
113
- export const postWorkflowIdReset = async (id, postWorkflowIdResetBody, options) => {
153
+ export const postWorkflowIdReset = async (id, resetWorkflowRequest, options) => {
114
154
  return customFetchInstance(getPostWorkflowIdResetUrl(id), {
115
155
  ...options,
116
156
  method: 'POST',
117
157
  headers: { 'Content-Type': 'application/json', ...options?.headers },
118
- body: JSON.stringify(postWorkflowIdResetBody)
158
+ body: JSON.stringify(resetWorkflowRequest)
119
159
  });
120
160
  };
121
161
  export const getGetWorkflowIdResultUrl = (id) => {
@@ -127,6 +167,15 @@ export const getWorkflowIdResult = async (id, options) => {
127
167
  method: 'GET'
128
168
  });
129
169
  };
170
+ export const getGetWorkflowIdRunsRidResultUrl = (id, rid) => {
171
+ return `/workflow/${id}/runs/${rid}/result`;
172
+ };
173
+ export const getWorkflowIdRunsRidResult = async (id, rid, options) => {
174
+ return customFetchInstance(getGetWorkflowIdRunsRidResultUrl(id, rid), {
175
+ ...options,
176
+ method: 'GET'
177
+ });
178
+ };
130
179
  export const getGetWorkflowIdTraceLogUrl = (id) => {
131
180
  return `/workflow/${id}/trace-log`;
132
181
  };
@@ -136,6 +185,15 @@ export const getWorkflowIdTraceLog = async (id, options) => {
136
185
  method: 'GET'
137
186
  });
138
187
  };
188
+ export const getGetWorkflowIdRunsRidTraceLogUrl = (id, rid) => {
189
+ return `/workflow/${id}/runs/${rid}/trace-log`;
190
+ };
191
+ export const getWorkflowIdRunsRidTraceLog = async (id, rid, options) => {
192
+ return customFetchInstance(getGetWorkflowIdRunsRidTraceLogUrl(id, rid), {
193
+ ...options,
194
+ method: 'GET'
195
+ });
196
+ };
139
197
  export const getGetWorkflowCatalogIdUrl = (id) => {
140
198
  return `/workflow/catalog/${id}`;
141
199
  };
@@ -81,7 +81,7 @@ services:
81
81
  condition: service_healthy
82
82
  worker:
83
83
  condition: service_healthy
84
- image: outputai/api:${OUTPUT_API_VERSION:-0.1.13-next.c93cdb4.0}
84
+ image: outputai/api:${OUTPUT_API_VERSION:-0.1.13-next.cc1ead7.0}
85
85
  init: true
86
86
  networks:
87
87
  - main
@@ -103,7 +103,7 @@ services:
103
103
  depends_on:
104
104
  temporal:
105
105
  condition: service_healthy
106
- image: node:24.13.0-slim
106
+ image: node:24.15.0-slim
107
107
  healthcheck:
108
108
  test: [ 'CMD-SHELL', 'npx output-healthcheck' ]
109
109
  interval: 3s
@@ -0,0 +1,11 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Migrate extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ from: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ to: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ notes: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ };
10
+ run(): Promise<void>;
11
+ }
@@ -0,0 +1,40 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { ensureOutputAISystem } from '#services/coding_agents.js';
3
+ import { invokeMigrate } from '#services/claude_client.js';
4
+ export default class Migrate extends Command {
5
+ static description = 'Upgrade a project between versions of the Output framework. ' +
6
+ 'Fetches the matching migration guide from docs.output.ai and applies it.';
7
+ static examples = [
8
+ '<%= config.bin %> <%= command.id %>',
9
+ '<%= config.bin %> <%= command.id %> --to 0.2.0',
10
+ '<%= config.bin %> <%= command.id %> --from 0.1.12 --to 0.2.0',
11
+ '<%= config.bin %> <%= command.id %> --to 0.2.0 --notes "skip the http changes, we don\'t use that package"'
12
+ ];
13
+ static flags = {
14
+ from: Flags.string({
15
+ description: 'Version to migrate from. Defaults to the framework version in your package.json.',
16
+ required: false
17
+ }),
18
+ to: Flags.string({
19
+ description: 'Version to migrate to. Defaults to the latest published version.',
20
+ required: false
21
+ }),
22
+ notes: Flags.string({
23
+ char: 'n',
24
+ description: 'Extra guidance passed through to the migration agent.',
25
+ required: false
26
+ })
27
+ };
28
+ async run() {
29
+ const { flags } = await this.parse(Migrate);
30
+ const projectRoot = process.cwd();
31
+ this.log('Checking .outputai directory structure...');
32
+ await ensureOutputAISystem(projectRoot);
33
+ this.log('\nInvoking the /output-migrate skill...');
34
+ this.log('This may take a moment...\n');
35
+ const summary = await invokeMigrate(flags.from ?? '', flags.to ?? '', flags.notes);
36
+ this.log('\n=========');
37
+ this.log(summary);
38
+ this.log('=========\n');
39
+ }
40
+ }
@@ -4,6 +4,7 @@ import { buildWorkflow, buildWorkflowInteractiveLoop } from '#services/workflow_
4
4
  import { ensureOutputAISystem } from '#services/coding_agents.js';
5
5
  import { getWorkflowGenerateSuccessMessage } from '#services/messages.js';
6
6
  import { DEFAULT_OUTPUT_DIRS } from '#utils/paths.js';
7
+ import { parseWorkflowDir } from '#utils/workflow_dir_parser.js';
7
8
  import path from 'node:path';
8
9
  import * as fsSync from 'node:fs';
9
10
  import { getErrorMessage } from '#utils/error_utils.js';
@@ -83,7 +84,8 @@ export default class Generate extends Command {
83
84
  this.displaySuccess(result);
84
85
  }
85
86
  displaySuccess(result) {
86
- const message = getWorkflowGenerateSuccessMessage(result.workflowName, result.targetDir, result.filesCreated);
87
+ const dirInfo = parseWorkflowDir(result.targetDir);
88
+ const message = getWorkflowGenerateSuccessMessage(result.workflowName, dirInfo.workflowId ?? result.workflowName, dirInfo.scenarioNames[0], result.targetDir, result.filesCreated);
87
89
  this.log(message);
88
90
  }
89
91
  }
@@ -2,10 +2,13 @@
2
2
  import { describe, it, expect, beforeEach, vi } from 'vitest';
3
3
  import Generate from './generate.js';
4
4
  import { generateWorkflow } from '#services/workflow_generator.js';
5
+ import { parseWorkflowDir } from '#utils/workflow_dir_parser.js';
5
6
  import { InvalidNameError, WorkflowExistsError } from '#types/errors.js';
6
7
  vi.mock('../../services/workflow_generator.js');
8
+ vi.mock('../../utils/workflow_dir_parser.js');
7
9
  describe('Generate Command', () => {
8
10
  let mockGenerateWorkflow;
11
+ let mockParseWorkflowDir;
9
12
  let logSpy;
10
13
  const createCommand = () => {
11
14
  const cmd = new Generate([], {});
@@ -20,6 +23,7 @@ describe('Generate Command', () => {
20
23
  beforeEach(() => {
21
24
  vi.clearAllMocks();
22
25
  mockGenerateWorkflow = vi.mocked(generateWorkflow);
26
+ mockParseWorkflowDir = vi.mocked(parseWorkflowDir);
23
27
  });
24
28
  describe('successful workflow generation', () => {
25
29
  it('should generate workflow with skeleton flag', async () => {
@@ -38,6 +42,10 @@ describe('Generate Command', () => {
38
42
  targetDir: '/tmp/test-workflow',
39
43
  filesCreated: ['index.ts', 'steps.ts', 'types.ts']
40
44
  });
45
+ mockParseWorkflowDir.mockReturnValue({
46
+ workflowId: 'testWorkflow',
47
+ scenarioNames: ['test_input']
48
+ });
41
49
  await cmd.run();
42
50
  expect(mockGenerateWorkflow).toHaveBeenCalledWith({
43
51
  name: 'test-workflow',
@@ -105,6 +113,10 @@ describe('Generate Command', () => {
105
113
  targetDir: '/custom/path/my-workflow',
106
114
  filesCreated: ['index.ts', 'steps.ts', 'types.ts']
107
115
  });
116
+ mockParseWorkflowDir.mockReturnValue({
117
+ workflowId: 'myWorkflow',
118
+ scenarioNames: ['test_input']
119
+ });
108
120
  await cmd.run();
109
121
  expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('SUCCESS!'));
110
122
  expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('my-workflow'));
@@ -6,6 +6,7 @@ interface WorkflowDisplay {
6
6
  inputs: string;
7
7
  outputs: string;
8
8
  scenarios: string;
9
+ aliases: string;
9
10
  }
10
11
  export declare function parseWorkflowForDisplay(workflow: Workflow): WorkflowDisplay;
11
12
  export default class WorkflowList extends Command {
@@ -17,7 +17,8 @@ export function parseWorkflowForDisplay(workflow) {
17
17
  description: parsed.description || 'No description',
18
18
  inputs: formatParameters(parsed.inputs),
19
19
  outputs: formatParameters(parsed.outputs),
20
- scenarios: scenarioNames.length > 0 ? scenarioNames.join(', ') : 'none'
20
+ scenarios: scenarioNames.length > 0 ? scenarioNames.join(', ') : 'none',
21
+ aliases: workflow.aliases?.length ? workflow.aliases.join(', ') : 'none'
21
22
  };
22
23
  }
23
24
  function caseInsensitiveIncludes(str, filter) {
@@ -26,7 +27,10 @@ function caseInsensitiveIncludes(str, filter) {
26
27
  function matchName(filterString) {
27
28
  return workflow => {
28
29
  const name = workflow.name || '';
29
- return caseInsensitiveIncludes(name, filterString);
30
+ if (caseInsensitiveIncludes(name, filterString)) {
31
+ return true;
32
+ }
33
+ return (workflow.aliases ?? []).some(alias => caseInsensitiveIncludes(alias, filterString));
30
34
  };
31
35
  }
32
36
  function sortWorkflowsByName(workflows) {
@@ -38,8 +42,8 @@ function sortWorkflowsByName(workflows) {
38
42
  }
39
43
  function createWorkflowTable(workflows, detailed) {
40
44
  const table = new Table({
41
- head: ['Name', 'Description', 'Inputs', 'Outputs', 'Scenarios'],
42
- colWidths: detailed ? [30, 42, 42, 42, 60] : [24, 30, 24, 24, 48],
45
+ head: ['Name', 'Description', 'Aliases', 'Inputs', 'Outputs', 'Scenarios'],
46
+ colWidths: detailed ? [28, 36, 36, 36, 36, 48] : [22, 26, 26, 22, 22, 36],
43
47
  wordWrap: true,
44
48
  style: {
45
49
  head: ['cyan']
@@ -49,13 +53,14 @@ function createWorkflowTable(workflows, detailed) {
49
53
  sortedWorkflows.forEach(workflow => {
50
54
  const display = parseWorkflowForDisplay(workflow);
51
55
  if (detailed) {
56
+ const aliases = workflow.aliases?.length ? workflow.aliases.join('\n') : 'none';
52
57
  const inputs = display.inputs.split(', ').join('\n');
53
58
  const outputs = display.outputs.split(', ').join('\n');
54
59
  const scenarios = display.scenarios.split(', ').join('\n');
55
- table.push([display.name, display.description, inputs, outputs, scenarios]);
60
+ table.push([display.name, display.description, aliases, inputs, outputs, scenarios]);
56
61
  }
57
62
  else {
58
- table.push([display.name, display.description, display.inputs, display.outputs, display.scenarios]);
63
+ table.push([display.name, display.description, display.aliases, display.inputs, display.outputs, display.scenarios]);
59
64
  }
60
65
  });
61
66
  return table.toString();
@@ -72,6 +77,7 @@ function formatWorkflowsAsJson(workflows) {
72
77
  return {
73
78
  name: display.name,
74
79
  description: display.description,
80
+ aliases: w.aliases ?? [],
75
81
  inputs: display.inputs.split(', '),
76
82
  outputs: display.outputs.split(', '),
77
83
  scenarios: display.scenarios === 'none' ? [] : display.scenarios.split(', '),
@@ -64,6 +64,27 @@ describe('workflow list parsing', () => {
64
64
  expect(parsed.inputs).toBe('none');
65
65
  expect(parsed.outputs).toBe('none');
66
66
  expect(parsed.scenarios).toBe('none');
67
+ expect(parsed.aliases).toBe('none');
68
+ });
69
+ it('should include aliases when present', async () => {
70
+ const { parseWorkflowForDisplay } = await import('./list.js');
71
+ const mockWorkflow = {
72
+ name: 'aliased-workflow',
73
+ description: 'Has aliases',
74
+ aliases: ['old_name', 'legacy_name']
75
+ };
76
+ const parsed = parseWorkflowForDisplay(mockWorkflow);
77
+ expect(parsed.aliases).toBe('old_name, legacy_name');
78
+ });
79
+ it('should show none when aliases array is empty', async () => {
80
+ const { parseWorkflowForDisplay } = await import('./list.js');
81
+ const mockWorkflow = {
82
+ name: 'no-aliases',
83
+ description: 'Empty aliases',
84
+ aliases: []
85
+ };
86
+ const parsed = parseWorkflowForDisplay(mockWorkflow);
87
+ expect(parsed.aliases).toBe('none');
67
88
  });
68
89
  it('should include scenario names when scenarios exist', async () => {
69
90
  mockListScenarios.mockReturnValueOnce(['basic', 'advanced', 'stress_test']);
@@ -56,7 +56,7 @@ export default class WorkflowPlan extends Command {
56
56
  return this.planModificationLoop(modifiedPlanContent);
57
57
  }
58
58
  async planGenerationLoop(promptDescription, planName, projectRoot) {
59
- this.log('\nInvoking the /outputai:plan_workflow command...');
59
+ this.log('\nInvoking the /output-plan-workflow command...');
60
60
  this.log('This may take a moment...\n');
61
61
  const planContent = await invokePlanWorkflow(promptDescription);
62
62
  const modifiedPlanContent = await this.planModificationLoop(planContent);
@@ -55,7 +55,8 @@ export default class WorkflowRun extends Command {
55
55
  }),
56
56
  'task-queue': Flags.string({
57
57
  char: 'q',
58
- description: 'Task queue name for workflow execution'
58
+ description: 'Task queue name for workflow execution (defaults to OUTPUT_CATALOG_ID)',
59
+ env: 'OUTPUT_CATALOG_ID'
59
60
  }),
60
61
  format: Flags.string({
61
62
  char: 'f',
@@ -13,6 +13,7 @@ vi.mock('#utils/sleep.js', () => ({
13
13
  describe('workflow run command', () => {
14
14
  beforeEach(async () => {
15
15
  vi.clearAllMocks();
16
+ delete process.env.OUTPUT_CATALOG_ID;
16
17
  const { resolveInput } = await import('#utils/resolve_input.js');
17
18
  const { sleep } = await import('#utils/sleep.js');
18
19
  vi.mocked(resolveInput).mockResolvedValue({});
@@ -28,7 +28,8 @@ export default class WorkflowStart extends Command {
28
28
  }),
29
29
  'task-queue': Flags.string({
30
30
  char: 'q',
31
- description: 'Task queue name for workflow execution'
31
+ description: 'Task queue name for workflow execution (defaults to OUTPUT_CATALOG_ID)',
32
+ env: 'OUTPUT_CATALOG_ID'
32
33
  })
33
34
  };
34
35
  async run() {
@@ -5,6 +5,7 @@ vi.mock('../../api/generated/api.js', () => ({
5
5
  describe('workflow start command', () => {
6
6
  beforeEach(() => {
7
7
  vi.clearAllMocks();
8
+ delete process.env.OUTPUT_CATALOG_ID;
8
9
  });
9
10
  describe('command definition', () => {
10
11
  it('should export a valid OCLIF command', async () => {
@@ -114,7 +114,7 @@ export default class WorkflowTest extends Command {
114
114
  }
115
115
  };
116
116
  if (save) {
117
- const filePath = join(dir, `${dataset.name}.yml`);
117
+ const filePath = dataset._source ?? join(dir, `${dataset.name}.yml`);
118
118
  await writeDataset(updated, filePath);
119
119
  this.log(` Saved output to ${filePath}`);
120
120
  }
@@ -137,7 +137,7 @@ export default class WorkflowTest extends Command {
137
137
  date: now
138
138
  }
139
139
  };
140
- const filePath = join(dir, `${dataset.name}.yml`);
140
+ const filePath = dataset._source ?? join(dir, `${dataset.name}.yml`);
141
141
  await writeDataset(updated, filePath);
142
142
  this.log(` Saved eval result to ${filePath}`);
143
143
  }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export interface CommandHint {
3
+ key: string;
4
+ label: string;
5
+ }
6
+ export declare const CommandFooter: React.FC<{
7
+ hints: CommandHint[];
8
+ }>;
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { Box, Text } from 'ink';
4
+ export const CommandFooter = ({ hints }) => (_jsx(Box, { marginTop: 1, children: hints.map((hint, i) => (_jsxs(React.Fragment, { children: [i > 0 && _jsx(Text, { dimColor: true, children: ' | ' }), _jsx(Text, { dimColor: true, children: '(' }), _jsx(Text, { dimColor: true, bold: true, children: hint.key }), _jsx(Text, { dimColor: true, children: ')' }), _jsx(Text, { dimColor: true, children: ` ${hint.label}` })] }, hint.key))) }));
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ interface StatusDisplay {
3
+ icon: string;
4
+ color: string;
5
+ }
6
+ export declare const resolveStatus: (status: string) => StatusDisplay;
7
+ export declare const statusColor: (status: string) => string;
8
+ export declare const StatusIcon: React.FC<{
9
+ status: string;
10
+ }>;
11
+ export {};
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Text } from 'ink';
3
+ const STATUS_MAP = {
4
+ // Docker service health
5
+ healthy: { icon: '●', color: 'green' },
6
+ unhealthy: { icon: '○', color: 'red' },
7
+ starting: { icon: '◐', color: 'yellow' },
8
+ none: { icon: '●', color: 'blue' },
9
+ exited: { icon: '✗', color: 'red' },
10
+ // Workflow run status
11
+ running: { icon: '●', color: 'blue' },
12
+ completed: { icon: '●', color: 'green' },
13
+ failed: { icon: '✗', color: 'red' },
14
+ canceled: { icon: '○', color: 'gray' },
15
+ terminated: { icon: '✗', color: 'red' },
16
+ timed_out: { icon: '✗', color: 'red' },
17
+ continued: { icon: '↻', color: 'blue' }
18
+ };
19
+ const DEFAULT_DISPLAY = { icon: '?', color: 'white' };
20
+ export const resolveStatus = (status) => STATUS_MAP[status] ?? DEFAULT_DISPLAY;
21
+ export const statusColor = (status) => resolveStatus(status).color;
22
+ export const StatusIcon = ({ status }) => {
23
+ const { icon, color } = resolveStatus(status);
24
+ return _jsx(Text, { color: color, children: icon });
25
+ };
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ export interface WorkflowSummary {
3
+ running: number;
4
+ completed: number;
5
+ failed: number;
6
+ total: number;
7
+ }
8
+ export declare const WorkflowSummarySection: React.FC<{
9
+ summary: WorkflowSummary;
10
+ }>;
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { statusColor } from '#components/status_icon.js';
4
+ export const WorkflowSummarySection = ({ summary }) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, children: "\uD83D\uDCCB Workflows" }), _jsxs(Box, { marginTop: 1, children: [_jsxs(Text, { color: statusColor('running'), children: [summary.running, " running"] }), _jsx(Text, { children: ", " }), _jsxs(Text, { color: statusColor('failed'), children: [summary.failed, " failed"] }), _jsx(Text, { children: ", " }), _jsxs(Text, { color: statusColor('completed'), children: [summary.completed, " complete"] })] })] }));
@@ -1,3 +1,3 @@
1
1
  {
2
- "framework": "0.1.13-next.c93cdb4.0"
2
+ "framework": "0.1.13-next.cc1ead7.0"
3
3
  }
@@ -5,6 +5,7 @@ import { Options } from '@anthropic-ai/claude-agent-sdk';
5
5
  export declare const ADDITIONAL_INSTRUCTIONS: {
6
6
  readonly PLAN: "\n! IMPORTANT !\n1. Use TodoWrite to track your progress through plan creation.\n\n2. Please respond with only the final version of the plan content.\n\n3. Respond in a markdown format with these metadata headers:\n\n---\ntitle: <plan-title>\ndescription: <plan-description>\ndate: <plan-date>\n---\n\n<plan-content>\n\n4. After you mark all todos as complete, you must respond with the final version of the plan.\n\n5. DO NOT write the plan to disk — the CLI will handle saving the file to the plans directory.\n\n6. DO NOT suggest any next steps, follow-up commands, or instructions for the user — the CLI will inform the user of next steps after saving.\n";
7
7
  readonly BUILD: "\n! IMPORTANT !\n1. Use TodoWrite to track your progress through workflow implementation.\n\n2. Follow the implementation plan exactly as specified in the plan file.\n\n3. Implement all workflow files following Output.ai patterns and best practices.\n\n4. After you mark all todos as complete, provide a summary of what was implemented.\n";
8
+ readonly MIGRATE: "\n! IMPORTANT !\n1. Use TodoWrite to track your progress through the migration.\n\n2. Fetch the migration guide from https://docs.output.ai/migrations — do not invent migration steps from memory.\n\n3. If the specific guide URL 404s, fall back to the /migrations index and chain the guides that cover the version range.\n\n4. Confirm the planned changes with the user before editing files.\n\n5. After you mark all todos as complete, provide a summary of which files changed and whether the type check passed.\n";
8
9
  };
9
10
  export declare const PLAN_COMMAND_OPTIONS: Options;
10
11
  interface ReplyToClaudeOptions {
@@ -12,13 +13,14 @@ interface ReplyToClaudeOptions {
12
13
  applyAdditionalInstructions?: string;
13
14
  }
14
15
  export declare const BUILD_COMMAND_OPTIONS: Options;
16
+ export declare const MIGRATE_COMMAND_OPTIONS: Options;
15
17
  export declare class ClaudeInvocationError extends Error {
16
18
  cause?: Error | undefined;
17
19
  constructor(message: string, cause?: Error | undefined);
18
20
  }
19
21
  export declare function replyToClaude(message: string, { anthropicOpts, applyAdditionalInstructions }?: ReplyToClaudeOptions): Promise<string>;
20
22
  /**
21
- * Invoke claude-code with /outputai:plan_workflow slash command
23
+ * Invoke claude-code with /output-plan-workflow slash command
22
24
  * The SDK loads custom commands from .claude/commands/ when settingSources includes 'project'.
23
25
  * ensureOutputAISystem() scaffolds the command files to that location.
24
26
  * @param description - Workflow description
@@ -26,7 +28,7 @@ export declare function replyToClaude(message: string, { anthropicOpts, applyAdd
26
28
  */
27
29
  export declare function invokePlanWorkflow(description: string): Promise<string>;
28
30
  /**
29
- * Invoke claude-code with /outputai:build_workflow slash command
31
+ * Invoke claude-code with /output-build-workflow slash command
30
32
  * The SDK loads custom commands from .claude/commands/ when settingSources includes 'project'.
31
33
  * ensureOutputAISystem() scaffolds the command files to that location.
32
34
  * @param planFilePath - Absolute path to the plan file
@@ -36,4 +38,14 @@ export declare function invokePlanWorkflow(description: string): Promise<string>
36
38
  * @returns Implementation output from claude-code
37
39
  */
38
40
  export declare function invokeBuildWorkflow(planFilePath: string, workflowDir: string, workflowName: string, additionalInstructions?: string): Promise<string>;
41
+ /**
42
+ * Invoke claude-code with /output-migrate slash command (registered via the output-migrate skill).
43
+ * The slash command fetches migration instructions from docs.output.ai —
44
+ * this CLI wrapper just passes through the version arguments.
45
+ * @param fromVersion - Current framework version (empty string = auto-detect)
46
+ * @param toVersion - Target version (empty string = use npm "latest")
47
+ * @param additionalInstructions - Optional user-supplied guidance
48
+ * @returns Migration summary from claude-code
49
+ */
50
+ export declare function invokeMigrate(fromVersion: string, toVersion: string, additionalInstructions?: string): Promise<string>;
39
51
  export {};
@@ -15,7 +15,7 @@ describe('invokePlanWorkflow - Integration Tests', () => {
15
15
  const messages = [];
16
16
  try {
17
17
  for await (const message of query({
18
- prompt: `/outputai:plan_workflow ${description}`,
18
+ prompt: `/output-plan-workflow ${description}`,
19
19
  options: { maxTurns: 1 }
20
20
  })) {
21
21
  console.log('\nReceived message:', JSON.stringify(message, null, 2));
@@ -31,7 +31,7 @@ describe('invokePlanWorkflow - Integration Tests', () => {
31
31
  // This test is just for debugging - we expect messages
32
32
  expect(messages.length).toBeGreaterThan(0);
33
33
  }, 60000); // 60 second timeout
34
- it('should successfully invoke /outputai:plan_workflow slash command and return content', async () => {
34
+ it('should successfully invoke /output-plan-workflow slash command and return content', async () => {
35
35
  const description = 'Simple workflow that takes a number and doubles it';
36
36
  const result = await invokePlanWorkflow(description);
37
37
  console.log('\n===== PLAN RESULT =====');