@outputai/cli 0.1.3 → 0.1.4-dev.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 (278) hide show
  1. package/dist/api/generated/api.d.ts +820 -0
  2. package/dist/api/generated/api.js +226 -0
  3. package/dist/api/http_client.d.ts +27 -0
  4. package/dist/api/http_client.js +71 -0
  5. package/dist/api/orval_post_process.d.ts +11 -0
  6. package/dist/api/orval_post_process.js +46 -0
  7. package/dist/api/parser.d.ts +17 -0
  8. package/dist/api/parser.js +68 -0
  9. package/dist/assets/config/costs.yml +309 -0
  10. package/dist/assets/docker/docker-compose-dev.yml +146 -0
  11. package/dist/commands/credentials/edit.d.ts +10 -0
  12. package/dist/commands/credentials/edit.js +67 -0
  13. package/dist/commands/credentials/edit.spec.d.ts +1 -0
  14. package/dist/commands/credentials/edit.spec.js +73 -0
  15. package/dist/commands/credentials/get.d.ts +13 -0
  16. package/dist/commands/credentials/get.js +46 -0
  17. package/dist/commands/credentials/get.spec.d.ts +1 -0
  18. package/dist/commands/credentials/get.spec.js +74 -0
  19. package/dist/commands/credentials/init.d.ts +11 -0
  20. package/dist/commands/credentials/init.js +45 -0
  21. package/dist/commands/credentials/init.spec.d.ts +1 -0
  22. package/dist/commands/credentials/init.spec.js +68 -0
  23. package/dist/commands/credentials/show.d.ts +10 -0
  24. package/dist/commands/credentials/show.js +33 -0
  25. package/dist/commands/credentials/show.spec.d.ts +1 -0
  26. package/dist/commands/credentials/show.spec.js +57 -0
  27. package/dist/commands/dev/eject.d.ts +11 -0
  28. package/dist/commands/dev/eject.js +58 -0
  29. package/dist/commands/dev/eject.spec.d.ts +1 -0
  30. package/dist/commands/dev/eject.spec.js +109 -0
  31. package/dist/commands/dev/index.d.ts +14 -0
  32. package/dist/commands/dev/index.js +173 -0
  33. package/dist/commands/dev/index.spec.d.ts +1 -0
  34. package/dist/commands/dev/index.spec.js +239 -0
  35. package/dist/commands/init.d.ts +12 -0
  36. package/dist/commands/init.js +37 -0
  37. package/dist/commands/init.spec.d.ts +1 -0
  38. package/dist/commands/init.spec.js +100 -0
  39. package/dist/commands/update.d.ts +14 -0
  40. package/dist/commands/update.js +120 -0
  41. package/dist/commands/update.spec.d.ts +1 -0
  42. package/dist/commands/update.spec.js +178 -0
  43. package/dist/commands/workflow/cost.d.ts +16 -0
  44. package/dist/commands/workflow/cost.js +71 -0
  45. package/dist/commands/workflow/cost.spec.d.ts +1 -0
  46. package/dist/commands/workflow/cost.spec.js +47 -0
  47. package/dist/commands/workflow/dataset/generate.d.ts +22 -0
  48. package/dist/commands/workflow/dataset/generate.js +143 -0
  49. package/dist/commands/workflow/dataset/list.d.ts +12 -0
  50. package/dist/commands/workflow/dataset/list.js +87 -0
  51. package/dist/commands/workflow/debug.d.ts +16 -0
  52. package/dist/commands/workflow/debug.js +60 -0
  53. package/dist/commands/workflow/debug.spec.d.ts +1 -0
  54. package/dist/commands/workflow/debug.spec.js +34 -0
  55. package/dist/commands/workflow/generate.d.ts +17 -0
  56. package/dist/commands/workflow/generate.js +85 -0
  57. package/dist/commands/workflow/generate.spec.d.ts +1 -0
  58. package/dist/commands/workflow/generate.spec.js +115 -0
  59. package/dist/commands/workflow/list.d.ts +22 -0
  60. package/dist/commands/workflow/list.js +152 -0
  61. package/dist/commands/workflow/list.spec.d.ts +1 -0
  62. package/dist/commands/workflow/list.spec.js +99 -0
  63. package/dist/commands/workflow/plan.d.ts +12 -0
  64. package/dist/commands/workflow/plan.js +66 -0
  65. package/dist/commands/workflow/plan.spec.d.ts +1 -0
  66. package/dist/commands/workflow/plan.spec.js +341 -0
  67. package/dist/commands/workflow/reset.d.ts +14 -0
  68. package/dist/commands/workflow/reset.js +51 -0
  69. package/dist/commands/workflow/result.d.ts +13 -0
  70. package/dist/commands/workflow/result.js +46 -0
  71. package/dist/commands/workflow/result.spec.d.ts +1 -0
  72. package/dist/commands/workflow/result.spec.js +23 -0
  73. package/dist/commands/workflow/run.d.ts +16 -0
  74. package/dist/commands/workflow/run.js +97 -0
  75. package/dist/commands/workflow/run.spec.d.ts +1 -0
  76. package/dist/commands/workflow/run.spec.js +110 -0
  77. package/dist/commands/workflow/runs/list.d.ts +14 -0
  78. package/dist/commands/workflow/runs/list.js +104 -0
  79. package/dist/commands/workflow/start.d.ts +15 -0
  80. package/dist/commands/workflow/start.js +62 -0
  81. package/dist/commands/workflow/start.spec.d.ts +1 -0
  82. package/dist/commands/workflow/start.spec.js +28 -0
  83. package/dist/commands/workflow/status.d.ts +13 -0
  84. package/dist/commands/workflow/status.js +57 -0
  85. package/dist/commands/workflow/status.spec.d.ts +1 -0
  86. package/dist/commands/workflow/status.spec.js +33 -0
  87. package/dist/commands/workflow/stop.d.ts +10 -0
  88. package/dist/commands/workflow/stop.js +31 -0
  89. package/dist/commands/workflow/stop.spec.d.ts +1 -0
  90. package/dist/commands/workflow/stop.spec.js +17 -0
  91. package/dist/commands/workflow/terminate.d.ts +13 -0
  92. package/dist/commands/workflow/terminate.js +39 -0
  93. package/dist/commands/workflow/test_eval.d.ts +20 -0
  94. package/dist/commands/workflow/test_eval.js +151 -0
  95. package/dist/config.d.ts +47 -0
  96. package/dist/config.js +47 -0
  97. package/dist/generated/framework_version.json +3 -0
  98. package/dist/hooks/init.d.ts +3 -0
  99. package/dist/hooks/init.js +30 -0
  100. package/dist/hooks/init.spec.d.ts +1 -0
  101. package/dist/hooks/init.spec.js +54 -0
  102. package/dist/index.d.ts +1 -0
  103. package/dist/index.js +1 -0
  104. package/dist/index.spec.d.ts +1 -0
  105. package/dist/index.spec.js +6 -0
  106. package/dist/services/claude_client.d.ts +30 -0
  107. package/dist/services/claude_client.integration.test.d.ts +1 -0
  108. package/dist/services/claude_client.integration.test.js +43 -0
  109. package/dist/services/claude_client.js +215 -0
  110. package/dist/services/claude_client.spec.d.ts +1 -0
  111. package/dist/services/claude_client.spec.js +145 -0
  112. package/dist/services/coding_agents.d.ts +36 -0
  113. package/dist/services/coding_agents.js +236 -0
  114. package/dist/services/coding_agents.spec.d.ts +1 -0
  115. package/dist/services/coding_agents.spec.js +256 -0
  116. package/dist/services/copy_assets.spec.d.ts +1 -0
  117. package/dist/services/copy_assets.spec.js +22 -0
  118. package/dist/services/cost_calculator.d.ts +18 -0
  119. package/dist/services/cost_calculator.js +359 -0
  120. package/dist/services/cost_calculator.spec.d.ts +1 -0
  121. package/dist/services/cost_calculator.spec.js +540 -0
  122. package/dist/services/credentials_service.d.ts +12 -0
  123. package/dist/services/credentials_service.integration.test.d.ts +1 -0
  124. package/dist/services/credentials_service.integration.test.js +66 -0
  125. package/dist/services/credentials_service.js +64 -0
  126. package/dist/services/credentials_service.spec.d.ts +1 -0
  127. package/dist/services/credentials_service.spec.js +106 -0
  128. package/dist/services/datasets.d.ts +20 -0
  129. package/dist/services/datasets.js +132 -0
  130. package/dist/services/docker.d.ts +39 -0
  131. package/dist/services/docker.js +160 -0
  132. package/dist/services/docker.spec.d.ts +1 -0
  133. package/dist/services/docker.spec.js +124 -0
  134. package/dist/services/env_configurator.d.ts +15 -0
  135. package/dist/services/env_configurator.js +163 -0
  136. package/dist/services/env_configurator.spec.d.ts +1 -0
  137. package/dist/services/env_configurator.spec.js +192 -0
  138. package/dist/services/generate_plan_name@v1.prompt +24 -0
  139. package/dist/services/messages.d.ts +9 -0
  140. package/dist/services/messages.js +338 -0
  141. package/dist/services/messages.spec.d.ts +1 -0
  142. package/dist/services/messages.spec.js +55 -0
  143. package/dist/services/npm_update_service.d.ts +6 -0
  144. package/dist/services/npm_update_service.js +87 -0
  145. package/dist/services/npm_update_service.spec.d.ts +1 -0
  146. package/dist/services/npm_update_service.spec.js +104 -0
  147. package/dist/services/project_scaffold.d.ts +31 -0
  148. package/dist/services/project_scaffold.js +212 -0
  149. package/dist/services/project_scaffold.spec.d.ts +1 -0
  150. package/dist/services/project_scaffold.spec.js +122 -0
  151. package/dist/services/s3_trace_downloader.d.ts +12 -0
  152. package/dist/services/s3_trace_downloader.js +57 -0
  153. package/dist/services/template_processor.d.ts +14 -0
  154. package/dist/services/template_processor.js +57 -0
  155. package/dist/services/trace_reader.d.ts +16 -0
  156. package/dist/services/trace_reader.js +57 -0
  157. package/dist/services/trace_reader.spec.d.ts +1 -0
  158. package/dist/services/trace_reader.spec.js +78 -0
  159. package/dist/services/version_check.d.ts +6 -0
  160. package/dist/services/version_check.js +52 -0
  161. package/dist/services/version_check.spec.d.ts +1 -0
  162. package/dist/services/version_check.spec.js +106 -0
  163. package/dist/services/workflow_builder.d.ts +16 -0
  164. package/dist/services/workflow_builder.js +86 -0
  165. package/dist/services/workflow_builder.spec.d.ts +1 -0
  166. package/dist/services/workflow_builder.spec.js +165 -0
  167. package/dist/services/workflow_generator.d.ts +5 -0
  168. package/dist/services/workflow_generator.js +40 -0
  169. package/dist/services/workflow_generator.spec.d.ts +1 -0
  170. package/dist/services/workflow_generator.spec.js +77 -0
  171. package/dist/services/workflow_planner.d.ts +15 -0
  172. package/dist/services/workflow_planner.js +48 -0
  173. package/dist/services/workflow_planner.spec.d.ts +1 -0
  174. package/dist/services/workflow_planner.spec.js +122 -0
  175. package/dist/services/workflow_runs.d.ts +14 -0
  176. package/dist/services/workflow_runs.js +25 -0
  177. package/dist/templates/agent_instructions/CLAUDE.md.template +19 -0
  178. package/dist/templates/agent_instructions/dotclaude/settings.json.template +29 -0
  179. package/dist/templates/project/.env.example.template +9 -0
  180. package/dist/templates/project/.gitignore.template +35 -0
  181. package/dist/templates/project/README.md.template +100 -0
  182. package/dist/templates/project/config/costs.yml.template +29 -0
  183. package/dist/templates/project/package.json.template +25 -0
  184. package/dist/templates/project/src/clients/jina.ts.template +30 -0
  185. package/dist/templates/project/src/shared/utils/string.ts.template +3 -0
  186. package/dist/templates/project/src/shared/utils/url.ts.template +15 -0
  187. package/dist/templates/project/src/workflows/blog_evaluator/evaluators.ts.template +23 -0
  188. package/dist/templates/project/src/workflows/blog_evaluator/prompts/signal_noise@v1.prompt.template +26 -0
  189. package/dist/templates/project/src/workflows/blog_evaluator/scenarios/paulgraham_hwh.json.template +3 -0
  190. package/dist/templates/project/src/workflows/blog_evaluator/steps.ts.template +27 -0
  191. package/dist/templates/project/src/workflows/blog_evaluator/types.ts.template +30 -0
  192. package/dist/templates/project/src/workflows/blog_evaluator/utils.ts.template +15 -0
  193. package/dist/templates/project/src/workflows/blog_evaluator/workflow.ts.template +27 -0
  194. package/dist/templates/project/tsconfig.json.template +20 -0
  195. package/dist/templates/workflow/README.md.template +216 -0
  196. package/dist/templates/workflow/evaluators.ts.template +21 -0
  197. package/dist/templates/workflow/prompts/example@v1.prompt.template +15 -0
  198. package/dist/templates/workflow/scenarios/test_input.json.template +3 -0
  199. package/dist/templates/workflow/steps.ts.template +20 -0
  200. package/dist/templates/workflow/types.ts.template +13 -0
  201. package/dist/templates/workflow/workflow.ts.template +23 -0
  202. package/dist/test_helpers/mocks.d.ts +38 -0
  203. package/dist/test_helpers/mocks.js +77 -0
  204. package/dist/types/cost.d.ts +149 -0
  205. package/dist/types/cost.js +6 -0
  206. package/dist/types/domain.d.ts +20 -0
  207. package/dist/types/domain.js +4 -0
  208. package/dist/types/errors.d.ts +68 -0
  209. package/dist/types/errors.js +100 -0
  210. package/dist/types/errors.spec.d.ts +1 -0
  211. package/dist/types/errors.spec.js +18 -0
  212. package/dist/types/generator.d.ts +26 -0
  213. package/dist/types/generator.js +1 -0
  214. package/dist/types/trace.d.ts +161 -0
  215. package/dist/types/trace.js +18 -0
  216. package/dist/utils/claude.d.ts +5 -0
  217. package/dist/utils/claude.js +19 -0
  218. package/dist/utils/claude.spec.d.ts +1 -0
  219. package/dist/utils/claude.spec.js +119 -0
  220. package/dist/utils/constants.d.ts +5 -0
  221. package/dist/utils/constants.js +4 -0
  222. package/dist/utils/cost_formatter.d.ts +5 -0
  223. package/dist/utils/cost_formatter.js +218 -0
  224. package/dist/utils/date_formatter.d.ts +23 -0
  225. package/dist/utils/date_formatter.js +49 -0
  226. package/dist/utils/env_loader.d.ts +1 -0
  227. package/dist/utils/env_loader.js +22 -0
  228. package/dist/utils/env_loader.spec.d.ts +1 -0
  229. package/dist/utils/env_loader.spec.js +43 -0
  230. package/dist/utils/error_handler.d.ts +8 -0
  231. package/dist/utils/error_handler.js +71 -0
  232. package/dist/utils/error_utils.d.ts +24 -0
  233. package/dist/utils/error_utils.js +87 -0
  234. package/dist/utils/file_system.d.ts +3 -0
  235. package/dist/utils/file_system.js +33 -0
  236. package/dist/utils/format_workflow_result.d.ts +5 -0
  237. package/dist/utils/format_workflow_result.js +18 -0
  238. package/dist/utils/format_workflow_result.spec.d.ts +1 -0
  239. package/dist/utils/format_workflow_result.spec.js +81 -0
  240. package/dist/utils/framework_version.d.ts +4 -0
  241. package/dist/utils/framework_version.js +4 -0
  242. package/dist/utils/framework_version.spec.d.ts +1 -0
  243. package/dist/utils/framework_version.spec.js +13 -0
  244. package/dist/utils/header_utils.d.ts +12 -0
  245. package/dist/utils/header_utils.js +29 -0
  246. package/dist/utils/header_utils.spec.d.ts +1 -0
  247. package/dist/utils/header_utils.spec.js +52 -0
  248. package/dist/utils/input_parser.d.ts +1 -0
  249. package/dist/utils/input_parser.js +19 -0
  250. package/dist/utils/output_formatter.d.ts +2 -0
  251. package/dist/utils/output_formatter.js +11 -0
  252. package/dist/utils/paths.d.ts +25 -0
  253. package/dist/utils/paths.js +36 -0
  254. package/dist/utils/process.d.ts +4 -0
  255. package/dist/utils/process.js +50 -0
  256. package/dist/utils/resolve_input.d.ts +1 -0
  257. package/dist/utils/resolve_input.js +22 -0
  258. package/dist/utils/scenario_resolver.d.ts +9 -0
  259. package/dist/utils/scenario_resolver.js +93 -0
  260. package/dist/utils/scenario_resolver.spec.d.ts +1 -0
  261. package/dist/utils/scenario_resolver.spec.js +214 -0
  262. package/dist/utils/secret_sanitizer.d.ts +1 -0
  263. package/dist/utils/secret_sanitizer.js +29 -0
  264. package/dist/utils/sleep.d.ts +5 -0
  265. package/dist/utils/sleep.js +5 -0
  266. package/dist/utils/template.d.ts +9 -0
  267. package/dist/utils/template.js +30 -0
  268. package/dist/utils/template.spec.d.ts +1 -0
  269. package/dist/utils/template.spec.js +77 -0
  270. package/dist/utils/trace_extractor.d.ts +27 -0
  271. package/dist/utils/trace_extractor.js +53 -0
  272. package/dist/utils/trace_formatter.d.ts +11 -0
  273. package/dist/utils/trace_formatter.js +402 -0
  274. package/dist/utils/validation.d.ts +13 -0
  275. package/dist/utils/validation.js +25 -0
  276. package/dist/utils/validation.spec.d.ts +1 -0
  277. package/dist/utils/validation.spec.js +140 -0
  278. package/package.json +4 -4
