@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,215 @@
1
+ /**
2
+ * Claude Agent SDK client for workflow planning
3
+ */
4
+ import { query } from '@anthropic-ai/claude-agent-sdk';
5
+ import { ux } from '@oclif/core';
6
+ import * as cliProgress from 'cli-progress';
7
+ import { getErrorMessage, toError } from '#utils/error_utils.js';
8
+ import { config } from '#config.js';
9
+ const ADDITIONAL_INSTRUCTIONS = `
10
+ ! IMPORTANT !
11
+ 1. Use TodoWrite to track your progress through plan creation.
12
+
13
+ 2. Please response with only the final version of the plan.
14
+
15
+ 3. Response in a markdown format with these metadata headers:
16
+
17
+ ---
18
+ title: <plan-title>
19
+ description: <plan-description>
20
+ date: <plan-date>
21
+ ---
22
+
23
+ <plan-content>
24
+
25
+ 4. After you mark all todos as complete, you must respond with the final version of the plan.
26
+ `;
27
+ const ADDITIONAL_INSTRUCTIONS_BUILD = `
28
+ ! IMPORTANT !
29
+ 1. Use TodoWrite to track your progress through workflow implementation.
30
+
31
+ 2. Follow the implementation plan exactly as specified in the plan file.
32
+
33
+ 3. Implement all workflow files following Output.ai patterns and best practices.
34
+
35
+ 4. After you mark all todos as complete, provide a summary of what was implemented.
36
+ `;
37
+ const PLAN_COMMAND = 'outputai:plan_workflow';
38
+ const BUILD_COMMAND = 'outputai:build_workflow';
39
+ const GLOBAL_CLAUDE_OPTIONS = {
40
+ settingSources: ['user', 'project', 'local']
41
+ };
42
+ export const PLAN_COMMAND_OPTIONS = {
43
+ allowedTools: ['Read', 'Grep', 'WebSearch', 'WebFetch', 'TodoWrite']
44
+ };
45
+ export const BUILD_COMMAND_OPTIONS = {
46
+ permissionMode: 'bypassPermissions'
47
+ };
48
+ export class ClaudeInvocationError extends Error {
49
+ cause;
50
+ constructor(message, cause) {
51
+ super(message);
52
+ this.cause = cause;
53
+ this.name = 'ClaudeInvocationError';
54
+ }
55
+ }
56
+ function validateEnvironment() {
57
+ if (!process.env.ANTHROPIC_API_KEY) {
58
+ throw new Error('ANTHROPIC_API_KEY environment variable is required.\n' +
59
+ '\n' +
60
+ 'Please set it using one of these methods:\n' +
61
+ '1. Add it to a .env file in your project root:\n' +
62
+ ' ANTHROPIC_API_KEY=your-api-key\n' +
63
+ '\n' +
64
+ '2. Export it in your shell:\n' +
65
+ ' export ANTHROPIC_API_KEY=your-api-key\n' +
66
+ '\n' +
67
+ '3. Set it when running the command:\n' +
68
+ ' ANTHROPIC_API_KEY=your-api-key output workflow plan\n' +
69
+ '\n' +
70
+ 'Get your API key from: https://console.anthropic.com/');
71
+ }
72
+ }
73
+ function validateSystem(systemMessage) {
74
+ const requiredCommands = [PLAN_COMMAND, BUILD_COMMAND];
75
+ const availableCommands = systemMessage.slash_commands;
76
+ const missingCommands = requiredCommands.filter(command => !availableCommands.includes(command));
77
+ return {
78
+ missingCommands,
79
+ hasIssues: missingCommands.length > 0
80
+ };
81
+ }
82
+ function displaySystemValidationWarnings(validation) {
83
+ if (!validation.hasIssues) {
84
+ return;
85
+ }
86
+ validation.missingCommands.forEach(command => {
87
+ ux.warn(`Missing required claude-code slash command: /${command}`);
88
+ });
89
+ ux.warn('Your claude-code agent is missing key configurations, it may not behave as expected.');
90
+ ux.warn('Please run "npx output update --agents" to fix this.');
91
+ }
92
+ function applyDefaultOptions(options) {
93
+ return {
94
+ ...GLOBAL_CLAUDE_OPTIONS,
95
+ ...options
96
+ };
97
+ }
98
+ function getTodoWriteMessage(message) {
99
+ if (message.type !== 'assistant') {
100
+ return null;
101
+ }
102
+ const todoWriteMessage = message.message.content.find((c) => c?.type === 'tool_use' && c.name === 'TodoWrite');
103
+ return todoWriteMessage ?? null;
104
+ }
105
+ function applyInstructions(initialMessage, instructionsType = 'plan') {
106
+ const instructions = instructionsType === 'build' ?
107
+ ADDITIONAL_INSTRUCTIONS_BUILD :
108
+ ADDITIONAL_INSTRUCTIONS;
109
+ return `${initialMessage}\n\n${instructions}`;
110
+ }
111
+ function createProgressBar() {
112
+ return new cliProgress.SingleBar({
113
+ format: '{bar} | {message} ({percentage}%)',
114
+ barCompleteChar: '█',
115
+ barIncompleteChar: '░',
116
+ hideCursor: true,
117
+ barsize: 40,
118
+ fps: 10,
119
+ stopOnComplete: false
120
+ });
121
+ }
122
+ function calculateProgress(completedCount, totalCount) {
123
+ if (totalCount === 0) {
124
+ return 0;
125
+ }
126
+ const percentage = ((completedCount + 1) / (totalCount + 1)) * 100;
127
+ return Math.round(percentage * 10) / 10;
128
+ }
129
+ function getProgressUpdate(message) {
130
+ const todoWriteMessage = getTodoWriteMessage(message);
131
+ if (!todoWriteMessage) {
132
+ return null;
133
+ }
134
+ const allTodos = todoWriteMessage.input.todos;
135
+ const inProgressTodo = allTodos.find(t => t.status === 'in_progress');
136
+ if (!inProgressTodo?.content) {
137
+ return null;
138
+ }
139
+ const completedTodos = allTodos.filter(t => t.status === 'completed');
140
+ return {
141
+ message: `${inProgressTodo.content}...`,
142
+ completed: completedTodos.length,
143
+ total: allTodos.length
144
+ };
145
+ }
146
+ function debugMessage(message) {
147
+ if (!config.debugMode) {
148
+ return;
149
+ }
150
+ ux.stdout(ux.colorize('teal', `[Message]: ${message.type}`));
151
+ ux.stdout(ux.colorize('teal', `[JSON]: ${JSON.stringify(message, null, 2)}`));
152
+ }
153
+ async function singleQuery(prompt, options = {}) {
154
+ validateEnvironment();
155
+ const progressBar = createProgressBar();
156
+ progressBar.start(100, 0, { message: 'Thinking...' });
157
+ try {
158
+ for await (const message of query({
159
+ prompt,
160
+ options: applyDefaultOptions(options)
161
+ })) {
162
+ debugMessage(message);
163
+ if (message.type === 'system' && message.subtype === 'init') {
164
+ const validation = validateSystem(message);
165
+ displaySystemValidationWarnings(validation);
166
+ progressBar.update(1, { message: 'Diving in...' });
167
+ }
168
+ const progressUpdate = getProgressUpdate(message);
169
+ if (progressUpdate) {
170
+ const percentage = calculateProgress(progressUpdate.completed, progressUpdate.total);
171
+ progressBar.update(percentage, { message: progressUpdate.message });
172
+ }
173
+ if (message.type === 'result' && message.subtype === 'success') {
174
+ progressBar.update(100, { message: 'Complete!' });
175
+ progressBar.stop();
176
+ return message.result;
177
+ }
178
+ }
179
+ throw new Error('No output received from claude-code');
180
+ }
181
+ catch (error) {
182
+ progressBar.stop();
183
+ throw new ClaudeInvocationError(`Failed to invoke claude-code: ${getErrorMessage(error)}`, toError(error));
184
+ }
185
+ }
186
+ export async function replyToClaude(message, options = {}) {
187
+ return singleQuery(applyInstructions(message), { continue: true, ...options });
188
+ }
189
+ /**
190
+ * Invoke claude-code with /outputai:plan_workflow slash command
191
+ * The SDK loads custom commands from .claude/commands/ when settingSources includes 'project'.
192
+ * ensureOutputAISystem() scaffolds the command files to that location.
193
+ * @param description - Workflow description
194
+ * @returns Plan output from claude-code
195
+ */
196
+ export async function invokePlanWorkflow(description) {
197
+ return singleQuery(applyInstructions(`/${PLAN_COMMAND} ${description}`), PLAN_COMMAND_OPTIONS);
198
+ }
199
+ /**
200
+ * Invoke claude-code with /outputai:build_workflow slash command
201
+ * The SDK loads custom commands from .claude/commands/ when settingSources includes 'project'.
202
+ * ensureOutputAISystem() scaffolds the command files to that location.
203
+ * @param planFilePath - Absolute path to the plan file
204
+ * @param workflowDir - Absolute path to the workflow directory
205
+ * @param workflowName - Name of the workflow
206
+ * @param additionalInstructions - Optional additional instructions
207
+ * @returns Implementation output from claude-code
208
+ */
209
+ export async function invokeBuildWorkflow(planFilePath, workflowDir, workflowName, additionalInstructions) {
210
+ const commandArgs = `${planFilePath} ${workflowName} ${workflowDir}`;
211
+ const fullCommand = additionalInstructions ?
212
+ `/${BUILD_COMMAND} ${commandArgs} ${additionalInstructions}` :
213
+ `/${BUILD_COMMAND} ${commandArgs}`;
214
+ return singleQuery(applyInstructions(fullCommand, 'build'), BUILD_COMMAND_OPTIONS);
215
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,145 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
2
+ import { invokePlanWorkflow, ClaudeInvocationError } from './claude_client.js';
3
+ import { isError } from '#utils/error_utils.js';
4
+ // Mock Claude SDK
5
+ vi.mock('@anthropic-ai/claude-agent-sdk', () => ({
6
+ query: vi.fn()
7
+ }));
8
+ describe('invokePlanWorkflow', () => {
9
+ beforeEach(() => {
10
+ vi.clearAllMocks();
11
+ });
12
+ afterEach(() => {
13
+ // Clean up environment variables
14
+ delete process.env.ANTHROPIC_API_KEY;
15
+ });
16
+ it('should invoke /outputai:plan_workflow slash command with settingSources', async () => {
17
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
18
+ process.env.ANTHROPIC_API_KEY = 'test-key';
19
+ async function* mockIterator() {
20
+ yield { type: 'result', subtype: 'success', result: '# Test Plan\n\nTest plan content' };
21
+ }
22
+ vi.mocked(query).mockReturnValue(mockIterator());
23
+ await invokePlanWorkflow('Test workflow');
24
+ const calls = vi.mocked(query).mock.calls;
25
+ expect(calls[0]?.[0]?.prompt).toContain('/outputai:plan_workflow Test workflow');
26
+ expect(calls[0]?.[0]?.options?.settingSources).toEqual(['user', 'project', 'local']);
27
+ expect(calls[0]?.[0]?.options?.allowedTools).toEqual(['Read', 'Grep', 'WebSearch', 'WebFetch', 'TodoWrite']);
28
+ });
29
+ it('should pass workflow description to slash command', async () => {
30
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
31
+ process.env.ANTHROPIC_API_KEY = 'test-key';
32
+ async function* mockIterator() {
33
+ yield { type: 'result', subtype: 'success', result: '# Plan\n\nContent' };
34
+ }
35
+ vi.mocked(query).mockReturnValue(mockIterator());
36
+ const description = 'Build a user authentication system';
37
+ await invokePlanWorkflow(description);
38
+ const calls = vi.mocked(query).mock.calls;
39
+ expect(calls[0]?.[0]?.prompt).toContain(`/outputai:plan_workflow ${description}`);
40
+ expect(calls[0]?.[0]?.options?.settingSources).toEqual(['user', 'project', 'local']);
41
+ });
42
+ it('should return plan output from claude-code', async () => {
43
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
44
+ process.env.ANTHROPIC_API_KEY = 'test-key';
45
+ const expectedOutput = '# Workflow Plan\n\nDetailed plan content here';
46
+ async function* mockIterator() {
47
+ yield { type: 'result', subtype: 'success', result: expectedOutput };
48
+ }
49
+ vi.mocked(query).mockReturnValue(mockIterator());
50
+ const result = await invokePlanWorkflow('Test');
51
+ expect(result).toBe(expectedOutput);
52
+ });
53
+ it('should throw error if API key is missing', async () => {
54
+ delete process.env.ANTHROPIC_API_KEY;
55
+ await expect(invokePlanWorkflow('Test'))
56
+ .rejects.toThrow('ANTHROPIC_API_KEY');
57
+ });
58
+ describe('error handling', () => {
59
+ it('should throw ClaudeInvocationError on API failures', async () => {
60
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
61
+ process.env.ANTHROPIC_API_KEY = 'test-key';
62
+ const apiError = new Error('API connection failed');
63
+ vi.mocked(query).mockRejectedValue(apiError);
64
+ await expect(invokePlanWorkflow('Test'))
65
+ .rejects.toThrow(ClaudeInvocationError);
66
+ });
67
+ it('should preserve original error in cause property', async () => {
68
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
69
+ process.env.ANTHROPIC_API_KEY = 'test-key';
70
+ const originalError = new Error('Network timeout');
71
+ // Mock async iterator that throws
72
+ async function* mockIterator() {
73
+ throw originalError;
74
+ }
75
+ vi.mocked(query).mockReturnValue(mockIterator());
76
+ try {
77
+ await invokePlanWorkflow('Test');
78
+ // If we get here, the test should fail
79
+ expect.fail('Should have thrown an error');
80
+ }
81
+ catch (error) {
82
+ expect(error).toBeInstanceOf(ClaudeInvocationError);
83
+ expect(error.cause).toBe(originalError);
84
+ }
85
+ });
86
+ it('should handle rate limit errors', async () => {
87
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
88
+ process.env.ANTHROPIC_API_KEY = 'test-key';
89
+ const rateLimitError = new Error('Rate limit exceeded');
90
+ rateLimitError.status = 429;
91
+ vi.mocked(query).mockRejectedValue(rateLimitError);
92
+ await expect(invokePlanWorkflow('Test'))
93
+ .rejects.toThrow(ClaudeInvocationError);
94
+ });
95
+ it('should handle authentication errors', async () => {
96
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
97
+ process.env.ANTHROPIC_API_KEY = 'invalid-key';
98
+ const authError = new Error('Invalid API key');
99
+ authError.status = 401;
100
+ vi.mocked(query).mockRejectedValue(authError);
101
+ await expect(invokePlanWorkflow('Test'))
102
+ .rejects.toThrow(ClaudeInvocationError);
103
+ });
104
+ it('should provide user-friendly error messages', async () => {
105
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
106
+ process.env.ANTHROPIC_API_KEY = 'test-key';
107
+ vi.mocked(query).mockRejectedValue(new Error('API error'));
108
+ try {
109
+ await invokePlanWorkflow('Test');
110
+ }
111
+ catch (error) {
112
+ expect(isError(error)).toBe(true);
113
+ if (isError(error)) {
114
+ expect(error.message).toMatch(/Failed to invoke/i);
115
+ }
116
+ }
117
+ });
118
+ it('should throw error when no result received', async () => {
119
+ const { query } = await import('@anthropic-ai/claude-agent-sdk');
120
+ process.env.ANTHROPIC_API_KEY = 'test-key';
121
+ // Mock iterator that yields no result messages
122
+ async function* mockIterator() {
123
+ yield { type: 'assistant', message: { content: [] } };
124
+ }
125
+ vi.mocked(query).mockReturnValue(mockIterator());
126
+ await expect(invokePlanWorkflow('Test'))
127
+ .rejects.toThrow(ClaudeInvocationError);
128
+ });
129
+ });
130
+ });
131
+ describe('ClaudeInvocationError', () => {
132
+ it('should be instance of Error', () => {
133
+ const error = new ClaudeInvocationError('test message');
134
+ expect(error).toBeInstanceOf(Error);
135
+ });
136
+ it('should have correct name property', () => {
137
+ const error = new ClaudeInvocationError('test message');
138
+ expect(error.name).toBe('ClaudeInvocationError');
139
+ });
140
+ it('should store cause error', () => {
141
+ const causeError = new Error('original error');
142
+ const error = new ClaudeInvocationError('test message', causeError);
143
+ expect(error.cause).toBe(causeError);
144
+ });
145
+ });
@@ -0,0 +1,36 @@
1
+ export interface StructureCheckResult {
2
+ isComplete: boolean;
3
+ needsInit: boolean;
4
+ }
5
+ export interface InitOptions {
6
+ projectRoot: string;
7
+ force: boolean;
8
+ }
9
+ interface EnsureClaudePluginOptions {
10
+ silent?: boolean;
11
+ }
12
+ export declare function checkAgentStructure(projectRoot: string): Promise<StructureCheckResult>;
13
+ /**
14
+ * Prepare template variables for file generation
15
+ */
16
+ export declare function prepareTemplateVariables(): Record<string, string>;
17
+ /**
18
+ * Ensure Claude Code plugin is configured
19
+ * Registers marketplace, updates it, and installs the plugin
20
+ */
21
+ export declare function ensureClaudePlugin(projectRoot: string, options?: EnsureClaudePluginOptions): Promise<void>;
22
+ /**
23
+ * Initialize agent configuration files and register Claude Code plugin
24
+ * Creates:
25
+ * - .claude/settings.json (static JSON)
26
+ * - CLAUDE.md (from template - user-customizable file)
27
+ * Then runs Claude CLI commands to register the plugin marketplace and install the plugin
28
+ */
29
+ export declare function initializeAgentConfig(options: InitOptions): Promise<void>;
30
+ /**
31
+ * Ensure OutputAI system is initialized
32
+ * Creates configuration files and registers Claude Code plugin
33
+ * @param projectRoot - Root directory of the project
34
+ */
35
+ export declare function ensureOutputAISystem(projectRoot: string): Promise<void>;
36
+ export {};
@@ -0,0 +1,236 @@
1
+ /**
2
+ * Coding agent configuration service
3
+ * Handles initialization and validation of agent configuration files
4
+ */
5
+ import fs from 'node:fs/promises';
6
+ import { access } from 'node:fs/promises';
7
+ import path from 'node:path';
8
+ import { join } from 'node:path';
9
+ import { ux } from '@oclif/core';
10
+ import { confirm } from '@inquirer/prompts';
11
+ import debugFactory from 'debug';
12
+ import { getTemplateDir } from '#utils/paths.js';
13
+ import { executeClaudeCommand } from '#utils/claude.js';
14
+ import { processTemplate } from '#utils/template.js';
15
+ import { ClaudePluginError, UserCancelledError } from '#types/errors.js';
16
+ const debug = debugFactory('output-cli:agent');
17
+ const EXPECTED_MARKETPLACE_REPO = 'growthxai/output';
18
+ function createLogger(silent) {
19
+ return silent ? debug : (msg) => ux.stdout(ux.colorize('gray', msg));
20
+ }
21
+ async function fileExists(filePath) {
22
+ try {
23
+ await access(filePath);
24
+ return true;
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ }
30
+ /**
31
+ * Validate settings.json content has correct marketplace and plugin configuration
32
+ */
33
+ async function validateSettingsJson(projectRoot) {
34
+ const settingsPath = join(projectRoot, '.claude/settings.json');
35
+ try {
36
+ const content = await fs.readFile(settingsPath, 'utf-8');
37
+ const settings = JSON.parse(content);
38
+ const marketplaceRepo = settings.extraKnownMarketplaces?.['team-tools']?.source?.repo;
39
+ const pluginEnabled = settings.enabledPlugins?.['outputai@outputai'];
40
+ return marketplaceRepo === EXPECTED_MARKETPLACE_REPO && pluginEnabled === true;
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ export async function checkAgentStructure(projectRoot) {
47
+ const settingsValid = await validateSettingsJson(projectRoot);
48
+ const claudeMdExists = await fileExists(join(projectRoot, 'CLAUDE.md'));
49
+ if (!settingsValid) {
50
+ ux.warn('.claude/settings.json missing critical configuration.');
51
+ }
52
+ if (!claudeMdExists) {
53
+ ux.warn('CLAUDE.md missing.');
54
+ }
55
+ const isComplete = settingsValid && claudeMdExists;
56
+ const needsInit = !settingsValid;
57
+ return { isComplete, needsInit };
58
+ }
59
+ /**
60
+ * Prepare template variables for file generation
61
+ */
62
+ export function prepareTemplateVariables() {
63
+ return {
64
+ date: new Date().toLocaleDateString('en-US', {
65
+ year: 'numeric',
66
+ month: 'long',
67
+ day: 'numeric'
68
+ })
69
+ };
70
+ }
71
+ /**
72
+ * Ensure a directory exists, creating it if necessary
73
+ */
74
+ async function ensureDirectoryExists(dir) {
75
+ try {
76
+ await fs.mkdir(dir, { recursive: true });
77
+ ux.stdout(ux.colorize('gray', `Created directory: ${dir}`));
78
+ }
79
+ catch (error) {
80
+ if (error.code !== 'EEXIST') {
81
+ throw error;
82
+ }
83
+ }
84
+ }
85
+ /**
86
+ * Create a file from a template
87
+ */
88
+ async function createFromTemplate(templateSubpath, output, variables) {
89
+ const templateDir = getTemplateDir('agent_instructions');
90
+ const templatePath = path.join(templateDir, templateSubpath);
91
+ const content = await fs.readFile(templatePath, 'utf-8');
92
+ const processed = processTemplate(content, variables);
93
+ await fs.writeFile(output, processed, 'utf-8');
94
+ ux.stdout(ux.colorize('gray', `Created from template: ${output}`));
95
+ }
96
+ /**
97
+ * Create a static file (no template processing)
98
+ */
99
+ async function createStaticFile(templateSubpath, output) {
100
+ const templateDir = getTemplateDir('agent_instructions');
101
+ const templatePath = path.join(templateDir, templateSubpath);
102
+ const content = await fs.readFile(templatePath, 'utf-8');
103
+ await fs.writeFile(output, content, 'utf-8');
104
+ ux.stdout(ux.colorize('gray', `Created file: ${output}`));
105
+ }
106
+ /**
107
+ * Create .claude/settings.json file from template
108
+ */
109
+ async function createSettingsFile(projectRoot, force) {
110
+ const claudeDir = join(projectRoot, '.claude');
111
+ await ensureDirectoryExists(claudeDir);
112
+ const settingsPath = join(claudeDir, 'settings.json');
113
+ if (force || !await fileExists(settingsPath)) {
114
+ await createStaticFile('dotclaude/settings.json.template', settingsPath);
115
+ }
116
+ else {
117
+ ux.warn('File already exists: .claude/settings.json (use --force to overwrite)');
118
+ }
119
+ }
120
+ /**
121
+ * Create CLAUDE.md file from template
122
+ */
123
+ async function createClaudeMdFile(projectRoot, force, variables) {
124
+ const claudeMdPath = join(projectRoot, 'CLAUDE.md');
125
+ if (force || !await fileExists(claudeMdPath)) {
126
+ await createFromTemplate('CLAUDE.md.template', claudeMdPath, variables);
127
+ }
128
+ else {
129
+ ux.warn('File already exists: CLAUDE.md (use --force to overwrite)');
130
+ }
131
+ }
132
+ /**
133
+ * Handle Claude plugin command errors with user confirmation to proceed
134
+ * @param error - The error that occurred
135
+ * @param commandName - Name of the command that failed
136
+ * @param silent - If true, log to debug and re-throw without user confirmation
137
+ * @throws UserCancelledError if user declines to proceed or presses Ctrl+C
138
+ */
139
+ async function handlePluginError(error, commandName, silent = false) {
140
+ const pluginError = new ClaudePluginError(commandName, error instanceof Error ? error : undefined);
141
+ if (silent) {
142
+ debug('Plugin error: %s', pluginError.message);
143
+ throw error;
144
+ }
145
+ ux.warn(pluginError.message);
146
+ try {
147
+ const shouldProceed = await confirm({
148
+ message: 'Claude plugin setup failed.\n\nThis means your project will be without Output.ai-specific commands, skills, and subagents.' +
149
+ ' You will not be able to use our AI-assisted workflow planning and building functionality.\n\n' +
150
+ 'Would you like to proceed without the Claude plugin setup?',
151
+ default: false
152
+ });
153
+ if (!shouldProceed) {
154
+ throw new UserCancelledError();
155
+ }
156
+ }
157
+ catch (promptError) {
158
+ if (promptError instanceof UserCancelledError) {
159
+ throw promptError;
160
+ }
161
+ // Ctrl+C throws ExitPromptError - convert to UserCancelledError
162
+ throw new UserCancelledError();
163
+ }
164
+ }
165
+ /**
166
+ * Register and update the OutputAI plugin marketplace
167
+ */
168
+ async function registerPluginMarketplace(projectRoot, silent = false) {
169
+ const log = createLogger(silent);
170
+ log('Registering plugin marketplace...');
171
+ try {
172
+ await executeClaudeCommand(['plugin', 'marketplace', 'add', 'growthxai/output'], projectRoot, { ignoreFailure: true });
173
+ }
174
+ catch (error) {
175
+ await handlePluginError(error, 'plugin marketplace add', silent);
176
+ return;
177
+ }
178
+ log('Updating plugin marketplace...');
179
+ try {
180
+ await executeClaudeCommand(['plugin', 'marketplace', 'update', 'outputai'], projectRoot);
181
+ }
182
+ catch (error) {
183
+ await handlePluginError(error, 'plugin marketplace update outputai', silent);
184
+ }
185
+ }
186
+ /**
187
+ * Install the OutputAI plugin
188
+ */
189
+ async function installOutputAIPlugin(projectRoot, silent = false) {
190
+ const log = createLogger(silent);
191
+ log('Installing OutputAI plugin...');
192
+ try {
193
+ await executeClaudeCommand(['plugin', 'install', 'outputai@outputai', '--scope', 'project'], projectRoot);
194
+ }
195
+ catch (error) {
196
+ await handlePluginError(error, 'plugin install outputai@outputai', silent);
197
+ }
198
+ }
199
+ /**
200
+ * Ensure Claude Code plugin is configured
201
+ * Registers marketplace, updates it, and installs the plugin
202
+ */
203
+ export async function ensureClaudePlugin(projectRoot, options = {}) {
204
+ const { silent = false } = options;
205
+ await registerPluginMarketplace(projectRoot, silent);
206
+ await installOutputAIPlugin(projectRoot, silent);
207
+ }
208
+ /**
209
+ * Initialize agent configuration files and register Claude Code plugin
210
+ * Creates:
211
+ * - .claude/settings.json (static JSON)
212
+ * - CLAUDE.md (from template - user-customizable file)
213
+ * Then runs Claude CLI commands to register the plugin marketplace and install the plugin
214
+ */
215
+ export async function initializeAgentConfig(options) {
216
+ const { projectRoot, force } = options;
217
+ const variables = prepareTemplateVariables();
218
+ await createSettingsFile(projectRoot, force);
219
+ await createClaudeMdFile(projectRoot, force, variables);
220
+ await ensureClaudePlugin(projectRoot);
221
+ }
222
+ /**
223
+ * Ensure OutputAI system is initialized
224
+ * Creates configuration files and registers Claude Code plugin
225
+ * @param projectRoot - Root directory of the project
226
+ */
227
+ export async function ensureOutputAISystem(projectRoot) {
228
+ const { isComplete, needsInit } = await checkAgentStructure(projectRoot);
229
+ if (isComplete) {
230
+ return;
231
+ }
232
+ if (needsInit) {
233
+ ux.warn('Agent configuration is incomplete. Initializing...');
234
+ await initializeAgentConfig({ projectRoot, force: false });
235
+ }
236
+ }
@@ -0,0 +1 @@
1
+ export {};