@outputai/cli 0.1.12 → 0.1.13-dev.01b8dea.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 (66) hide show
  1. package/bin/run.js +2 -0
  2. package/dist/api/generated/api.d.ts +4 -0
  3. package/dist/assets/docker/docker-compose-dev.yml +1 -1
  4. package/dist/commands/fix.js +1 -1
  5. package/dist/commands/fix.spec.js +2 -2
  6. package/dist/commands/update.js +1 -1
  7. package/dist/commands/update.spec.js +2 -2
  8. package/dist/commands/workflow/generate.js +3 -1
  9. package/dist/commands/workflow/generate.spec.js +12 -0
  10. package/dist/commands/workflow/list.d.ts +1 -0
  11. package/dist/commands/workflow/list.js +12 -6
  12. package/dist/commands/workflow/list.spec.js +21 -0
  13. package/dist/commands/workflow/plan.js +5 -1
  14. package/dist/commands/workflow/plan.spec.js +3 -2
  15. package/dist/commands/workflow/run.js +2 -1
  16. package/dist/commands/workflow/run.spec.js +1 -0
  17. package/dist/commands/workflow/start.js +2 -1
  18. package/dist/commands/workflow/start.spec.js +1 -0
  19. package/dist/components/command_footer.d.ts +8 -0
  20. package/dist/components/command_footer.js +4 -0
  21. package/dist/components/status_icon.d.ts +11 -0
  22. package/dist/components/status_icon.js +25 -0
  23. package/dist/components/workflow_summary.d.ts +10 -0
  24. package/dist/components/workflow_summary.js +4 -0
  25. package/dist/generated/framework_version.json +1 -1
  26. package/dist/hooks/init.js +4 -0
  27. package/dist/services/claude_client.js +4 -1
  28. package/dist/services/coding_agents.js +1 -1
  29. package/dist/services/coding_agents.spec.js +6 -6
  30. package/dist/services/credentials_configurator.js +1 -1
  31. package/dist/services/docker.d.ts +1 -3
  32. package/dist/services/docker.js +38 -13
  33. package/dist/services/env_configurator.js +1 -1
  34. package/dist/services/env_configurator.spec.js +12 -12
  35. package/dist/services/messages.d.ts +1 -1
  36. package/dist/services/messages.js +2 -2
  37. package/dist/services/project_scaffold.js +2 -2
  38. package/dist/services/project_scaffold.spec.js +6 -6
  39. package/dist/services/workflow_builder.js +5 -1
  40. package/dist/services/workflow_builder.spec.js +3 -2
  41. package/dist/templates/agent_instructions/CLAUDE.md.template +36 -2
  42. package/dist/templates/agent_instructions/dotclaude/settings.json.template +2 -2
  43. package/dist/utils/date_formatter.d.ts +11 -1
  44. package/dist/utils/date_formatter.js +26 -1
  45. package/dist/utils/interactive.d.ts +2 -0
  46. package/dist/utils/interactive.js +5 -0
  47. package/dist/utils/interactive.spec.d.ts +1 -0
  48. package/dist/utils/interactive.spec.js +40 -0
  49. package/dist/utils/open_url.d.ts +1 -0
  50. package/dist/utils/open_url.js +12 -0
  51. package/dist/utils/prompt.d.ts +17 -0
  52. package/dist/utils/prompt.js +20 -0
  53. package/dist/utils/prompt.spec.d.ts +1 -0
  54. package/dist/utils/prompt.spec.js +74 -0
  55. package/dist/utils/proxy.d.ts +1 -0
  56. package/dist/utils/proxy.js +9 -0
  57. package/dist/utils/proxy.spec.d.ts +1 -0
  58. package/dist/utils/proxy.spec.js +39 -0
  59. package/dist/utils/workflow_dir_parser.d.ts +5 -0
  60. package/dist/utils/workflow_dir_parser.js +39 -0
  61. package/dist/utils/workflow_dir_parser.spec.d.ts +1 -0
  62. package/dist/utils/workflow_dir_parser.spec.js +74 -0
  63. package/dist/views/dev.js +62 -26
  64. package/dist/views/workflow/list.d.ts +6 -0
  65. package/dist/views/workflow/list.js +127 -0
  66. package/package.json +12 -11
package/bin/run.js CHANGED
@@ -2,10 +2,12 @@
2
2
 
3
3
  import { execute } from '@oclif/core';
4
4
  import { loadEnvironment } from '../dist/utils/env_loader.js';