@@ -0,0 +1,173 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import logUpdate from 'log-update';
5
+ import { validateDockerEnvironment, startDockerCompose, startDockerComposeDetached, stopDockerCompose, getServiceStatus, DockerComposeConfigNotFoundError, getDefaultDockerComposePath, SERVICE_HEALTH, SERVICE_STATE } from '#services/docker.js';
6
+ import { getErrorMessage } from '#utils/error_utils.js';
7
+ import { getDevSuccessMessage } from '#services/messages.js';
8
+ import { ensureClaudePlugin } from '#services/coding_agents.js';
9
+ const ANSI = {
10
+ RESET: '\x1b[0m',
11
+ DIM: '\x1b[2m',
12
+ BOLD: '\x1b[1m',
13
+ CYAN: '\x1b[36m',
14
+ RED: '\x1b[31m',
15
+ YELLOW: '\x1b[33m',
16
+ BG_RED: '\x1b[41m',
17
+ WHITE: '\x1b[37m'
18
+ };
19
+ const STATUS_ICONS = {
20
+ [SERVICE_HEALTH.HEALTHY]: '●',
21
+ [SERVICE_HEALTH.UNHEALTHY]: '○',
22
+ [SERVICE_HEALTH.STARTING]: '◐',
23
+ [SERVICE_HEALTH.NONE]: '●',
24
+ [SERVICE_STATE.RUNNING]: '●',
25
+ [SERVICE_STATE.EXITED]: '✗'
26
+ };
27
+ const STATUS_COLORS = {
28
+ [SERVICE_HEALTH.HEALTHY]: '\x1b[32m',
29
+ [SERVICE_HEALTH.UNHEALTHY]: '\x1b[31m',
30
+ [SERVICE_HEALTH.STARTING]: '\x1b[33m',
31
+ [SERVICE_HEALTH.NONE]: '\x1b[34m',
32
+ [SERVICE_STATE.RUNNING]: '\x1b[34m',
33
+ [SERVICE_STATE.EXITED]: '\x1b[31m'
34
+ };
35
+ const formatService = (service) => {
36
+ const healthKey = service.health === SERVICE_HEALTH.NONE ? service.state : service.health;
37
+ const icon = STATUS_ICONS[healthKey] || '?';
38
+ const color = STATUS_COLORS[healthKey] || '';
39
+ const ports = service.ports.length ? service.ports.join(', ') : '-';
40
+ const status = service.health === SERVICE_HEALTH.NONE ? service.state : service.health;
41
+ const name = service.name.padEnd(15);
42
+ const statusPadded = status.padEnd(10);
43
+ return ` ${color}${icon}${ANSI.RESET} ${name} ${ANSI.DIM}${statusPadded}${ANSI.RESET} ${ANSI.DIM}${ports}${ANSI.RESET}`;
44
+ };
45
+ const getFailedServicesWarning = (services) => {
46
+ const failedServices = services.filter(s => s.state === SERVICE_STATE.EXITED);
47
+ if (failedServices.length === 0) {
48
+ return [];
49
+ }
50
+ const failedNames = failedServices.map(s => s.name);
51
+ const hasWorkerFailed = failedNames.some(name => name.toLowerCase().includes('worker'));
52
+ const warningLines = [
53
+ '',
54
+ `${ANSI.BG_RED}${ANSI.WHITE}${ANSI.BOLD} ⚠️ SERVICE FAILURE DETECTED ${ANSI.RESET}`,
55
+ '',
56
+ `${ANSI.RED}${ANSI.BOLD}Failed services:${ANSI.RESET} ${failedNames.join(', ')}`
57
+ ];
58
+ if (hasWorkerFailed) {
59
+ warningLines.push('', `${ANSI.YELLOW}${ANSI.BOLD}⚡ The worker is not running!${ANSI.RESET}`, `${ANSI.YELLOW} Workflows will fail until the worker is restarted.${ANSI.RESET}`, '', `${ANSI.DIM}Check the logs with: docker compose logs worker${ANSI.RESET}`);
60
+ }
61
+ else {
62
+ warningLines.push('', `${ANSI.DIM}Check the logs with: docker compose logs <service-name>${ANSI.RESET}`);
63
+ }
64
+ return warningLines;
65
+ };
66
+ const poll = async (fn, intervalMs) => {
67
+ for (;;) {
68
+ await fn();
69
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
70
+ }
71
+ };
72
+ export default class Dev extends Command {
73
+ static description = 'Start Output development services (auto-restarts worker on file changes)';
74
+ static examples = [
75
+ '<%= config.bin %> <%= command.id %>',
76
+ '<%= config.bin %> <%= command.id %> --compose-file ./custom-docker-compose.yml',
77
+ '<%= config.bin %> <%= command.id %> --image-pull-policy missing'
78
+ ];
79
+ static args = {};
80
+ static flags = {
81
+ 'compose-file': Flags.string({
82
+ description: 'Path to a custom docker-compose file',
83
+ required: false,
84
+ char: 'f'
85
+ }),
86
+ 'image-pull-policy': Flags.string({
87
+ description: 'Image pull policy for docker compose (always, missing, never)',
88
+ options: ['always', 'missing', 'never'],
89
+ default: 'always'
90
+ }),
91
+ detached: Flags.boolean({
92
+ description: 'Start services in detached (background) mode and exit immediately',
93
+ default: false,
94
+ char: 'd'
95
+ })
96
+ };
97
+ dockerProcess = null;
98
+ async run() {
99
+ const { flags } = await this.parse(Dev);
100
+ // Ensure Claude plugin is configured (fire-and-forget, silent)
101
+ ensureClaudePlugin(process.cwd(), { silent: true }).catch(() => { });
102
+ validateDockerEnvironment();
103
+ const dockerComposePath = flags['compose-file'] ?
104
+ path.resolve(process.cwd(), flags['compose-file']) :
105
+ getDefaultDockerComposePath();
106
+ try {
107
+ await fs.access(dockerComposePath);
108
+ }
109
+ catch {
110
+ throw new DockerComposeConfigNotFoundError(dockerComposePath);
111
+ }
112
+ this.log('\n🚀 Starting Output development services...\n');
113
+ if (flags['compose-file']) {
114
+ this.log(`Using custom docker-compose file: ${flags['compose-file']}\n`);
115
+ }
116
+ const pullPolicy = flags['image-pull-policy'];
117
+ if (flags.detached) {
118
+ this.log('🐳 Starting services in detached mode...\n');
119
+ startDockerComposeDetached(dockerComposePath, pullPolicy);
120
+ this.log('✅ Services started. Run `output dev` without --detached to monitor status.\n');
121
+ return;
122
+ }
123
+ this.log('File watching enabled - worker will restart automatically on changes\n');
124
+ const cleanup = async () => {
125
+ this.log('\n');
126
+ if (this.dockerProcess) {
127
+ this.dockerProcess.kill('SIGTERM');
128
+ }
129
+ await stopDockerCompose(dockerComposePath);
130
+ process.exit(0);
131
+ };
132
+ process.on('SIGINT', cleanup);
133
+ process.on('SIGTERM', cleanup);
134
+ try {
135
+ const { process: dockerProc, waitForHealthy } = await startDockerCompose(dockerComposePath, pullPolicy);
136
+ this.dockerProcess = dockerProc;
137
+ dockerProc.on('error', error => {
138
+ this.error(`Docker process error: ${getErrorMessage(error)}`, { exit: 1 });
139
+ });
140
+ this.log('⏳ Waiting for services to become healthy...\n');
141
+ await waitForHealthy();
142
+ const services = await getServiceStatus(dockerComposePath);
143
+ this.log(getDevSuccessMessage(services));
144
+ await this.pollServiceStatus(dockerComposePath);
145
+ }
146
+ catch (error) {
147
+ this.error(getErrorMessage(error), { exit: 1 });
148
+ }
149
+ }
150
+ async pollServiceStatus(dockerComposePath) {
151
+ const outputServiceStatus = async () => {
152
+ try {
153
+ const services = await getServiceStatus(dockerComposePath);
154
+ const failureWarning = getFailedServicesWarning(services);
155
+ const lines = [
156
+ `${ANSI.BOLD}📊 Service Status${ANSI.RESET}`,
157
+ '',
158
+ ...services.map(formatService),
159
+ ...failureWarning,
160
+ '',
161
+ `${ANSI.CYAN}🌐 Temporal UI:${ANSI.RESET} ${ANSI.BOLD}http://localhost:8080${ANSI.RESET}`,
162
+ '',
163
+ `${ANSI.DIM}Press Ctrl+C to stop services${ANSI.RESET}`
164
+ ];
165
+ logUpdate(lines.join('\n'));
166
+ }
167
+ catch {
168
+ // silent retry on next poll
169
+ }
170
+ };
171
+ await poll(outputServiceStatus, 2000);
172
+ }
173
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,239 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
3
+ import fs from 'node:fs/promises';
4
+ import * as dockerService from '#services/docker.js';
5
+ import * as codingAgentsService from '#services/coding_agents.js';
6
+ import Dev from './index.js';
7
+ vi.mock('#services/coding_agents.js', () => ({
8
+ ensureClaudePlugin: vi.fn().mockResolvedValue(undefined)
9
+ }));
10
+ vi.mock('#services/docker.js', () => ({
11
+ validateDockerEnvironment: vi.fn(),
12
+ startDockerCompose: vi.fn(),
13
+ stopDockerCompose: vi.fn().mockResolvedValue(undefined),
14
+ getServiceStatus: vi.fn().mockResolvedValue([
15
+ { name: 'redis', state: 'running', health: 'healthy', ports: ['6379:6379'] },
16
+ { name: 'temporal', state: 'running', health: 'healthy', ports: ['7233:7233'] }
17
+ ]),
18
+ DockerComposeConfigNotFoundError: Error,
19
+ DockerValidationError: Error,
20
+ getDefaultDockerComposePath: vi.fn(() => '/path/to/docker-compose-dev.yml'),
21
+ SERVICE_HEALTH: {
22
+ HEALTHY: 'healthy',
23
+ UNHEALTHY: 'unhealthy',
24
+ STARTING: 'starting',
25
+ NONE: 'none'
26
+ },
27
+ SERVICE_STATE: {
28
+ RUNNING: 'running',
29
+ EXITED: 'exited'
30
+ }
31
+ }));
32
+ vi.mock('node:fs/promises', () => ({
33
+ default: {
34
+ access: vi.fn()
35
+ }
36
+ }));
37
+ const createMockDockerProcess = () => ({
38
+ process: {
39
+ on: vi.fn(),
40
+ kill: vi.fn(),
41
+ stdout: { on: vi.fn() },
42
+ stderr: { on: vi.fn() }
43
+ },
44
+ waitForHealthy: vi.fn().mockResolvedValue(undefined)
45
+ });
46
+ describe('dev command', () => {
47
+ beforeEach(() => {
48
+ vi.clearAllMocks();
49
+ // By default, docker validation succeeds
50
+ vi.mocked(dockerService.validateDockerEnvironment).mockResolvedValue(undefined);
51
+ // By default, startDockerCompose returns a mock process
52
+ vi.mocked(dockerService.startDockerCompose).mockResolvedValue(createMockDockerProcess());
53
+ // By default, fs.access succeeds (file exists)
54
+ vi.mocked(fs).access.mockResolvedValue(undefined);
55
+ // By default, ensureClaudePlugin succeeds
56
+ vi.mocked(codingAgentsService.ensureClaudePlugin).mockResolvedValue(undefined);
57
+ });
58
+ afterEach(() => {
59
+ vi.restoreAllMocks();
60
+ });
61
+ describe('command structure', () => {
62
+ it('should have correct description', () => {
63
+ expect(Dev.description).toBeDefined();
64
+ expect(Dev.description).toContain('development services');
65
+ });
66
+ it('should have examples', () => {
67
+ expect(Dev.examples).toBeDefined();
68
+ expect(Array.isArray(Dev.examples)).toBe(true);
69
+ expect(Dev.examples.length).toBeGreaterThan(0);
70
+ });
71
+ it('should have no required arguments', () => {
72
+ expect(Dev.args).toBeDefined();
73
+ expect(Object.keys(Dev.args)).toHaveLength(0);
74
+ });
75
+ it('should have compose-file flag defined', () => {
76
+ expect(Dev.flags).toBeDefined();
77
+ expect(Dev.flags['compose-file']).toBeDefined();
78
+ expect(Dev.flags['compose-file'].description).toContain('custom docker-compose');
79
+ expect(Dev.flags['compose-file'].required).toBe(false);
80
+ expect(Dev.flags['compose-file'].char).toBe('f');
81
+ });
82
+ it('should have image-pull-policy flag defined', () => {
83
+ expect(Dev.flags).toBeDefined();
84
+ expect(Dev.flags['image-pull-policy']).toBeDefined();
85
+ expect(Dev.flags['image-pull-policy'].description).toContain('pull policy');
86
+ });
87
+ });
88
+ describe('command instantiation', () => {
89
+ it('should be instantiable', () => {
90
+ const cmd = new Dev([], {});
91
+ expect(cmd).toBeInstanceOf(Dev);
92
+ });
93
+ it('should have a run method', () => {
94
+ const cmd = new Dev([], {});
95
+ expect(cmd.run).toBeDefined();
96
+ expect(typeof cmd.run).toBe('function');
97
+ });
98
+ });
99
+ describe('Docker validation', () => {
100
+ it('should error if Docker validation fails', async () => {
101
+ const config = {
102
+ runHook: vi.fn().mockResolvedValue({ failures: [], successes: [] })
103
+ };
104
+ const cmd = new Dev([], config);
105
+ cmd.log = vi.fn();
106
+ // Mock parse to return flags
107
+ Object.defineProperty(cmd, 'parse', {
108
+ value: vi.fn().mockResolvedValue({ flags: { 'compose-file': undefined }, args: {} }),
109
+ configurable: true
110
+ });
111
+ const validationError = new Error('Docker is not installed');
112
+ vi.mocked(dockerService.validateDockerEnvironment).mockImplementation(() => {
113
+ throw validationError;
114
+ });
115
+ await expect(cmd.run()).rejects.toThrow('Docker is not installed');
116
+ });
117
+ it('should call validateDockerEnvironment', async () => {
118
+ const cmd = new Dev([], {});
119
+ cmd.log = vi.fn();
120
+ cmd.error = vi.fn();
121
+ // Mock the subprocess spawn to prevent actual execution
122
+ vi.doMock('node:child_process', () => ({
123
+ spawn: vi.fn().mockReturnValue({
124
+ on: vi.fn(),
125
+ kill: vi.fn()
126
+ })
127
+ }));
128
+ // This test just verifies the function is called
129
+ expect(vi.mocked(dockerService.validateDockerEnvironment)).toBeDefined();
130
+ });
131
+ });
132
+ describe('Claude plugin update', () => {
133
+ it('should call ensureClaudePlugin on startup', async () => {
134
+ const cmd = new Dev([], {});
135
+ cmd.log = vi.fn();
136
+ cmd.error = vi.fn();
137
+ Object.defineProperty(cmd, 'parse', {
138
+ value: vi.fn().mockResolvedValue({ flags: { 'compose-file': undefined, 'image-pull-policy': 'always' }, args: {} }),
139
+ configurable: true
140
+ });
141
+ const runPromise = cmd.run();
142
+ await new Promise(resolve => setImmediate(resolve));
143
+ expect(codingAgentsService.ensureClaudePlugin).toHaveBeenCalledWith(process.cwd(), { silent: true });
144
+ runPromise.catch(() => { });
145
+ });
146
+ it('should not block dev if ensureClaudePlugin fails', async () => {
147
+ vi.mocked(codingAgentsService.ensureClaudePlugin).mockRejectedValue(new Error('Plugin update failed'));
148
+ const cmd = new Dev([], {});
149
+ cmd.log = vi.fn();
150
+ cmd.error = vi.fn();
151
+ Object.defineProperty(cmd, 'parse', {
152
+ value: vi.fn().mockResolvedValue({ flags: { 'compose-file': undefined, 'image-pull-policy': 'always' }, args: {} }),
153
+ configurable: true
154
+ });
155
+ const runPromise = cmd.run();
156
+ await new Promise(resolve => setImmediate(resolve));
157
+ // Docker compose should still be called even if plugin update fails
158
+ expect(dockerService.startDockerCompose).toHaveBeenCalled();
159
+ runPromise.catch(() => { });
160
+ });
161
+ });
162
+ describe('watch functionality', () => {
163
+ it('should start docker compose', async () => {
164
+ const cmd = new Dev([], {});
165
+ cmd.log = vi.fn();
166
+ cmd.error = vi.fn();
167
+ // Mock parse to return flags
168
+ Object.defineProperty(cmd, 'parse', {
169
+ value: vi.fn().mockResolvedValue({ flags: { 'compose-file': undefined, 'image-pull-policy': 'always' }, args: {} }),
170
+ configurable: true
171
+ });
172
+ // Run the command but don't await it since it waits forever after startup
173
+ const runPromise = cmd.run();
174
+ // Wait a tick for startDockerCompose to be called
175
+ await new Promise(resolve => setImmediate(resolve));
176
+ expect(dockerService.startDockerCompose).toHaveBeenCalledWith('/path/to/docker-compose-dev.yml', 'always' // default pull policy
177
+ );
178
+ expect(cmd.log).toHaveBeenCalledWith(expect.stringContaining('File watching enabled'));
179
+ // Cancel the promise (it will be rejected but we don't care)
180
+ runPromise.catch(() => { });
181
+ });
182
+ it('should handle docker compose configuration not found', async () => {
183
+ vi.mocked(fs).access.mockRejectedValue(new Error('File not found'));
184
+ const cmd = new Dev([], {});
185
+ cmd.log = vi.fn();
186
+ cmd.error = vi.fn();
187
+ await expect(cmd.run()).rejects.toThrow();
188
+ });
189
+ it('should handle startDockerCompose errors', async () => {
190
+ vi.mocked(dockerService.startDockerCompose).mockRejectedValue(new Error('Docker error'));
191
+ const cmd = new Dev([], {});
192
+ cmd.log = vi.fn();
193
+ cmd.error = vi.fn();
194
+ // Mock parse to return flags
195
+ Object.defineProperty(cmd, 'parse', {
196
+ value: vi.fn().mockResolvedValue({ flags: { 'compose-file': undefined, 'image-pull-policy': 'always' }, args: {} }),
197
+ configurable: true
198
+ });
199
+ await cmd.run();
200
+ expect(cmd.error).toHaveBeenCalledWith('Docker error', { exit: 1 });
201
+ });
202
+ });
203
+ describe('image pull policy', () => {
204
+ it('should pass pull policy to startDockerCompose', async () => {
205
+ const cmd = new Dev([], {});
206
+ cmd.log = vi.fn();
207
+ cmd.error = vi.fn();
208
+ // Mock parse to return flags with missing pull policy
209
+ Object.defineProperty(cmd, 'parse', {
210
+ value: vi.fn().mockResolvedValue({ flags: { 'compose-file': undefined, 'image-pull-policy': 'missing' }, args: {} }),
211
+ configurable: true
212
+ });
213
+ // Run the command but don't await it since it waits forever after startup
214
+ const runPromise = cmd.run();
215
+ // Wait a tick for startDockerCompose to be called
216
+ await new Promise(resolve => setImmediate(resolve));
217
+ expect(dockerService.startDockerCompose).toHaveBeenCalledWith('/path/to/docker-compose-dev.yml', 'missing');
218
+ // Cancel the promise (it will be rejected but we don't care)
219
+ runPromise.catch(() => { });
220
+ });
221
+ it('should use never pull policy when specified', async () => {
222
+ const cmd = new Dev([], {});
223
+ cmd.log = vi.fn();
224
+ cmd.error = vi.fn();
225
+ // Mock parse to return flags with never pull policy
226
+ Object.defineProperty(cmd, 'parse', {
227
+ value: vi.fn().mockResolvedValue({ flags: { 'compose-file': undefined, 'image-pull-policy': 'never' }, args: {} }),
228
+ configurable: true
229
+ });
230
+ // Run the command but don't await it since it waits forever after startup
231
+ const runPromise = cmd.run();
232
+ // Wait a tick for startDockerCompose to be called
233
+ await new Promise(resolve => setImmediate(resolve));
234
+ expect(dockerService.startDockerCompose).toHaveBeenCalledWith('/path/to/docker-compose-dev.yml', 'never');
235
+ // Cancel the promise (it will be rejected but we don't care)
236
+ runPromise.catch(() => { });
237
+ });
238
+ });
239
+ });
@@ -0,0 +1,12 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Init extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ folderName: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ 'skip-env': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,37 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import { UserCancelledError } from '#types/errors.js';
3
+ import { runInit } from '#services/project_scaffold.js';
4
+ export default class Init extends Command {
5
+ static description = 'Initialize a new Output project by scaffolding the complete project structure';
6
+ static examples = [
7
+ '<%= config.bin %> <%= command.id %>',
8
+ '<%= config.bin %> <%= command.id %> my-workflow-project'
9
+ ];
10
+ static args = {
11
+ folderName: Args.string({
12
+ description: 'Optional folder name for the project (skips folder name prompt)',
13
+ required: false
14
+ })
15
+ };
16
+ static flags = {
17
+ 'skip-env': Flags.boolean({
18
+ description: 'Skip interactive environment variable configuration',
19
+ default: false
20
+ })
21
+ };
22
+ async run() {
23
+ try {
24
+ const { args, flags } = await this.parse(Init);
25
+ await runInit(flags['skip-env'], args.folderName);
26
+ }
27
+ catch (error) {
28
+ if (error instanceof UserCancelledError) {
29
+ this.log(error.message);
30
+ return;
31
+ }
32
+ // runInit handles cleanup internally and throws Error with message
33
+ const errorMessage = error instanceof Error ? error.message : String(error);
34
+ this.error(errorMessage);
35
+ }
36
+ }
37
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,100 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
3
+ import * as projectScaffold from '#services/project_scaffold.js';
4
+ import { UserCancelledError } from '#types/errors.js';
5
+ import Init from './init.js';
6
+ vi.mock('#services/project_scaffold.js');
7
+ describe('init command', () => {
8
+ beforeEach(() => {
9
+ vi.clearAllMocks();
10
+ vi.mocked(projectScaffold.runInit).mockResolvedValue(undefined);
11
+ });
12
+ afterEach(() => {
13
+ vi.restoreAllMocks();
14
+ });
15
+ describe('command structure', () => {
16
+ it('should have correct description', () => {
17
+ expect(Init.description).toBeDefined();
18
+ expect(Init.description).toContain('scaffold');
19
+ });
20
+ it('should have correct examples', () => {
21
+ expect(Init.examples).toBeDefined();
22
+ expect(Array.isArray(Init.examples)).toBe(true);
23
+ expect(Init.examples.length).toBeGreaterThan(0);
24
+ });
25
+ it('should have an optional folderName argument', () => {
26
+ expect(Init.args).toBeDefined();
27
+ expect(Init.args.folderName).toBeDefined();
28
+ expect(Init.args.folderName.required).toBe(false);
29
+ });
30
+ });
31
+ describe('command execution', () => {
32
+ const createTestCommand = (args = [], flags = {}, parsedArgs = {}) => {
33
+ const cmd = new Init(args, {});
34
+ cmd.log = vi.fn();
35
+ cmd.warn = vi.fn();
36
+ cmd.error = vi.fn();
37
+ Object.defineProperty(cmd, 'parse', {
38
+ value: vi.fn().mockResolvedValue({
39
+ args: { folderName: undefined, ...parsedArgs },
40
+ flags: { 'skip-env': false, ...flags }
41
+ }),
42
+ configurable: true
43
+ });
44
+ return cmd;
45
+ };
46
+ it('should be instantiable', () => {
47
+ const cmd = createTestCommand();
48
+ expect(cmd).toBeInstanceOf(Init);
49
+ });
50
+ it('should have a run method', () => {
51
+ const cmd = createTestCommand();
52
+ expect(cmd.run).toBeDefined();
53
+ expect(typeof cmd.run).toBe('function');
54
+ });
55
+ it('should call runInit with skip-env flag and folder name', async () => {
56
+ const cmd = createTestCommand();
57
+ await cmd.run();
58
+ expect(projectScaffold.runInit).toHaveBeenCalledWith(false, undefined);
59
+ });
60
+ it('should handle UserCancelledError', async () => {
61
+ const error = new UserCancelledError();
62
+ vi.mocked(projectScaffold.runInit).mockRejectedValue(error);
63
+ const cmd = createTestCommand();
64
+ await cmd.run();
65
+ expect(cmd.log).toHaveBeenCalledWith('Init cancelled by user.');
66
+ expect(cmd.error).not.toHaveBeenCalled();
67
+ });
68
+ it('should handle other errors by passing error message', async () => {
69
+ // runInit now handles cleanup internally and throws Error with message
70
+ const testError = new Error('Failed to create project');
71
+ vi.mocked(projectScaffold.runInit).mockRejectedValue(testError);
72
+ const cmd = createTestCommand();
73
+ await cmd.run();
74
+ expect(cmd.error).toHaveBeenCalledWith('Failed to create project');
75
+ });
76
+ it('should pass error message to this.error', async () => {
77
+ const testError = new Error('Folder already exists');
78
+ vi.mocked(projectScaffold.runInit).mockRejectedValue(testError);
79
+ const cmd = createTestCommand();
80
+ await cmd.run();
81
+ expect(cmd.error).toHaveBeenCalledWith('Folder already exists');
82
+ });
83
+ it('should successfully complete project creation', async () => {
84
+ const cmd = createTestCommand();
85
+ await cmd.run();
86
+ expect(projectScaffold.runInit).toHaveBeenCalled();
87
+ expect(cmd.error).not.toHaveBeenCalled();
88
+ });
89
+ it('should pass skip-env flag to runInit when --skip-env is provided', async () => {
90
+ const cmd = createTestCommand([], { 'skip-env': true });
91
+ await cmd.run();
92
+ expect(projectScaffold.runInit).toHaveBeenCalledWith(true, undefined);
93
+ });
94
+ it('should pass folder name to runInit when provided', async () => {
95
+ const cmd = createTestCommand([], {}, { folderName: 'my-project' });
96
+ await cmd.run();
97
+ expect(projectScaffold.runInit).toHaveBeenCalledWith(false, 'my-project');
98
+ });
99
+ });
100
+ });
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Update extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ cli: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ agents: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ };
9
+ run(): Promise<void>;
10
+ private updateCli;
11
+ private updateAgents;
12
+ private handleGlobalUpdate;
13
+ private handleLocalUpdate;
14
+ }