@outputai/cli 0.1.4 → 0.1.5

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,74 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
3
+ import * as credentialsService from '#services/credentials_service.js';
4
+ import CredentialsGet from './get.js';
5
+ vi.mock('#services/credentials_service.js');
6
+ vi.mock('js-yaml', () => ({
7
+ load: vi.fn((yaml) => ({ anthropic: { api_key: yaml.includes('sk-test') ? 'sk-test' : undefined } }))
8
+ }));
9
+ vi.mock('@outputai/credentials', () => ({
10
+ getNestedValue: vi.fn((obj, path) => {
11
+ const parts = path.split('.');
12
+ return parts.reduce((acc, part) => acc?.[part], obj);
13
+ })
14
+ }));
15
+ describe('credentials get command', () => {
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ vi.mocked(credentialsService.credentialsExist).mockReturnValue(true);
19
+ vi.mocked(credentialsService.decryptCredentials).mockReturnValue('anthropic:\n api_key: sk-test\n');
20
+ });
21
+ afterEach(() => {
22
+ vi.restoreAllMocks();
23
+ });
24
+ const createTestCommand = (parsedArgs = {}, flags = {}) => {
25
+ const cmd = new CredentialsGet([], {});
26
+ cmd.log = vi.fn();
27
+ cmd.error = vi.fn((msg) => {
28
+ throw new Error(msg);
29
+ });
30
+ Object.defineProperty(cmd, 'parse', {
31
+ value: vi.fn().mockResolvedValue({
32
+ args: { path: 'anthropic.api_key', ...parsedArgs },
33
+ flags: { environment: undefined, workflow: undefined, ...flags }
34
+ }),
35
+ configurable: true
36
+ });
37
+ return cmd;
38
+ };
39
+ describe('command structure', () => {
40
+ it('should have correct description', () => {
41
+ expect(CredentialsGet.description).toContain('credential value');
42
+ });
43
+ it('should have a required path argument', () => {
44
+ expect(CredentialsGet.args.path).toBeDefined();
45
+ expect(CredentialsGet.args.path.required).toBe(true);
46
+ });
47
+ it('should have environment and workflow flags', () => {
48
+ expect(CredentialsGet.flags.environment).toBeDefined();
49
+ expect(CredentialsGet.flags.workflow).toBeDefined();
50
+ });
51
+ });
52
+ describe('command execution', () => {
53
+ it('should log the credential value', async () => {
54
+ const cmd = createTestCommand();
55
+ await cmd.run();
56
+ expect(credentialsService.decryptCredentials).toHaveBeenCalledWith(undefined, undefined);
57
+ expect(cmd.log).toHaveBeenCalledWith('sk-test');
58
+ });
59
+ it('should error when both environment and workflow are specified', async () => {
60
+ const cmd = createTestCommand({}, { environment: 'production', workflow: 'my_workflow' });
61
+ await expect(cmd.run()).rejects.toThrow('Cannot specify both');
62
+ });
63
+ it('should error when credentials file does not exist', async () => {
64
+ vi.mocked(credentialsService.credentialsExist).mockReturnValue(false);
65
+ vi.mocked(credentialsService.resolveCredentialsPath).mockReturnValue('/project/config/credentials.yml.enc');
66
+ const cmd = createTestCommand();
67
+ await expect(cmd.run()).rejects.toThrow('No credentials file found');
68
+ });
69
+ it('should error when credential path is not found', async () => {
70
+ const cmd = createTestCommand({ path: 'nonexistent.key' });
71
+ await expect(cmd.run()).rejects.toThrow('Credential not found');
72
+ });
73
+ });
74
+ });
@@ -0,0 +1,11 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class CredentialsInit extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ environment: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ workflow: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ };
10
+ run(): Promise<void>;
11
+ }
@@ -0,0 +1,45 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { initCredentials, resolveCredentialsPath, resolveKeyPath, credentialsExist } from '#services/credentials_service.js';
3
+ export default class CredentialsInit extends Command {
4
+ static description = 'Initialize encrypted credentials file and master key';
5
+ static examples = [
6
+ '<%= config.bin %> <%= command.id %>',
7
+ '<%= config.bin %> <%= command.id %> --environment production',
8
+ '<%= config.bin %> <%= command.id %> --workflow my_workflow'
9
+ ];
10
+ static flags = {
11
+ environment: Flags.string({
12
+ char: 'e',
13
+ description: 'Target environment (e.g. production, development)'
14
+ }),
15
+ workflow: Flags.string({
16
+ char: 'w',
17
+ description: 'Target a specific workflow directory'
18
+ }),
19
+ force: Flags.boolean({
20
+ char: 'f',
21
+ description: 'Overwrite existing credentials',
22
+ default: false
23
+ })
24
+ };
25
+ async run() {
26
+ const { flags } = await this.parse(CredentialsInit);
27
+ const environment = flags.environment;
28
+ const workflow = flags.workflow;
29
+ if (environment && workflow) {
30
+ this.error('Cannot specify both --environment and --workflow.');
31
+ }
32
+ if (!flags.force && credentialsExist(environment, workflow)) {
33
+ this.error(`Credentials already exist at ${resolveCredentialsPath(environment, workflow)}. Use --force to overwrite.`);
34
+ }
35
+ const { keyPath, credPath } = initCredentials(environment, workflow);
36
+ this.log('');
37
+ this.log(`Created key: ${keyPath}`);
38
+ this.log(`Created credentials: ${credPath}`);
39
+ this.log('');
40
+ this.log('IMPORTANT: Add the key file to .gitignore:');
41
+ this.log(` ${resolveKeyPath(environment, workflow)}`);
42
+ this.log('');
43
+ this.log('Edit credentials with: output credentials edit');
44
+ }
45
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,68 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
3
+ import * as credentialsService from '#services/credentials_service.js';
4
+ import CredentialsInit from './init.js';
5
+ vi.mock('#services/credentials_service.js');
6
+ describe('credentials init command', () => {
7
+ beforeEach(() => {
8
+ vi.clearAllMocks();
9
+ vi.mocked(credentialsService.credentialsExist).mockReturnValue(false);
10
+ vi.mocked(credentialsService.initCredentials).mockReturnValue({
11
+ keyPath: '/project/config/credentials.key',
12
+ credPath: '/project/config/credentials.yml.enc'
13
+ });
14
+ vi.mocked(credentialsService.resolveKeyPath).mockReturnValue('/project/config/credentials.key');
15
+ });
16
+ afterEach(() => {
17
+ vi.restoreAllMocks();
18
+ });
19
+ const createTestCommand = (flags = {}) => {
20
+ const cmd = new CredentialsInit([], {});
21
+ cmd.log = vi.fn();
22
+ cmd.error = vi.fn((msg) => {
23
+ throw new Error(msg);
24
+ });
25
+ Object.defineProperty(cmd, 'parse', {
26
+ value: vi.fn().mockResolvedValue({
27
+ args: {},
28
+ flags: { environment: undefined, workflow: undefined, force: false, ...flags }
29
+ }),
30
+ configurable: true
31
+ });
32
+ return cmd;
33
+ };
34
+ describe('command structure', () => {
35
+ it('should have correct description', () => {
36
+ expect(CredentialsInit.description).toContain('Initialize');
37
+ });
38
+ it('should have environment, workflow, and force flags', () => {
39
+ expect(CredentialsInit.flags.environment).toBeDefined();
40
+ expect(CredentialsInit.flags.workflow).toBeDefined();
41
+ expect(CredentialsInit.flags.force).toBeDefined();
42
+ });
43
+ });
44
+ describe('command execution', () => {
45
+ it('should call initCredentials and log results', async () => {
46
+ const cmd = createTestCommand();
47
+ await cmd.run();
48
+ expect(credentialsService.initCredentials).toHaveBeenCalledWith(undefined, undefined);
49
+ expect(cmd.log).toHaveBeenCalledWith(expect.stringContaining('credentials.key'));
50
+ });
51
+ it('should error when both environment and workflow are specified', async () => {
52
+ const cmd = createTestCommand({ environment: 'production', workflow: 'my_workflow' });
53
+ await expect(cmd.run()).rejects.toThrow('Cannot specify both');
54
+ });
55
+ it('should error when credentials already exist without --force', async () => {
56
+ vi.mocked(credentialsService.credentialsExist).mockReturnValue(true);
57
+ vi.mocked(credentialsService.resolveCredentialsPath).mockReturnValue('/project/config/credentials.yml.enc');
58
+ const cmd = createTestCommand();
59
+ await expect(cmd.run()).rejects.toThrow('already exist');
60
+ });
61
+ it('should proceed when credentials exist with --force', async () => {
62
+ vi.mocked(credentialsService.credentialsExist).mockReturnValue(true);
63
+ const cmd = createTestCommand({ force: true });
64
+ await cmd.run();
65
+ expect(credentialsService.initCredentials).toHaveBeenCalled();
66
+ });
67
+ });
68
+ });
@@ -0,0 +1,10 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class CredentialsShow extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ environment: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ workflow: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ };
9
+ run(): Promise<void>;
10
+ }
@@ -0,0 +1,33 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { decryptCredentials, credentialsExist, resolveCredentialsPath } from '#services/credentials_service.js';
3
+ export default class CredentialsShow extends Command {
4
+ static description = 'Show decrypted credentials (for debugging)';
5
+ static examples = [
6
+ '<%= config.bin %> <%= command.id %>',
7
+ '<%= config.bin %> <%= command.id %> --environment production',
8
+ '<%= config.bin %> <%= command.id %> --workflow my_workflow'
9
+ ];
10
+ static flags = {
11
+ environment: Flags.string({
12
+ char: 'e',
13
+ description: 'Target environment (e.g. production, development)'
14
+ }),
15
+ workflow: Flags.string({
16
+ char: 'w',
17
+ description: 'Target a specific workflow directory'
18
+ })
19
+ };
20
+ async run() {
21
+ const { flags } = await this.parse(CredentialsShow);
22
+ const environment = flags.environment;
23
+ const workflow = flags.workflow;
24
+ if (environment && workflow) {
25
+ this.error('Cannot specify both --environment and --workflow.');
26
+ }
27
+ if (!credentialsExist(environment, workflow)) {
28
+ this.error(`No credentials file found at ${resolveCredentialsPath(environment, workflow)}. Run "output credentials init" first.`);
29
+ }
30
+ const plaintext = decryptCredentials(environment, workflow);
31
+ this.log(plaintext);
32
+ }
33
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
3
+ import * as credentialsService from '#services/credentials_service.js';
4
+ import CredentialsShow from './show.js';
5
+ vi.mock('#services/credentials_service.js');
6
+ describe('credentials show command', () => {
7
+ beforeEach(() => {
8
+ vi.clearAllMocks();
9
+ vi.mocked(credentialsService.credentialsExist).mockReturnValue(true);
10
+ vi.mocked(credentialsService.decryptCredentials).mockReturnValue('anthropic:\n api_key: sk-test\n');
11
+ });
12
+ afterEach(() => {
13
+ vi.restoreAllMocks();
14
+ });
15
+ const createTestCommand = (flags = {}) => {
16
+ const cmd = new CredentialsShow([], {});
17
+ cmd.log = vi.fn();
18
+ cmd.error = vi.fn((msg) => {
19
+ throw new Error(msg);
20
+ });
21
+ Object.defineProperty(cmd, 'parse', {
22
+ value: vi.fn().mockResolvedValue({
23
+ args: {},
24
+ flags: { environment: undefined, workflow: undefined, ...flags }
25
+ }),
26
+ configurable: true
27
+ });
28
+ return cmd;
29
+ };
30
+ describe('command structure', () => {
31
+ it('should have correct description', () => {
32
+ expect(CredentialsShow.description).toContain('Show');
33
+ });
34
+ it('should have environment and workflow flags', () => {
35
+ expect(CredentialsShow.flags.environment).toBeDefined();
36
+ expect(CredentialsShow.flags.workflow).toBeDefined();
37
+ });
38
+ });
39
+ describe('command execution', () => {
40
+ it('should log decrypted credentials', async () => {
41
+ const cmd = createTestCommand();
42
+ await cmd.run();
43
+ expect(credentialsService.decryptCredentials).toHaveBeenCalledWith(undefined, undefined);
44
+ expect(cmd.log).toHaveBeenCalledWith('anthropic:\n api_key: sk-test\n');
45
+ });
46
+ it('should error when both environment and workflow are specified', async () => {
47
+ const cmd = createTestCommand({ environment: 'production', workflow: 'my_workflow' });
48
+ await expect(cmd.run()).rejects.toThrow('Cannot specify both');
49
+ });
50
+ it('should error when credentials file does not exist', async () => {
51
+ vi.mocked(credentialsService.credentialsExist).mockReturnValue(false);
52
+ vi.mocked(credentialsService.resolveCredentialsPath).mockReturnValue('/project/config/credentials.yml.enc');
53
+ const cmd = createTestCommand();
54
+ await expect(cmd.run()).rejects.toThrow('No credentials file found');
55
+ });
56
+ });
57
+ });
@@ -0,0 +1,11 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class DevEject extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {};
6
+ static flags: {
7
+ output: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ };
10
+ run(): Promise<void>;
11
+ }
@@ -0,0 +1,58 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { getDefaultDockerComposePath } from '#services/docker.js';
5
+ import { getErrorMessage } from '#utils/error_utils.js';
6
+ import { getEjectSuccessMessage } from '#services/messages.js';
7
+ export default class DevEject extends Command {
8
+ static description = 'Eject the Docker Compose configuration to your project root for customization';
9
+ static examples = [
10
+ '<%= config.bin %> <%= command.id %>',
11
+ '<%= config.bin %> <%= command.id %> --output ./custom-compose.yml'
12
+ ];
13
+ static args = {};
14
+ static flags = {
15
+ output: Flags.string({
16
+ description: 'Output path for the docker-compose file',
17
+ required: false,
18
+ char: 'o',
19
+ default: 'docker-compose.yml'
20
+ }),
21
+ force: Flags.boolean({
22
+ description: 'Overwrite existing file without prompting',
23
+ required: false,
24
+ char: 'f',
25
+ default: false
26
+ })
27
+ };
28
+ async run() {
29
+ const { flags } = await this.parse(DevEject);
30
+ // Source docker-compose file from assets
31
+ const sourcePath = getDefaultDockerComposePath();
32
+ // Destination path (relative to current working directory)
33
+ const destPath = path.resolve(process.cwd(), flags.output);
34
+ try {
35
+ // Check if source file exists
36
+ await fs.access(sourcePath);
37
+ }
38
+ catch {
39
+ this.error(`Docker Compose template not found at: ${sourcePath}`, { exit: 1 });
40
+ }
41
+ // Check if destination file already exists
42
+ const fileExists = await fs.access(destPath).then(() => true).catch(() => false);
43
+ if (fileExists && !flags.force) {
44
+ this.error(`File already exists at ${destPath}. Use --force to overwrite or specify a different output path with --output`, { exit: 1 });
45
+ }
46
+ try {
47
+ // Read the source file
48
+ const dockerComposeContent = await fs.readFile(sourcePath, 'utf-8');
49
+ // Write to destination
50
+ await fs.writeFile(destPath, dockerComposeContent, 'utf-8');
51
+ // Display the styled success message
52
+ this.log(getEjectSuccessMessage(destPath, flags.output, this.config.bin));
53
+ }
54
+ catch (error) {
55
+ this.error(`Failed to eject docker-compose configuration: ${getErrorMessage(error)}`, { exit: 1 });
56
+ }
57
+ }
58
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,109 @@
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 DevEject from './eject.js';
5
+ vi.mock('node:fs/promises');
6
+ describe('dev eject command', () => {
7
+ beforeEach(() => {
8
+ vi.clearAllMocks();
9
+ });
10
+ afterEach(() => {
11
+ vi.restoreAllMocks();
12
+ });
13
+ describe('command structure', () => {
14
+ it('should have correct description', () => {
15
+ expect(DevEject.description).toBeDefined();
16
+ expect(DevEject.description).toContain('Eject');
17
+ expect(DevEject.description).toContain('Docker Compose');
18
+ });
19
+ it('should have examples', () => {
20
+ expect(DevEject.examples).toBeDefined();
21
+ expect(Array.isArray(DevEject.examples)).toBe(true);
22
+ expect(DevEject.examples.length).toBeGreaterThan(0);
23
+ });
24
+ it('should have no required arguments', () => {
25
+ expect(DevEject.args).toBeDefined();
26
+ expect(Object.keys(DevEject.args)).toHaveLength(0);
27
+ });
28
+ it('should have output and force flags defined', () => {
29
+ expect(DevEject.flags).toBeDefined();
30
+ expect(DevEject.flags.output).toBeDefined();
31
+ expect(DevEject.flags.output.description).toContain('Output path');
32
+ expect(DevEject.flags.output.required).toBe(false);
33
+ expect(DevEject.flags.output.char).toBe('o');
34
+ expect(DevEject.flags.output.default).toBe('docker-compose.yml');
35
+ expect(DevEject.flags.force).toBeDefined();
36
+ expect(DevEject.flags.force.description).toContain('Overwrite');
37
+ expect(DevEject.flags.force.required).toBe(false);
38
+ expect(DevEject.flags.force.char).toBe('f');
39
+ expect(DevEject.flags.force.default).toBe(false);
40
+ });
41
+ });
42
+ describe('command instantiation', () => {
43
+ it('should be instantiable', () => {
44
+ const cmd = new DevEject([], {});
45
+ expect(cmd).toBeInstanceOf(DevEject);
46
+ });
47
+ it('should have a run method', () => {
48
+ const cmd = new DevEject([], {});
49
+ expect(cmd.run).toBeDefined();
50
+ expect(typeof cmd.run).toBe('function');
51
+ });
52
+ });
53
+ describe('file ejection', () => {
54
+ it('should eject docker-compose file to default location', async () => {
55
+ const config = {
56
+ runHook: vi.fn().mockResolvedValue({ failures: [], successes: [] })
57
+ };
58
+ const cmd = new DevEject([], config);
59
+ cmd.log = vi.fn();
60
+ cmd.error = vi.fn();
61
+ const mockDockerComposeContent = 'name: output-sdk\nservices:\n redis:\n image: redis:8-alpine';
62
+ // Mock source file exists and can be read
63
+ vi.mocked(fs.access).mockImplementation(path => {
64
+ if (path.toString().includes('assets/docker/docker-compose-dev.yml')) {
65
+ return Promise.resolve();
66
+ }
67
+ // Destination file doesn't exist
68
+ return Promise.reject(new Error('File not found'));
69
+ });
70
+ vi.mocked(fs.readFile).mockResolvedValue(mockDockerComposeContent);
71
+ vi.mocked(fs.writeFile).mockResolvedValue();
72
+ await cmd.run();
73
+ expect(fs.readFile).toHaveBeenCalled();
74
+ expect(fs.writeFile).toHaveBeenCalledWith(expect.stringContaining('docker-compose.yml'), mockDockerComposeContent, 'utf-8');
75
+ expect(cmd.log).toHaveBeenCalledWith(expect.stringContaining('SUCCESS!'));
76
+ });
77
+ it('should error if destination file exists and force flag is not set', async () => {
78
+ const config = {
79
+ runHook: vi.fn().mockResolvedValue({ failures: [], successes: [] })
80
+ };
81
+ const cmd = new DevEject([], config);
82
+ cmd.log = vi.fn();
83
+ cmd.error = vi.fn().mockImplementation(msg => {
84
+ throw new Error(msg);
85
+ });
86
+ // Mock both source and destination files exist
87
+ vi.mocked(fs.access).mockResolvedValue();
88
+ await expect(cmd.run()).rejects.toThrow('File already exists');
89
+ expect(cmd.error).toHaveBeenCalledWith(expect.stringContaining('File already exists'), { exit: 1 });
90
+ });
91
+ it('should overwrite file if force flag is set', async () => {
92
+ const config = {
93
+ runHook: vi.fn().mockResolvedValue({ failures: [], successes: [] })
94
+ };
95
+ const cmd = new DevEject(['--force'], config);
96
+ cmd.log = vi.fn();
97
+ cmd.error = vi.fn();
98
+ const mockDockerComposeContent = 'name: output-sdk\nservices:\n redis:\n image: redis:8-alpine';
99
+ // Mock both source and destination files exist
100
+ vi.mocked(fs.access).mockResolvedValue();
101
+ vi.mocked(fs.readFile).mockResolvedValue(mockDockerComposeContent);
102
+ vi.mocked(fs.writeFile).mockResolvedValue();
103
+ await cmd.run();
104
+ expect(fs.readFile).toHaveBeenCalled();
105
+ expect(fs.writeFile).toHaveBeenCalledWith(expect.stringContaining('docker-compose.yml'), mockDockerComposeContent, 'utf-8');
106
+ expect(cmd.log).toHaveBeenCalledWith(expect.stringContaining('SUCCESS!'));
107
+ });
108
+ });
109
+ });
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Dev extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {};
6
+ static flags: {
7
+ 'compose-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ 'image-pull-policy': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ detached: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ private dockerProcess;
12
+ run(): Promise<void>;
13
+ private pollServiceStatus;
14
+ }