5
+ import { bootstrapProxy } from '../dist/utils/proxy.js';
5
6
  import { resolveCredentialRefs } from '@outputai/credentials';
6
7
 
7
8
  // Load environment variables from .env files before executing CLI
8
9
  loadEnvironment();
10
+ bootstrapProxy();
9
11
  resolveCredentialRefs();
10
12
 
11
13
  await execute( { dir: import.meta.url } );
@@ -63,6 +63,8 @@ export interface Workflow {
63
63
  path?: string;
64
64
  inputSchema?: JSONSchema;
65
65
  outputSchema?: JSONSchema;
66
+ /** Alternative names that resolve to this workflow */
67
+ aliases?: string[];
66
68
  }
67
69
  /**
68
70
  * File destinations for trace data
@@ -304,6 +306,8 @@ export declare const GetWorkflowIdResult200Status: {
304
306
  export type GetWorkflowIdResult200 = {
305
307
  /** The workflow execution id */
306
308
  workflowId?: string;
309
+ /** The original input passed to the workflow, null if unavailable */
310
+ input?: unknown;
307
311
  /** The result of workflow, null if workflow failed */
308
312
  output?: unknown;
309
313
  trace?: TraceInfo;
@@ -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.12}
84
+ image: outputai/api:${OUTPUT_API_VERSION:-0.1.13-dev.01b8dea.0}
85
85
  init: true
86
86
  networks:
87
87
  - main
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- import { confirm } from '@inquirer/prompts';
2
+ import { confirm } from '#utils/prompt.js';
3
3
  import { applyFix, planFix } from '#services/fix_package.js';
4
4
  import { getErrorMessage } from '#utils/error_utils.js';
