@hone-ai/cli 1.7.1 → 1.8.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/hone-cli.js +194 -0
- package/package.json +1 -1
package/hone-cli.js
CHANGED
|
@@ -4235,6 +4235,200 @@ program
|
|
|
4235
4235
|
process.exit(results.failed + results.errors > 0 ? 1 : 0);
|
|
4236
4236
|
});
|
|
4237
4237
|
|
|
4238
|
+
// ── HC-041: Run Story (Orchestrator) ─────────────────────────────────────────
|
|
4239
|
+
program
|
|
4240
|
+
.command('run-story <storyId>')
|
|
4241
|
+
.description('Run the SDLC pipeline for a story via the orchestrator')
|
|
4242
|
+
.option('--mode <mode>', 'Execution mode: interactive or batch', 'interactive')
|
|
4243
|
+
.option('--repo <name>', 'Repository name override (default: directory name)')
|
|
4244
|
+
.option('--branch <name>', 'Git branch (default: current)')
|
|
4245
|
+
.option('--status', 'Show status of the latest workflow run for this story')
|
|
4246
|
+
.option('--kill', 'Kill the latest workflow run for this story')
|
|
4247
|
+
.option('--approve <step>', 'Approve a paused gate (e.g., --approve step_0)')
|
|
4248
|
+
.option('--format <fmt>', 'Output format: pretty or json', 'pretty')
|
|
4249
|
+
.option('--poll-interval <s>', 'Poll interval in seconds', '5')
|
|
4250
|
+
.action(async (storyIdOrRunId, opts) => {
|
|
4251
|
+
const config = getConfig();
|
|
4252
|
+
const client = api(config);
|
|
4253
|
+
|
|
4254
|
+
// Resolve: if it looks like a UUID, use as runId directly.
|
|
4255
|
+
// Otherwise treat as storyId — will be used for POST /orchestrate body.
|
|
4256
|
+
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(storyIdOrRunId);
|
|
4257
|
+
const storyId = isUuid ? null : storyIdOrRunId;
|
|
4258
|
+
const runId = isUuid ? storyIdOrRunId : null;
|
|
4259
|
+
|
|
4260
|
+
// Status mode
|
|
4261
|
+
if (opts.status) {
|
|
4262
|
+
const statusId = runId || storyIdOrRunId;
|
|
4263
|
+
try {
|
|
4264
|
+
const { data } = await client.get(`/orchestrate/${statusId}`);
|
|
4265
|
+
if (opts.format === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
|
|
4266
|
+
console.log('');
|
|
4267
|
+
console.log(`Workflow: ${data.runId}`);
|
|
4268
|
+
console.log(`Story: ${data.storyId}`);
|
|
4269
|
+
console.log(`Status: ${data.status}`);
|
|
4270
|
+
console.log(`Mode: ${data.mode}`);
|
|
4271
|
+
console.log(`Tokens: ${(data.totalTokens || 0).toLocaleString()}`);
|
|
4272
|
+
if (data.errorMessage) console.log(`Error: ${data.errorMessage}`);
|
|
4273
|
+
console.log('');
|
|
4274
|
+
console.log('Steps:');
|
|
4275
|
+
for (const s of (data.steps || [])) {
|
|
4276
|
+
const icon = s.status === 'completed' ? '✓' : s.status === 'running' ? '⏳' : s.status === 'failed' ? '✗' : '⬜';
|
|
4277
|
+
const gate = s.gateResult ? ` (${s.gateResult})` : '';
|
|
4278
|
+
console.log(` ${icon} ${s.stepKey} (${s.agent}) ${s.status}${gate}`);
|
|
4279
|
+
}
|
|
4280
|
+
if (data.awaitingApproval) {
|
|
4281
|
+
console.log('');
|
|
4282
|
+
console.log(`Awaiting approval at ${data.currentStep}.`);
|
|
4283
|
+
console.log(`Approve: hone run-story ${data.runId} --approve ${data.currentStep}`);
|
|
4284
|
+
}
|
|
4285
|
+
console.log('');
|
|
4286
|
+
} catch (e) {
|
|
4287
|
+
console.error(`Failed: ${e.response?.data?.error || e.message}`);
|
|
4288
|
+
process.exit(1);
|
|
4289
|
+
}
|
|
4290
|
+
return;
|
|
4291
|
+
}
|
|
4292
|
+
|
|
4293
|
+
// Kill mode
|
|
4294
|
+
if (opts.kill) {
|
|
4295
|
+
const killId = runId || storyIdOrRunId;
|
|
4296
|
+
try {
|
|
4297
|
+
const { data } = await client.post(`/orchestrate/${killId}/kill`, { reason: 'CLI kill' });
|
|
4298
|
+
console.log(`Workflow ${data.runId} killed.`);
|
|
4299
|
+
} catch (e) {
|
|
4300
|
+
console.error(`Failed: ${e.response?.data?.error || e.message}`);
|
|
4301
|
+
process.exit(1);
|
|
4302
|
+
}
|
|
4303
|
+
return;
|
|
4304
|
+
}
|
|
4305
|
+
|
|
4306
|
+
// Approve mode
|
|
4307
|
+
if (opts.approve) {
|
|
4308
|
+
const approveId = runId || storyIdOrRunId;
|
|
4309
|
+
try {
|
|
4310
|
+
const { data } = await client.post(`/orchestrate/${approveId}/approve`, { stepKey: opts.approve });
|
|
4311
|
+
console.log(`Gate ${opts.approve} approved. Workflow resuming.`);
|
|
4312
|
+
console.log(`Poll: hone run-story ${approveId} --status`);
|
|
4313
|
+
} catch (e) {
|
|
4314
|
+
console.error(`Failed: ${e.response?.data?.error || e.message}`);
|
|
4315
|
+
process.exit(1);
|
|
4316
|
+
}
|
|
4317
|
+
return;
|
|
4318
|
+
}
|
|
4319
|
+
|
|
4320
|
+
// Start workflow (storyId required, runId not accepted for starting)
|
|
4321
|
+
if (isUuid) {
|
|
4322
|
+
console.error('Cannot start workflow with a runId. Use a story ID like HC-042.');
|
|
4323
|
+
console.error('To check status: hone run-story <runId> --status');
|
|
4324
|
+
process.exit(1);
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
const repoName = opts.repo || path.basename(process.cwd());
|
|
4328
|
+
let branch = opts.branch;
|
|
4329
|
+
if (!branch) {
|
|
4330
|
+
try { branch = execSync('git symbolic-ref --short HEAD', { encoding: 'utf8' }).trim(); } catch { branch = null; }
|
|
4331
|
+
}
|
|
4332
|
+
|
|
4333
|
+
try {
|
|
4334
|
+
const { data } = await client.post('/orchestrate', {
|
|
4335
|
+
storyId: storyIdOrRunId, repoName, branch, mode: opts.mode, config: {},
|
|
4336
|
+
});
|
|
4337
|
+
|
|
4338
|
+
console.log('');
|
|
4339
|
+
console.log(`Workflow started: ${data.runId}`);
|
|
4340
|
+
console.log(`Mode: ${data.mode} | Story: ${data.storyId}`);
|
|
4341
|
+
console.log(`Poll: ${data.pollUrl}`);
|
|
4342
|
+
console.log('');
|
|
4343
|
+
|
|
4344
|
+
// Poll loop
|
|
4345
|
+
const interval = parseInt(opts.pollInterval, 10) * 1000;
|
|
4346
|
+
while (true) {
|
|
4347
|
+
await new Promise(r => setTimeout(r, interval));
|
|
4348
|
+
|
|
4349
|
+
let status;
|
|
4350
|
+
try {
|
|
4351
|
+
const { data: pollData } = await client.get(`/orchestrate/${data.runId}`);
|
|
4352
|
+
status = pollData;
|
|
4353
|
+
} catch (e) {
|
|
4354
|
+
console.error(`Poll error: ${e.message}`);
|
|
4355
|
+
continue;
|
|
4356
|
+
}
|
|
4357
|
+
|
|
4358
|
+
if (status.status === 'completed') {
|
|
4359
|
+
console.log(`✓ Workflow completed! Total tokens: ${(status.totalTokens || 0).toLocaleString()}`);
|
|
4360
|
+
process.exit(0);
|
|
4361
|
+
}
|
|
4362
|
+
|
|
4363
|
+
if (status.status === 'failed' || status.status === 'killed') {
|
|
4364
|
+
console.error(`✗ Workflow ${status.status}: ${status.errorMessage || 'unknown'}`);
|
|
4365
|
+
process.exit(1);
|
|
4366
|
+
}
|
|
4367
|
+
|
|
4368
|
+
if (status.status === 'paused' && status.awaitingApproval) {
|
|
4369
|
+
const step = status.steps.find(s => s.stepKey === status.currentStep);
|
|
4370
|
+
console.log(`⏳ Step ${status.currentStep} (${step?.agent || 'unknown'}) — awaiting approval`);
|
|
4371
|
+
|
|
4372
|
+
// Interactive prompt
|
|
4373
|
+
if (process.stdin.isTTY) {
|
|
4374
|
+
const readline = require('readline');
|
|
4375
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
4376
|
+
const answer = await new Promise(resolve => {
|
|
4377
|
+
rl.question(' Approve? [y/n/kill] ', resolve);
|
|
4378
|
+
});
|
|
4379
|
+
rl.close();
|
|
4380
|
+
|
|
4381
|
+
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
4382
|
+
try {
|
|
4383
|
+
await client.post(`/orchestrate/${data.runId}/approve`, { stepKey: status.currentStep });
|
|
4384
|
+
console.log(' Approved — workflow resuming...');
|
|
4385
|
+
} catch (e) {
|
|
4386
|
+
console.error(` Approve failed: ${e.response?.data?.error || e.message}`);
|
|
4387
|
+
}
|
|
4388
|
+
} else if (answer.toLowerCase() === 'kill') {
|
|
4389
|
+
try {
|
|
4390
|
+
await client.post(`/orchestrate/${data.runId}/kill`, { reason: 'CLI kill' });
|
|
4391
|
+
console.log(' Workflow killed.');
|
|
4392
|
+
process.exit(0);
|
|
4393
|
+
} catch (e) {
|
|
4394
|
+
console.error(` Kill failed: ${e.message}`);
|
|
4395
|
+
}
|
|
4396
|
+
} else {
|
|
4397
|
+
console.log(` Skipped. Resume later: hone run-story ${data.runId} --status`);
|
|
4398
|
+
process.exit(0);
|
|
4399
|
+
}
|
|
4400
|
+
} else {
|
|
4401
|
+
// Non-interactive (CI): exit with code 2 (paused, needs approval)
|
|
4402
|
+
console.log(` Non-interactive mode. Approve with: hone run-story ${data.runId} --approve ${status.currentStep}`);
|
|
4403
|
+
process.exit(2);
|
|
4404
|
+
}
|
|
4405
|
+
continue;
|
|
4406
|
+
}
|
|
4407
|
+
|
|
4408
|
+
// Still running
|
|
4409
|
+
const currentStep = status.steps.find(s => s.status === 'running');
|
|
4410
|
+
if (currentStep) {
|
|
4411
|
+
process.stdout.write(` Running: ${currentStep.stepKey} (${currentStep.agent})...\r`);
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
} catch (e) {
|
|
4415
|
+
if (e.response?.status === 429) {
|
|
4416
|
+
console.error(`Max concurrent workflows reached. ${e.response.data.error}`);
|
|
4417
|
+
} else {
|
|
4418
|
+
console.error(`Failed to start: ${e.response?.data?.error || e.message}`);
|
|
4419
|
+
}
|
|
4420
|
+
process.exit(1);
|
|
4421
|
+
}
|
|
4422
|
+
});
|
|
4423
|
+
|
|
4424
|
+
// SIGINT handler for run-story
|
|
4425
|
+
process.on('SIGINT', () => {
|
|
4426
|
+
console.log('\n\nWorkflow continues on the server.');
|
|
4427
|
+
console.log('Resume: hone run-story <storyId> --status');
|
|
4428
|
+
console.log('Kill: hone run-story <storyId> --kill');
|
|
4429
|
+
process.exit(0);
|
|
4430
|
+
});
|
|
4431
|
+
|
|
4238
4432
|
// ── CLI setup ─────────────────────────────────────────────────────────────────
|
|
4239
4433
|
program
|
|
4240
4434
|
.name('hone')
|