@fractary/faber-cli 1.0.0 → 1.2.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.
- package/dist/commands/init.js +1 -1
- package/dist/commands/plan/index.d.ts +11 -0
- package/dist/commands/plan/index.d.ts.map +1 -0
- package/dist/commands/plan/index.js +383 -0
- package/dist/commands/workflow/index.d.ts +7 -7
- package/dist/commands/workflow/index.js +17 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +90 -8
- package/dist/lib/anthropic-client.d.ts +69 -0
- package/dist/lib/anthropic-client.d.ts.map +1 -0
- package/dist/lib/anthropic-client.js +225 -0
- package/dist/lib/config.d.ts +30 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +127 -0
- package/dist/lib/repo-client.d.ts +81 -0
- package/dist/lib/repo-client.d.ts.map +1 -0
- package/dist/lib/repo-client.js +105 -0
- package/dist/types/config.d.ts +34 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +6 -0
- package/dist/utils/prompt.d.ts +8 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +19 -0
- package/dist/utils/validation.d.ts +71 -0
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +157 -0
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -6,7 +6,7 @@ import * as fs from 'fs/promises';
|
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
export function createInitCommand() {
|
|
9
|
-
return new Command('init')
|
|
9
|
+
return new Command('workflow-init')
|
|
10
10
|
.description('Initialize a new FABER project')
|
|
11
11
|
.option('--preset <name>', 'Use a preset configuration', 'default')
|
|
12
12
|
.option('--force', 'Overwrite existing configuration')
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan command - FABER CLI planning command
|
|
3
|
+
*
|
|
4
|
+
* Batch workflow planning for GitHub issues
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
/**
|
|
8
|
+
* Create the plan command
|
|
9
|
+
*/
|
|
10
|
+
export declare function createPlanCommand(): Command;
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/plan/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+CpC;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAkB3C"}
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan command - FABER CLI planning command
|
|
3
|
+
*
|
|
4
|
+
* Batch workflow planning for GitHub issues
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { AnthropicClient } from '../../lib/anthropic-client.js';
|
|
9
|
+
import { RepoClient } from '../../lib/repo-client.js';
|
|
10
|
+
import { ConfigManager } from '../../lib/config.js';
|
|
11
|
+
import { prompt } from '../../utils/prompt.js';
|
|
12
|
+
import { validateWorkIds, validateLabels, validateWorkflowName, validatePlanId, } from '../../utils/validation.js';
|
|
13
|
+
import fs from 'fs/promises';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
/**
|
|
16
|
+
* Create the plan command
|
|
17
|
+
*/
|
|
18
|
+
export function createPlanCommand() {
|
|
19
|
+
return new Command('plan')
|
|
20
|
+
.description('Plan workflows for GitHub issues')
|
|
21
|
+
.option('--work-id <ids>', 'Comma-separated list of work item IDs (e.g., "258,259,260")')
|
|
22
|
+
.option('--work-label <labels>', 'Comma-separated label filters (e.g., "workflow:etl,status:approved")')
|
|
23
|
+
.option('--workflow <name>', 'Override workflow (default: read from issue "workflow:*" label)')
|
|
24
|
+
.option('--no-worktree', 'Skip worktree creation')
|
|
25
|
+
.option('--no-branch', 'Skip branch creation')
|
|
26
|
+
.option('--skip-confirm', 'Skip confirmation prompt (use with caution)')
|
|
27
|
+
.option('--output <format>', 'Output format: text|json|yaml', 'text')
|
|
28
|
+
.option('--json', 'Output as JSON (shorthand for --output json)')
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
try {
|
|
31
|
+
await executePlanCommand(options);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
handlePlanError(error, options);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Main execution logic for plan command
|
|
40
|
+
*/
|
|
41
|
+
async function executePlanCommand(options) {
|
|
42
|
+
const outputFormat = options.json ? 'json' : options.output || 'text';
|
|
43
|
+
// Validate arguments
|
|
44
|
+
if (!options.workId && !options.workLabel) {
|
|
45
|
+
throw new Error('Either --work-id or --work-label must be provided');
|
|
46
|
+
}
|
|
47
|
+
if (options.workId && options.workLabel) {
|
|
48
|
+
throw new Error('Cannot use both --work-id and --work-label at the same time');
|
|
49
|
+
}
|
|
50
|
+
// Initialize clients
|
|
51
|
+
const config = await ConfigManager.load();
|
|
52
|
+
const repoClient = new RepoClient(config);
|
|
53
|
+
const anthropicClient = new AnthropicClient(config);
|
|
54
|
+
if (outputFormat === 'text') {
|
|
55
|
+
console.log(chalk.blue('FABER CLI - Workflow Planning'));
|
|
56
|
+
console.log(chalk.gray('═'.repeat(50)));
|
|
57
|
+
}
|
|
58
|
+
// Step 1: Fetch issues from GitHub
|
|
59
|
+
if (outputFormat === 'text') {
|
|
60
|
+
console.log(chalk.cyan('\n→ Fetching issues from GitHub...'));
|
|
61
|
+
}
|
|
62
|
+
let issues;
|
|
63
|
+
try {
|
|
64
|
+
if (options.workId) {
|
|
65
|
+
// Validate work IDs before fetching
|
|
66
|
+
const ids = validateWorkIds(options.workId);
|
|
67
|
+
issues = await repoClient.fetchIssues(ids);
|
|
68
|
+
if (outputFormat === 'text') {
|
|
69
|
+
console.log(chalk.green(` ✓ Fetched ${issues.length} issue(s) by ID`));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (options.workLabel) {
|
|
73
|
+
// Validate labels before searching
|
|
74
|
+
const labels = validateLabels(options.workLabel);
|
|
75
|
+
issues = await repoClient.searchIssues(labels);
|
|
76
|
+
if (outputFormat === 'text') {
|
|
77
|
+
console.log(chalk.green(` ✓ Found ${issues.length} issue(s) matching labels`));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
throw new Error('No issues to process');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error instanceof Error && error.message.includes('not yet implemented')) {
|
|
86
|
+
if (outputFormat === 'text') {
|
|
87
|
+
console.log(chalk.yellow('\n⚠️ fractary-repo commands not yet available'));
|
|
88
|
+
console.log(chalk.gray(' This command requires fractary-repo plugin implementation.'));
|
|
89
|
+
console.log(chalk.gray(' See SPEC-00030-FRACTARY-REPO-ENHANCEMENTS.md'));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
console.log(JSON.stringify({
|
|
93
|
+
status: 'error',
|
|
94
|
+
error: {
|
|
95
|
+
code: 'DEPENDENCY_NOT_AVAILABLE',
|
|
96
|
+
message: 'fractary-repo commands not yet implemented',
|
|
97
|
+
details: 'See SPEC-00030-FRACTARY-REPO-ENHANCEMENTS.md',
|
|
98
|
+
},
|
|
99
|
+
}, null, 2));
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
if (issues.length === 0) {
|
|
106
|
+
if (outputFormat === 'text') {
|
|
107
|
+
console.log(chalk.yellow('\n⚠️ No issues found'));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
console.log(JSON.stringify({ status: 'success', issues: [], message: 'No issues found' }, null, 2));
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
// Step 2: Extract workflows from labels or prompt user
|
|
115
|
+
if (outputFormat === 'text') {
|
|
116
|
+
console.log(chalk.cyan('\n→ Identifying workflows...'));
|
|
117
|
+
}
|
|
118
|
+
const availableWorkflows = await loadAvailableWorkflows(config);
|
|
119
|
+
const issuesWithWorkflows = await assignWorkflows(issues, availableWorkflows, options, outputFormat);
|
|
120
|
+
// Step 3: Show confirmation prompt
|
|
121
|
+
if (!options.skipConfirm) {
|
|
122
|
+
const confirmed = await showConfirmationPrompt(issuesWithWorkflows, config, outputFormat);
|
|
123
|
+
if (!confirmed) {
|
|
124
|
+
if (outputFormat === 'text') {
|
|
125
|
+
console.log(chalk.yellow('\n✖ Planning cancelled'));
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
console.log(JSON.stringify({ status: 'cancelled', message: 'User cancelled planning' }, null, 2));
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Step 4: Plan each issue
|
|
134
|
+
if (outputFormat === 'text') {
|
|
135
|
+
console.log(chalk.cyan('\n→ Planning workflows...'));
|
|
136
|
+
}
|
|
137
|
+
const results = [];
|
|
138
|
+
for (const issue of issuesWithWorkflows) {
|
|
139
|
+
if (outputFormat === 'text') {
|
|
140
|
+
console.log(chalk.gray(`\n[${results.length + 1}/${issuesWithWorkflows.length}] Issue #${issue.number}: ${issue.title}`));
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const result = await planSingleIssue(issue, config, repoClient, anthropicClient, options, outputFormat);
|
|
144
|
+
results.push(result);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
148
|
+
if (outputFormat === 'text') {
|
|
149
|
+
console.log(chalk.red(` ✗ Error: ${errorMessage}`));
|
|
150
|
+
}
|
|
151
|
+
results.push({
|
|
152
|
+
issue,
|
|
153
|
+
planId: '',
|
|
154
|
+
branch: '',
|
|
155
|
+
worktree: '',
|
|
156
|
+
error: errorMessage,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Step 5: Output summary
|
|
161
|
+
if (outputFormat === 'json') {
|
|
162
|
+
console.log(JSON.stringify({
|
|
163
|
+
status: 'success',
|
|
164
|
+
total: results.length,
|
|
165
|
+
successful: results.filter(r => !r.error).length,
|
|
166
|
+
failed: results.filter(r => r.error).length,
|
|
167
|
+
results,
|
|
168
|
+
}, null, 2));
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
outputTextSummary(results);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Load available workflow configurations
|
|
176
|
+
*/
|
|
177
|
+
async function loadAvailableWorkflows(config) {
|
|
178
|
+
const workflowDir = config.workflow?.config_path || './plugins/faber/config/workflows';
|
|
179
|
+
try {
|
|
180
|
+
const files = await fs.readdir(workflowDir);
|
|
181
|
+
return files
|
|
182
|
+
.filter(f => f.endsWith('.json'))
|
|
183
|
+
.map(f => path.basename(f, '.json'));
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
// Default workflows if directory doesn't exist
|
|
187
|
+
return ['core', 'etl', 'bugfix', 'feature'];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Assign workflows to issues (extract from labels or prompt user)
|
|
192
|
+
*/
|
|
193
|
+
async function assignWorkflows(issues, availableWorkflows, options, outputFormat) {
|
|
194
|
+
const issuesWithWorkflows = [];
|
|
195
|
+
for (const issue of issues) {
|
|
196
|
+
let workflow = options.workflow; // Command-line override
|
|
197
|
+
// Validate workflow override if provided
|
|
198
|
+
if (workflow) {
|
|
199
|
+
validateWorkflowName(workflow);
|
|
200
|
+
}
|
|
201
|
+
if (!workflow) {
|
|
202
|
+
// Extract from issue labels
|
|
203
|
+
const workflowLabel = issue.labels.find(label => label.startsWith('workflow:'));
|
|
204
|
+
if (workflowLabel) {
|
|
205
|
+
workflow = workflowLabel.replace('workflow:', '');
|
|
206
|
+
// Validate extracted workflow name
|
|
207
|
+
validateWorkflowName(workflow);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (!workflow) {
|
|
211
|
+
// Prompt user
|
|
212
|
+
if (outputFormat === 'text') {
|
|
213
|
+
console.log(chalk.yellow(`\n⚠️ Issue #${issue.number} is missing a workflow label:`));
|
|
214
|
+
console.log(chalk.gray(` ${issue.title}`));
|
|
215
|
+
console.log(chalk.gray(` Available workflows: ${availableWorkflows.join(', ')}`));
|
|
216
|
+
workflow = await prompt(` Select workflow for this issue [${availableWorkflows[0]}]: `);
|
|
217
|
+
if (!workflow) {
|
|
218
|
+
workflow = availableWorkflows[0];
|
|
219
|
+
}
|
|
220
|
+
if (!availableWorkflows.includes(workflow)) {
|
|
221
|
+
throw new Error(`Invalid workflow: ${workflow}. Available: ${availableWorkflows.join(', ')}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
throw new Error(`Issue #${issue.number} is missing workflow label and interactive prompts are disabled in JSON mode`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
issuesWithWorkflows.push({ ...issue, workflow });
|
|
229
|
+
}
|
|
230
|
+
if (outputFormat === 'text') {
|
|
231
|
+
console.log(chalk.green(` ✓ All issues have workflows assigned`));
|
|
232
|
+
}
|
|
233
|
+
return issuesWithWorkflows;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Show confirmation prompt before planning
|
|
237
|
+
*/
|
|
238
|
+
async function showConfirmationPrompt(issues, config, outputFormat) {
|
|
239
|
+
if (outputFormat !== 'text') {
|
|
240
|
+
return true; // Skip in JSON mode
|
|
241
|
+
}
|
|
242
|
+
console.log(chalk.cyan('\n📋 Will plan workflows for the following issues:\n'));
|
|
243
|
+
for (const issue of issues) {
|
|
244
|
+
const { organization, project } = getRepoInfoFromConfig(config);
|
|
245
|
+
const branch = `feature/${issue.number}`;
|
|
246
|
+
const worktree = `~/.claude-worktrees/${organization}-${project}-${issue.number}`;
|
|
247
|
+
console.log(chalk.bold(`#${issue.number}: ${issue.title}`));
|
|
248
|
+
console.log(chalk.gray(` Workflow: ${issue.workflow}`));
|
|
249
|
+
console.log(chalk.gray(` Branch: ${branch}`));
|
|
250
|
+
console.log(chalk.gray(` Worktree: ${worktree}`));
|
|
251
|
+
console.log();
|
|
252
|
+
}
|
|
253
|
+
const response = await prompt('Proceed? [Y/n]: ');
|
|
254
|
+
return !response || response.toLowerCase() === 'y' || response.toLowerCase() === 'yes';
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Plan a single issue
|
|
258
|
+
*/
|
|
259
|
+
async function planSingleIssue(issue, config, repoClient, anthropicClient, options, outputFormat) {
|
|
260
|
+
const { organization, project } = getRepoInfoFromConfig(config);
|
|
261
|
+
const branch = `feature/${issue.number}`;
|
|
262
|
+
const worktree = `~/.claude-worktrees/${organization}-${project}-${issue.number}`;
|
|
263
|
+
// Generate plan via Anthropic API
|
|
264
|
+
if (outputFormat === 'text') {
|
|
265
|
+
console.log(chalk.gray(' → Generating plan...'));
|
|
266
|
+
}
|
|
267
|
+
const plan = await anthropicClient.generatePlan({
|
|
268
|
+
workflow: issue.workflow,
|
|
269
|
+
issueTitle: issue.title,
|
|
270
|
+
issueDescription: issue.description,
|
|
271
|
+
issueNumber: issue.number,
|
|
272
|
+
});
|
|
273
|
+
const planId = plan.plan_id;
|
|
274
|
+
// Create branch
|
|
275
|
+
if (!options.noBranch) {
|
|
276
|
+
if (outputFormat === 'text') {
|
|
277
|
+
console.log(chalk.gray(` → Creating branch: ${branch}...`));
|
|
278
|
+
}
|
|
279
|
+
await repoClient.createBranch(branch);
|
|
280
|
+
}
|
|
281
|
+
// Create worktree
|
|
282
|
+
let worktreePath = worktree;
|
|
283
|
+
if (!options.noWorktree) {
|
|
284
|
+
if (outputFormat === 'text') {
|
|
285
|
+
console.log(chalk.gray(` → Creating worktree: ${worktree}...`));
|
|
286
|
+
}
|
|
287
|
+
const worktreeResult = await repoClient.createWorktree({
|
|
288
|
+
workId: issue.number.toString(),
|
|
289
|
+
path: worktree,
|
|
290
|
+
});
|
|
291
|
+
worktreePath = worktreeResult.absolute_path;
|
|
292
|
+
}
|
|
293
|
+
// Write plan to worktree
|
|
294
|
+
if (!options.noWorktree) {
|
|
295
|
+
// Validate plan ID format (prevent path traversal via malicious plan IDs)
|
|
296
|
+
validatePlanId(planId);
|
|
297
|
+
const planDir = path.join(worktreePath, '.fractary', 'plans');
|
|
298
|
+
await fs.mkdir(planDir, { recursive: true });
|
|
299
|
+
// Construct and validate path
|
|
300
|
+
const planPath = path.join(planDir, `${planId}.json`);
|
|
301
|
+
// Note: path.join automatically normalizes and prevents basic traversal,
|
|
302
|
+
// but we validate the plan ID format as an additional layer of defense
|
|
303
|
+
await fs.writeFile(planPath, JSON.stringify(plan, null, 2));
|
|
304
|
+
if (outputFormat === 'text') {
|
|
305
|
+
console.log(chalk.gray(` → Plan written to ${planPath}`));
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// Update GitHub issue with plan_id
|
|
309
|
+
if (outputFormat === 'text') {
|
|
310
|
+
console.log(chalk.gray(` → Updating GitHub issue...`));
|
|
311
|
+
}
|
|
312
|
+
await repoClient.updateIssue({
|
|
313
|
+
id: issue.number.toString(),
|
|
314
|
+
comment: `🤖 Workflow plan created: ${planId}`,
|
|
315
|
+
addLabel: 'faber:planned',
|
|
316
|
+
});
|
|
317
|
+
if (outputFormat === 'text') {
|
|
318
|
+
console.log(chalk.green(` ✓ Plan: ${planId}`));
|
|
319
|
+
}
|
|
320
|
+
return {
|
|
321
|
+
issue,
|
|
322
|
+
planId,
|
|
323
|
+
branch,
|
|
324
|
+
worktree: worktreePath,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Get repository info from config
|
|
329
|
+
*/
|
|
330
|
+
function getRepoInfoFromConfig(config) {
|
|
331
|
+
return {
|
|
332
|
+
organization: config.github?.organization || 'unknown',
|
|
333
|
+
project: config.github?.project || 'unknown',
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Output text summary
|
|
338
|
+
*/
|
|
339
|
+
function outputTextSummary(results) {
|
|
340
|
+
console.log(chalk.cyan('\n' + '═'.repeat(50)));
|
|
341
|
+
const successful = results.filter(r => !r.error);
|
|
342
|
+
const failed = results.filter(r => r.error);
|
|
343
|
+
if (successful.length > 0) {
|
|
344
|
+
console.log(chalk.green(`\n✓ Planned ${successful.length} workflow(s) successfully:\n`));
|
|
345
|
+
successful.forEach((result, index) => {
|
|
346
|
+
console.log(chalk.bold(`[${index + 1}/${successful.length}] Issue #${result.issue.number}: ${result.issue.title}`));
|
|
347
|
+
console.log(chalk.gray(` Workflow: ${result.issue.workflow}`));
|
|
348
|
+
console.log(chalk.gray(` Plan: ${result.planId}`));
|
|
349
|
+
console.log(chalk.gray(` Branch: ${result.branch}`));
|
|
350
|
+
console.log(chalk.gray(` Worktree: ${result.worktree}`));
|
|
351
|
+
console.log();
|
|
352
|
+
console.log(chalk.cyan(' To execute:'));
|
|
353
|
+
console.log(chalk.gray(` cd ${result.worktree} && claude`));
|
|
354
|
+
console.log(chalk.gray(` /fractary-faber:workflow-run ${result.issue.number}`));
|
|
355
|
+
console.log();
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
if (failed.length > 0) {
|
|
359
|
+
console.log(chalk.red(`\n✗ Failed to plan ${failed.length} workflow(s):\n`));
|
|
360
|
+
failed.forEach((result, index) => {
|
|
361
|
+
console.log(chalk.bold(`[${index + 1}/${failed.length}] Issue #${result.issue.number}: ${result.issue.title}`));
|
|
362
|
+
console.log(chalk.red(` Error: ${result.error}`));
|
|
363
|
+
console.log();
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Error handling
|
|
369
|
+
*/
|
|
370
|
+
function handlePlanError(error, options) {
|
|
371
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
372
|
+
const outputFormat = options.json ? 'json' : options.output || 'text';
|
|
373
|
+
if (outputFormat === 'json') {
|
|
374
|
+
console.error(JSON.stringify({
|
|
375
|
+
status: 'error',
|
|
376
|
+
error: { code: 'PLAN_ERROR', message },
|
|
377
|
+
}));
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
console.error(chalk.red('Error:'), message);
|
|
381
|
+
}
|
|
382
|
+
process.exit(1);
|
|
383
|
+
}
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow commands - FABER workflow execution
|
|
3
3
|
*
|
|
4
|
-
* Provides run, status, resume, pause commands via FaberWorkflow SDK.
|
|
4
|
+
* Provides workflow-run, workflow-status, workflow-resume, workflow-pause commands via FaberWorkflow SDK.
|
|
5
5
|
*/
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
/**
|
|
8
|
-
* Create the run command
|
|
8
|
+
* Create the workflow-run command
|
|
9
9
|
*/
|
|
10
10
|
export declare function createRunCommand(): Command;
|
|
11
11
|
/**
|
|
12
|
-
* Create the status command
|
|
12
|
+
* Create the workflow-status command
|
|
13
13
|
*/
|
|
14
14
|
export declare function createStatusCommand(): Command;
|
|
15
15
|
/**
|
|
16
|
-
* Create the resume command
|
|
16
|
+
* Create the workflow-resume command
|
|
17
17
|
*/
|
|
18
18
|
export declare function createResumeCommand(): Command;
|
|
19
19
|
/**
|
|
20
|
-
* Create the pause command
|
|
20
|
+
* Create the workflow-pause command
|
|
21
21
|
*/
|
|
22
22
|
export declare function createPauseCommand(): Command;
|
|
23
23
|
/**
|
|
24
|
-
* Create the recover command
|
|
24
|
+
* Create the workflow-recover command
|
|
25
25
|
*/
|
|
26
26
|
export declare function createRecoverCommand(): Command;
|
|
27
27
|
/**
|
|
28
|
-
* Create the cleanup command
|
|
28
|
+
* Create the workflow-cleanup command
|
|
29
29
|
*/
|
|
30
30
|
export declare function createCleanupCommand(): Command;
|
|
31
31
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Workflow commands - FABER workflow execution
|
|
3
3
|
*
|
|
4
|
-
* Provides run, status, resume, pause commands via FaberWorkflow SDK.
|
|
4
|
+
* Provides workflow-run, workflow-status, workflow-resume, workflow-pause commands via FaberWorkflow SDK.
|
|
5
5
|
*/
|
|
6
6
|
import { Command } from 'commander';
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { FaberWorkflow, StateManager } from '@fractary/faber';
|
|
9
9
|
import { parsePositiveInteger } from '../../utils/validation.js';
|
|
10
10
|
/**
|
|
11
|
-
* Create the run command
|
|
11
|
+
* Create the workflow-run command
|
|
12
12
|
*/
|
|
13
13
|
export function createRunCommand() {
|
|
14
|
-
return new Command('run')
|
|
14
|
+
return new Command('workflow-run')
|
|
15
15
|
.description('Run FABER workflow')
|
|
16
16
|
.requiredOption('--work-id <id>', 'Work item ID to process')
|
|
17
17
|
.option('--autonomy <level>', 'Autonomy level: supervised|assisted|autonomous', 'supervised')
|
|
@@ -60,10 +60,10 @@ export function createRunCommand() {
|
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
/**
|
|
63
|
-
* Create the status command
|
|
63
|
+
* Create the workflow-status command
|
|
64
64
|
*/
|
|
65
65
|
export function createStatusCommand() {
|
|
66
|
-
return new Command('status')
|
|
66
|
+
return new Command('workflow-status')
|
|
67
67
|
.description('Show workflow status')
|
|
68
68
|
.option('--work-id <id>', 'Work item ID to check')
|
|
69
69
|
.option('--workflow-id <id>', 'Workflow ID to check')
|
|
@@ -75,7 +75,7 @@ export function createStatusCommand() {
|
|
|
75
75
|
if (options.workflowId) {
|
|
76
76
|
// Status for specific workflow by ID
|
|
77
77
|
const workflow = new FaberWorkflow();
|
|
78
|
-
const status = workflow.
|
|
78
|
+
const status = workflow.status.get(options.workflowId);
|
|
79
79
|
if (options.json) {
|
|
80
80
|
console.log(JSON.stringify({ status: 'success', data: status }, null, 2));
|
|
81
81
|
}
|
|
@@ -87,7 +87,7 @@ export function createStatusCommand() {
|
|
|
87
87
|
}
|
|
88
88
|
else if (options.workId) {
|
|
89
89
|
// Status for work item's active workflow
|
|
90
|
-
const state = stateManager.
|
|
90
|
+
const state = stateManager.workflow.getActive(options.workId);
|
|
91
91
|
if (!state) {
|
|
92
92
|
if (options.json) {
|
|
93
93
|
console.log(JSON.stringify({
|
|
@@ -123,7 +123,7 @@ export function createStatusCommand() {
|
|
|
123
123
|
}
|
|
124
124
|
else {
|
|
125
125
|
// List all workflows
|
|
126
|
-
const workflows = stateManager.
|
|
126
|
+
const workflows = stateManager.workflow.list();
|
|
127
127
|
if (options.json) {
|
|
128
128
|
console.log(JSON.stringify({ status: 'success', data: workflows }, null, 2));
|
|
129
129
|
}
|
|
@@ -147,10 +147,10 @@ export function createStatusCommand() {
|
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
/**
|
|
150
|
-
* Create the resume command
|
|
150
|
+
* Create the workflow-resume command
|
|
151
151
|
*/
|
|
152
152
|
export function createResumeCommand() {
|
|
153
|
-
return new Command('resume')
|
|
153
|
+
return new Command('workflow-resume')
|
|
154
154
|
.description('Resume a paused workflow')
|
|
155
155
|
.argument('<workflow_id>', 'Workflow ID to resume')
|
|
156
156
|
.option('--json', 'Output as JSON')
|
|
@@ -175,10 +175,10 @@ export function createResumeCommand() {
|
|
|
175
175
|
});
|
|
176
176
|
}
|
|
177
177
|
/**
|
|
178
|
-
* Create the pause command
|
|
178
|
+
* Create the workflow-pause command
|
|
179
179
|
*/
|
|
180
180
|
export function createPauseCommand() {
|
|
181
|
-
return new Command('pause')
|
|
181
|
+
return new Command('workflow-pause')
|
|
182
182
|
.description('Pause a running workflow')
|
|
183
183
|
.argument('<workflow_id>', 'Workflow ID to pause')
|
|
184
184
|
.option('--json', 'Output as JSON')
|
|
@@ -199,10 +199,10 @@ export function createPauseCommand() {
|
|
|
199
199
|
});
|
|
200
200
|
}
|
|
201
201
|
/**
|
|
202
|
-
* Create the recover command
|
|
202
|
+
* Create the workflow-recover command
|
|
203
203
|
*/
|
|
204
204
|
export function createRecoverCommand() {
|
|
205
|
-
return new Command('recover')
|
|
205
|
+
return new Command('workflow-recover')
|
|
206
206
|
.description('Recover a workflow from checkpoint')
|
|
207
207
|
.argument('<workflow_id>', 'Workflow ID to recover')
|
|
208
208
|
.option('--checkpoint <id>', 'Specific checkpoint ID to recover from')
|
|
@@ -211,7 +211,7 @@ export function createRecoverCommand() {
|
|
|
211
211
|
.action(async (workflowId, options) => {
|
|
212
212
|
try {
|
|
213
213
|
const stateManager = new StateManager();
|
|
214
|
-
const state = stateManager.
|
|
214
|
+
const state = stateManager.workflow.recover(workflowId, {
|
|
215
215
|
checkpointId: options.checkpoint,
|
|
216
216
|
fromPhase: options.phase,
|
|
217
217
|
});
|
|
@@ -230,10 +230,10 @@ export function createRecoverCommand() {
|
|
|
230
230
|
});
|
|
231
231
|
}
|
|
232
232
|
/**
|
|
233
|
-
* Create the cleanup command
|
|
233
|
+
* Create the workflow-cleanup command
|
|
234
234
|
*/
|
|
235
235
|
export function createCleanupCommand() {
|
|
236
|
-
return new Command('cleanup')
|
|
236
|
+
return new Command('workflow-cleanup')
|
|
237
237
|
.description('Clean up old workflow states')
|
|
238
238
|
.option('--max-age <days>', 'Delete workflows older than N days', '30')
|
|
239
239
|
.option('--json', 'Output as JSON')
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC;;GAEG;AACH,wBAAgB,cAAc,IAAI,OAAO,CA2IxC"}
|