5
5
  const Ansi = {
@@ -2,12 +2,12 @@
2
2
  import { describe, it, expect, beforeEach, vi } from 'vitest';
3
3
  import Fix from './fix.js';
4
4
  import * as fixService from '#services/fix_package.js';
5
- import { confirm } from '@inquirer/prompts';
5
+ import { confirm } from '#utils/prompt.js';
6
6
  vi.mock('#services/fix_package.js', () => ({
7
7
  planFix: vi.fn(),
8
8
  applyFix: vi.fn()
9
9
  }));
10
- vi.mock('@inquirer/prompts', () => ({
10
+ vi.mock('#utils/prompt.js', () => ({
11
11
  confirm: vi.fn()
12
12
  }));
13
13
  const basePlan = () => ({
@@ -1,5 +1,5 @@
1
1
  import { Command, Flags } from '@oclif/core';
2
- import { confirm } from '@inquirer/prompts';
2
+ import { confirm } from '#utils/prompt.js';
3
3
  import { fetchLatestVersion, getGlobalInstalledVersion, getLocalInstalledVersion, updateGlobal, updateLocal, isOutdated } from '#services/npm_update_service.js';
4
4
  import { ensureClaudePlugin } from '#services/coding_agents.js';
5
5
  import { getErrorMessage } from '#utils/error_utils.js';
@@ -3,7 +3,7 @@ import { describe, it, expect, beforeEach, vi } from 'vitest';
3
3
  import Update from './update.js';
4
4
  import { fetchLatestVersion, getGlobalInstalledVersion, getLocalInstalledVersion, updateGlobal, updateLocal, isOutdated } from '#services/npm_update_service.js';
5
5
  import { ensureClaudePlugin } from '#services/coding_agents.js';
6
- import { confirm } from '@inquirer/prompts';
6
+ import { confirm } from '#utils/prompt.js';
7
7
  vi.mock('#services/npm_update_service.js', () => ({
8
8
  fetchLatestVersion: vi.fn(),
9
9
  getGlobalInstalledVersion: vi.fn(),
@@ -15,7 +15,7 @@ vi.mock('#services/npm_update_service.js', () => ({
15
15
  vi.mock('#services/coding_agents.js', () => ({
16
16
  ensureClaudePlugin: vi.fn()
17
17
  }));
18
- vi.mock('@inquirer/prompts', () => ({
18
+ vi.mock('#utils/prompt.js', () => ({
19
19
  confirm: vi.fn()
20
20
  }));
21
21
  describe('update command', () => {
@@ -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']);
@@ -1,5 +1,6 @@
1
1
  import { Command, Flags, ux } from '@oclif/core';
2
- import { input } from '@inquirer/prompts';
2
+ import { input } from '#utils/prompt.js';
3
+ import { isInteractive } from '#utils/interactive.js';
3
4
  import { generatePlanName, updateAgentTemplates, writePlanFile } from '#services/workflow_planner.js';
4
5
  import { ensureOutputAISystem } from '#services/coding_agents.js';
5
6
  import { invokePlanWorkflow, PLAN_COMMAND_OPTIONS, replyToClaude } from '#services/claude_client.js';
@@ -45,6 +46,9 @@ export default class WorkflowPlan extends Command {
45
46
  this.log('=========');
46
47
  this.log(originalPlanContent);
47
48
  this.log('=========');
49
+ if (!isInteractive()) {
50
+ return originalPlanContent;
51
+ }
48
52
  const modifications = await input({
49
53
  message: ux.colorize('gray', `Reply or type ${acceptKey} to accept the plan as is: `),
50
54
  validate: (value) => value.length >= 10 || value === acceptKey
@@ -3,11 +3,12 @@ import WorkflowPlan from './plan.js';
3
3
  import { generatePlanName, writePlanFile, updateAgentTemplates } from '#services/workflow_planner.js';
4
4
  import { ensureOutputAISystem } from '#services/coding_agents.js';
5
5
  import { invokePlanWorkflow, replyToClaude, ClaudeInvocationError } from '#services/claude_client.js';
6
- import { input } from '@inquirer/prompts';
6
+ import { input } from '#utils/prompt.js';
7
7
  vi.mock('#services/workflow_planner.js');
8
8
  vi.mock('#services/coding_agents.js');
9
9
  vi.mock('#services/claude_client.js');
10
- vi.mock('@inquirer/prompts');
10
+ vi.mock('#utils/prompt.js');
11
+ vi.mock('#utils/interactive.js', () => ({ isInteractive: () => true }));
11
12
  describe('WorkflowPlan Command', () => {
12
13
  const createCommand = () => {
13
14
  const cmd = new WorkflowPlan([], {});
@@ -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 () => {
@@ -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.12"
2
+ "framework": "0.1.13-dev.01b8dea.0"
3
3
  }
@@ -1,6 +1,10 @@
1
1
  import { ux } from '@oclif/core';
2
2
  import { checkForUpdate } from '#services/version_check.js';
3
+ import { setNonInteractive } from '#utils/interactive.js';
3
4
  const hook = async function () {
5
+ if (process.argv.includes('--yes') || process.argv.includes('--non-interactive') || !process.stdin.isTTY) {
6
+ setNonInteractive(true);
7
+ }
4
8
  try {
5
9
  const result = await checkForUpdate(this.config.version, this.config.cacheDir);
6
10
  if (!result.updateAvailable) {
@@ -105,7 +105,10 @@ function getTodoWriteMessage(message) {
105
105
  if (message.type !== 'assistant') {
106
106
  return null;
107
107
  }
108
- const todoWriteMessage = message.message.content.find((c) => c?.type === 'tool_use' && c.name === 'TodoWrite');
108
+ const todoWriteMessage = message.message.content.find((c) => {
109
+ const block = c;
110
+ return block.type === 'tool_use' && block.name === 'TodoWrite';
111
+ });
109
112
  return todoWriteMessage ?? null;
110
113
  }
111
114
  function applyInstructions(message, instructions) {
@@ -7,7 +7,7 @@ import { access } from 'node:fs/promises';
7
7
  import path from 'node:path';
8
8
  import { join } from 'node:path';
9
9
  import { ux } from '@oclif/core';
10
- import { confirm } from '@inquirer/prompts';
10
+ import { confirm } from '#utils/prompt.js';
11
11
  import debugFactory from 'debug';
12
12
  import { getTemplateDir } from '#utils/paths.js';
13
13
  import { executeClaudeCommand } from '#utils/claude.js';
@@ -19,7 +19,7 @@ vi.mock('@oclif/core', () => ({
19
19
  colorize: vi.fn().mockImplementation((_color, text) => text)
20
20
  }
21
21
  }));
22
- vi.mock('@inquirer/prompts', () => ({
22
+ vi.mock('#utils/prompt.js', () => ({
23
23
  confirm: vi.fn()
24
24
  }));
25
25
  describe('coding_agents service', () => {
@@ -157,7 +157,7 @@ describe('coding_agents service', () => {
157
157
  });
158
158
  it('should show error and prompt user when plugin commands fail', async () => {
159
159
  const { executeClaudeCommand } = await import('../utils/claude.js');
160
- const { confirm } = await import('@inquirer/prompts');
160
+ const { confirm } = await import('#utils/prompt.js');
161
161
  vi.mocked(executeClaudeCommand)
162
162
  .mockResolvedValueOnce(undefined) // marketplace add
163
163
  .mockRejectedValueOnce(new Error('Plugin update failed')); // marketplace update
@@ -169,7 +169,7 @@ describe('coding_agents service', () => {
169
169
  });
170
170
  it('should allow user to proceed without plugin setup if they confirm', async () => {
171
171
  const { executeClaudeCommand } = await import('../utils/claude.js');
172
- const { confirm } = await import('@inquirer/prompts');
172
+ const { confirm } = await import('#utils/prompt.js');
173
173
  vi.mocked(executeClaudeCommand)
174
174
  .mockRejectedValue(new Error('All plugin commands fail'));
175
175
  vi.mocked(confirm).mockResolvedValue(true);
@@ -219,7 +219,7 @@ describe('coding_agents service', () => {
219
219
  });
220
220
  it('should show error and prompt user when registerPluginMarketplace fails', async () => {
221
221
  const { executeClaudeCommand } = await import('../utils/claude.js');
222
- const { confirm } = await import('@inquirer/prompts');
222
+ const { confirm } = await import('#utils/prompt.js');
223
223
  vi.mocked(executeClaudeCommand)
224
224
  .mockResolvedValueOnce(undefined) // marketplace add
225
225
  .mockRejectedValueOnce(new Error('Plugin update failed')); // marketplace update
@@ -231,7 +231,7 @@ describe('coding_agents service', () => {
231
231
  });
232
232
  it('should show error and prompt user when installOutputAIPlugin fails', async () => {
233
233
  const { executeClaudeCommand } = await import('../utils/claude.js');
234
- const { confirm } = await import('@inquirer/prompts');
234
+ const { confirm } = await import('#utils/prompt.js');
235
235
  vi.mocked(executeClaudeCommand)
236
236
  .mockResolvedValueOnce(undefined) // marketplace add
237
237
  .mockResolvedValueOnce(undefined) // marketplace update
@@ -244,7 +244,7 @@ describe('coding_agents service', () => {
244
244
  });
245
245
  it('should allow user to proceed without plugin setup if they confirm', async () => {
246
246
  const { executeClaudeCommand } = await import('../utils/claude.js');
247
- const { confirm } = await import('@inquirer/prompts');
247
+ const { confirm } = await import('#utils/prompt.js');
248
248
  vi.mocked(executeClaudeCommand)
249
249
  .mockRejectedValue(new Error('All plugin commands fail'));
250
250
  vi.mocked(confirm).mockResolvedValue(true);
@@ -1,4 +1,4 @@
1
- import { password, confirm } from '@inquirer/prompts';
1
+ import { password, confirm } from '#utils/prompt.js';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { ux } from '@oclif/core';
@@ -21,8 +21,6 @@ export declare class DockerComposeConfigNotFoundError extends Error {
21
21
  constructor(dockerComposePath: string);
22
22
  }
23
23
  declare const isDockerInstalled: () => boolean;
24
- declare const isDockerComposeAvailable: () => boolean;
25
- declare const isDockerDaemonRunning: () => boolean;
26
24
  export declare function validateDockerEnvironment(): void;
27
25
  export declare function getDefaultDockerComposePath(): string;
28
26
  export declare function parseServiceStatus(jsonOutput: string): ServiceStatus[];
@@ -37,4 +35,4 @@ export type PullPolicy = 'always' | 'missing' | 'never';
37
35
  export declare function startDockerCompose(dockerComposePath: string, pullPolicy?: PullPolicy): Promise<DockerComposeProcess>;
38
36
  export declare function startDockerComposeDetached(dockerComposePath: string, pullPolicy?: PullPolicy): void;
39
37
  export declare function stopDockerCompose(dockerComposePath: string): Promise<void>;
40
- export { isDockerInstalled, isDockerComposeAvailable, isDockerDaemonRunning, DockerValidationError };
38
+ export { isDockerInstalled, DockerValidationError };
@@ -2,6 +2,7 @@ import { execFileSync, execSync, spawn } from 'node:child_process';
2
2
  import path from 'node:path';
3
3
  import { fileURLToPath } from 'node:url';
4
4
  import { ux } from '@oclif/core';
5
+ import semver from 'semver';
5
6
  const DEFAULT_COMPOSE_PATH = '../assets/docker/docker-compose-dev.yml';
6
7
  export const SERVICE_HEALTH = {
7
8
  HEALTHY: 'healthy',
@@ -30,27 +31,51 @@ const checkDockerCommand = (command) => {
30
31
  return false;
31
32
  }
32
33
  };
34
+ const getCommandVersion = (command, pattern = /(\d+\.\d+\.\d+)/) => {
35
+ try {
36
+ const output = execSync(command, { stdio: 'pipe', encoding: 'utf-8' }).trim();
37
+ const match = output.match(pattern);
38
+ return match ? match[1] : null;
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ };
33
44
  const isDockerInstalled = () => checkDockerCommand('docker --version');
34
- const isDockerComposeAvailable = () => checkDockerCommand('docker compose version');
35
- const isDockerDaemonRunning = () => checkDockerCommand('docker ps');
36
- const DOCKER_VALIDATIONS = [
45
+ const PREREQUISITES = [
37
46
  {
38
- check: isDockerInstalled,
39
- error: 'Docker is not installed. Please install Docker to use the dev command.\nVisit: https://docs.docker.com/get-docker/'
47
+ name: 'Docker',
48
+ semverRange: '>=20.0.0',
49
+ getVersion: () => getCommandVersion('docker --version'),
50
+ errorMessage: (current, required) => current === null ?
51
+ 'Docker is not installed. Please install Docker to use the dev command.\nVisit: https://docs.docker.com/get-docker/' :
52
+ `Docker version ${required} is required (found v${current}).\nVisit: https://docs.docker.com/get-docker/`
40
53
  },
41
54
  {
42
- check: isDockerComposeAvailable,
43
- error: 'Docker Compose is not installed. Please install Docker Compose to use the dev command.\nVisit: https://docs.docker.com/compose/install/'
55
+ name: 'Docker Compose',
56
+ semverRange: '>=2.24.0',
57
+ getVersion: () => getCommandVersion('docker compose version --short'),
58
+ errorMessage: (current, required) => current === null ?
59
+ 'Docker Compose is not installed. Please install Docker Compose to use the dev command.\nVisit: https://docs.docker.com/compose/install/' :
60
+ `Docker Compose ${required} is required (found v${current}).\nPlease update Docker Compose: https://docs.docker.com/compose/install/`
44
61
  },
45
62
  {
46
- check: isDockerDaemonRunning,
47
- error: 'Docker daemon is not running. Please start Docker and try again.'
63
+ name: 'Docker Daemon',
64
+ semverRange: '*',
65
+ getVersion: () => checkDockerCommand('docker ps') ? '0.0.0' : null,
66
+ errorMessage: () => 'Docker daemon is not running. Please start Docker and try again.'
48
67
  }
49
68
  ];
50
69
  export function validateDockerEnvironment() {
51
- const failedValidation = DOCKER_VALIDATIONS.find(v => !v.check());
52
- if (failedValidation) {
53
- throw new DockerValidationError(failedValidation.error);
70
+ for (const prereq of PREREQUISITES) {
71
+ const raw = prereq.getVersion();
72
+ const version = raw ? semver.valid(semver.coerce(raw)) : null;
73
+ if (!version) {
74
+ throw new DockerValidationError(prereq.errorMessage(null, prereq.semverRange));
75
+ }
76
+ if (!semver.satisfies(version, prereq.semverRange)) {
77
+ throw new DockerValidationError(prereq.errorMessage(version, prereq.semverRange));
78
+ }
54
79
  }
55
80
  }
56
81
  export function getDefaultDockerComposePath() {
@@ -129,4 +154,4 @@ export async function stopDockerCompose(dockerComposePath) {
129
154
  ux.stdout('⏹️ Stopping services...\n');
130
155
  execFileSync('docker', ['compose', '-f', dockerComposePath, 'down'], { stdio: 'inherit', cwd: process.cwd() });
131
156
  }
132
- export { isDockerInstalled, isDockerComposeAvailable, isDockerDaemonRunning, DockerValidationError };
157
+ export { isDockerInstalled, DockerValidationError };
@@ -1,4 +1,4 @@
1
- import { input, confirm, password } from '@inquirer/prompts';
1
+ import { input, confirm, password } from '#utils/prompt.js';
2
2
  import fs from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { ux } from '@oclif/core';