@formigio/fazemos-cli 0.10.8 → 0.10.11
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/api.d.ts +8 -0
- package/dist/api.js +8 -0
- package/dist/api.js.map +1 -1
- package/dist/index.js +139 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/api.d.ts
CHANGED
|
@@ -29,6 +29,14 @@ export interface ApiOptions {
|
|
|
29
29
|
projectSlug?: string;
|
|
30
30
|
allProjects?: boolean;
|
|
31
31
|
noProjectHeader?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Extra request headers merged on top of the standard auth/org/project
|
|
34
|
+
* headers. Used by callers that need optimistic-concurrency or other
|
|
35
|
+
* out-of-band headers (e.g., I30 force-transition's `If-Match: <n>` opt-in).
|
|
36
|
+
* Caller-supplied keys win on conflict — this is intentional so a caller
|
|
37
|
+
* can override `Content-Type` if needed.
|
|
38
|
+
*/
|
|
39
|
+
headers?: Record<string, string>;
|
|
32
40
|
}
|
|
33
41
|
/**
|
|
34
42
|
* Resolve the project id to send as X-Fazemos-Project-Id on a scoped call.
|
package/dist/api.js
CHANGED
|
@@ -119,6 +119,14 @@ export async function api(method, path, body, opts = {}) {
|
|
|
119
119
|
headers['X-Fazemos-Project-Id'] = projectId;
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
|
+
// I30 — caller-supplied extra headers (e.g., If-Match for opt-in optimistic
|
|
123
|
+
// concurrency on force-transition). Applied last so callers can override
|
|
124
|
+
// anything we set above on purpose.
|
|
125
|
+
if (opts.headers) {
|
|
126
|
+
for (const [k, v] of Object.entries(opts.headers)) {
|
|
127
|
+
headers[k] = v;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
122
130
|
// Build URL with optional ?view=all append. We don't try to be clever
|
|
123
131
|
// about merging with existing query strings here — callers that use
|
|
124
132
|
// --all-projects know they're on GET list endpoints with no other query.
|
package/dist/api.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,QAAQ,EACR,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,gBAAgB,GAEjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IACjC,MAAM,CAAS;IACf,IAAI,CAAU;IACd,IAAI,CAAW;IACf,YAAY,OAAe,EAAE,MAAc,EAAE,IAAa,EAAE,IAAc;QACxE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,13 @@ function parseStreamSilenceAbortMs(val) {
|
|
|
26
26
|
}
|
|
27
27
|
return n;
|
|
28
28
|
}
|
|
29
|
+
function parseEvalMaxTurns(val) {
|
|
30
|
+
const n = Number(val);
|
|
31
|
+
if (!Number.isInteger(n) || n < 8 || n > 30) {
|
|
32
|
+
throw new Error(`eval_max_turns must be an integer between 8 and 30 (got ${val})`);
|
|
33
|
+
}
|
|
34
|
+
return n;
|
|
35
|
+
}
|
|
29
36
|
function formatMsHuman(ms) {
|
|
30
37
|
if (ms >= 60000) {
|
|
31
38
|
const min = ms / 60000;
|
|
@@ -2823,6 +2830,9 @@ templates
|
|
|
2823
2830
|
else if (step.outputs?.length) {
|
|
2824
2831
|
console.log(' Inputs: none');
|
|
2825
2832
|
}
|
|
2833
|
+
if (step.eval_max_turns != null) {
|
|
2834
|
+
console.log(` Eval Max Turns: ${step.eval_max_turns}`);
|
|
2835
|
+
}
|
|
2826
2836
|
if (step.stream_silence_abort_ms) {
|
|
2827
2837
|
console.log(` Stream Silence Abort: ${step.stream_silence_abort_ms.toLocaleString()}ms (${formatMsHuman(step.stream_silence_abort_ms)})`);
|
|
2828
2838
|
}
|
|
@@ -3179,6 +3189,7 @@ templates
|
|
|
3179
3189
|
.option('--sort-order <n>', 'Set sort_order directly (lower-level escape hatch)', parseNumber)
|
|
3180
3190
|
.option('--agent-config <json>', 'Per-step agent config overrides as JSON (e.g., \'{"model":"opus","maxBudgetUsd":20}\'). Overrides agent member defaults for: model, maxBudgetUsd, maxTurns, timeoutMs, cwd, repos')
|
|
3181
3191
|
.option('--stream-silence-abort-ms <ms>', 'Stream silence abort threshold in milliseconds (30000-1800000)', parseStreamSilenceAbortMs)
|
|
3192
|
+
.option('--eval-max-turns <n>', 'Evaluator turn budget for tool-enabled (agent) evals on this step (8-30). Has no effect on script-typed steps (rejected by API).', parseEvalMaxTurns)
|
|
3182
3193
|
.action(async (templateId, opts) => {
|
|
3183
3194
|
try {
|
|
3184
3195
|
if (!VALID_STEP_TYPES.includes(opts.type)) {
|
|
@@ -3240,6 +3251,8 @@ templates
|
|
|
3240
3251
|
step.agent_config = parseJson(opts.agentConfig, '--agent-config');
|
|
3241
3252
|
if (opts.streamSilenceAbortMs !== undefined)
|
|
3242
3253
|
step.stream_silence_abort_ms = opts.streamSilenceAbortMs;
|
|
3254
|
+
if (opts.evalMaxTurns !== undefined)
|
|
3255
|
+
step.eval_max_turns = opts.evalMaxTurns;
|
|
3243
3256
|
// Positioning: --after or --sort-order
|
|
3244
3257
|
if (opts.after && opts.sortOrder !== undefined) {
|
|
3245
3258
|
console.error(chalk.red('Cannot use both --after and --sort-order'));
|
|
@@ -3268,6 +3281,9 @@ templates
|
|
|
3268
3281
|
if (step.stream_silence_abort_ms) {
|
|
3269
3282
|
console.log(` Stream Silence Abort: ${step.stream_silence_abort_ms.toLocaleString()}ms (${formatMsHuman(step.stream_silence_abort_ms)})`);
|
|
3270
3283
|
}
|
|
3284
|
+
if (step.eval_max_turns != null) {
|
|
3285
|
+
console.log(` Eval Max Turns: ${step.eval_max_turns}`);
|
|
3286
|
+
}
|
|
3271
3287
|
}
|
|
3272
3288
|
catch (err) {
|
|
3273
3289
|
console.error(chalk.red(err.message));
|
|
@@ -3337,12 +3353,20 @@ templates
|
|
|
3337
3353
|
.option('--sections <text>', 'Agent instructions / step content. Use @filepath to load from a file (e.g., --sections @steps/review.md)')
|
|
3338
3354
|
.option('--agent-config <json>', 'Per-step agent config overrides as JSON (merges with existing)')
|
|
3339
3355
|
.option('--stream-silence-abort-ms <ms>', 'Stream silence abort threshold in milliseconds (30000-1800000)', parseStreamSilenceAbortMs)
|
|
3356
|
+
.option('--eval-max-turns <n>', 'Evaluator turn budget for tool-enabled (agent) evals on this step (8-30). Mutually exclusive with --clear-eval-max-turns.', parseEvalMaxTurns)
|
|
3357
|
+
.option('--clear-eval-max-turns', 'Remove a previously-set eval_max_turns from this step, reverting to runner default. Mutually exclusive with --eval-max-turns.')
|
|
3340
3358
|
.action(async (templateId, opts) => {
|
|
3341
3359
|
try {
|
|
3360
|
+
if (opts.evalMaxTurns !== undefined && opts.clearEvalMaxTurns) {
|
|
3361
|
+
console.error(chalk.red('--eval-max-turns and --clear-eval-max-turns are mutually exclusive'));
|
|
3362
|
+
process.exit(1);
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
3342
3365
|
const hasUpdate = opts.name || opts.type || opts.role || opts.description != null
|
|
3343
3366
|
|| opts.reviewer != null || opts.maxReviewCycles != null || opts.parallelGroup != null
|
|
3344
3367
|
|| opts.image || opts.command || opts.timeout !== undefined || opts.workingDir || opts.env
|
|
3345
|
-
|| opts.agentConfig || opts.sections != null || opts.streamSilenceAbortMs !== undefined
|
|
3368
|
+
|| opts.agentConfig || opts.sections != null || opts.streamSilenceAbortMs !== undefined
|
|
3369
|
+
|| opts.evalMaxTurns !== undefined || opts.clearEvalMaxTurns;
|
|
3346
3370
|
if (!hasUpdate) {
|
|
3347
3371
|
console.error(chalk.red('Provide at least one field to update'));
|
|
3348
3372
|
process.exit(1);
|
|
@@ -3415,6 +3439,13 @@ templates
|
|
|
3415
3439
|
if (opts.streamSilenceAbortMs !== undefined) {
|
|
3416
3440
|
step.stream_silence_abort_ms = opts.streamSilenceAbortMs;
|
|
3417
3441
|
}
|
|
3442
|
+
// eval_max_turns: set or clear (mutually exclusive — checked above)
|
|
3443
|
+
if (opts.evalMaxTurns !== undefined) {
|
|
3444
|
+
step.eval_max_turns = opts.evalMaxTurns;
|
|
3445
|
+
}
|
|
3446
|
+
else if (opts.clearEvalMaxTurns) {
|
|
3447
|
+
delete step.eval_max_turns;
|
|
3448
|
+
}
|
|
3418
3449
|
await api('PUT', `/api/pipeline-templates/${templateId}`, { definition: t.definition });
|
|
3419
3450
|
console.log(chalk.green(`Updated step: ${step.name}`));
|
|
3420
3451
|
}
|
|
@@ -4022,18 +4053,114 @@ pipelines
|
|
|
4022
4053
|
process.exit(1);
|
|
4023
4054
|
}
|
|
4024
4055
|
});
|
|
4056
|
+
// I30 — `pl force-cancel` removed. Replaced by the generalised
|
|
4057
|
+
// `pl force-transition pipeline <id> --to cancelled` admin endpoint, which
|
|
4058
|
+
// covers the same emergency cancel path plus 12 more entity/state combos
|
|
4059
|
+
// across pipelines, steps, and executions. No shim: callers update or 404.
|
|
4025
4060
|
pipelines
|
|
4026
|
-
.command('force-
|
|
4027
|
-
.description('Force
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
.
|
|
4031
|
-
|
|
4032
|
-
|
|
4061
|
+
.command('force-transition')
|
|
4062
|
+
.description('Force an entity (pipeline, step, or execution) into a target state. Admin / org owner / project member only. ' +
|
|
4063
|
+
'Bypasses normal state transition rules and fires the appropriate subscriber chain (audit log, ECS stop, ' +
|
|
4064
|
+
'agent reset, token revoke, etc.). Use only when the normal lifecycle is stuck.')
|
|
4065
|
+
.argument('<entity-type>', 'Entity type: pipeline | step | execution')
|
|
4066
|
+
.argument('<id>', 'Entity ID (pipeline_instance / step_instance / execution UUID)')
|
|
4067
|
+
.requiredOption('--to <state>', 'Target state (e.g., cancelled, failed, completed, in_progress, ...)')
|
|
4068
|
+
.option('-r, --reason <reason>', 'Free-form audit reason (recorded in audit_log)')
|
|
4069
|
+
.option('--output-data-file <path>', 'Path to a JSON file with output_data. Required when forcing a step with a structured output schema to "completed".')
|
|
4070
|
+
.option('--expect-version <n>', 'Optimistic-concurrency guard: send If-Match: "<n>" so the API rejects the call (409) ' +
|
|
4071
|
+
'if the current entity version differs. Omit to bypass version checking (default).')
|
|
4072
|
+
.option('--json', 'Print the raw API response as JSON (machine-readable)')
|
|
4073
|
+
.action(async (entityType, id, opts) => {
|
|
4074
|
+
try {
|
|
4075
|
+
const valid = ['pipeline', 'step', 'execution'];
|
|
4076
|
+
if (!valid.includes(entityType)) {
|
|
4077
|
+
console.error(chalk.red(`Invalid entity-type "${entityType}". Must be one of: ${valid.join(', ')}`));
|
|
4078
|
+
process.exit(1);
|
|
4079
|
+
}
|
|
4080
|
+
if (!opts.to || typeof opts.to !== 'string' || !opts.to.trim()) {
|
|
4081
|
+
console.error(chalk.red('--to <state> is required'));
|
|
4082
|
+
process.exit(1);
|
|
4083
|
+
}
|
|
4084
|
+
// Body field names match the API contract verbatim:
|
|
4085
|
+
// target_state — required
|
|
4086
|
+
// reason — optional, free-form audit string
|
|
4087
|
+
// output_data — required for step→completed when the step has an
|
|
4088
|
+
// output schema; carrier rejects with 400 OUTPUT_DATA_*
|
|
4089
|
+
// otherwise.
|
|
4090
|
+
const body = { target_state: opts.to };
|
|
4033
4091
|
if (opts.reason)
|
|
4034
4092
|
body.reason = opts.reason;
|
|
4035
|
-
|
|
4036
|
-
|
|
4093
|
+
if (opts.outputDataFile) {
|
|
4094
|
+
if (entityType !== 'step') {
|
|
4095
|
+
console.error(chalk.red('--output-data-file is only valid when entity-type is "step"'));
|
|
4096
|
+
process.exit(1);
|
|
4097
|
+
}
|
|
4098
|
+
let raw;
|
|
4099
|
+
try {
|
|
4100
|
+
raw = readFileSync(resolve(opts.outputDataFile), 'utf-8');
|
|
4101
|
+
}
|
|
4102
|
+
catch (err) {
|
|
4103
|
+
console.error(chalk.red(`Cannot read --output-data-file "${opts.outputDataFile}": ${err.message}`));
|
|
4104
|
+
process.exit(1);
|
|
4105
|
+
return;
|
|
4106
|
+
}
|
|
4107
|
+
let parsed;
|
|
4108
|
+
try {
|
|
4109
|
+
parsed = JSON.parse(raw);
|
|
4110
|
+
}
|
|
4111
|
+
catch (err) {
|
|
4112
|
+
console.error(chalk.red(`Invalid JSON in --output-data-file "${opts.outputDataFile}": ${err.message}`));
|
|
4113
|
+
process.exit(1);
|
|
4114
|
+
return;
|
|
4115
|
+
}
|
|
4116
|
+
body.output_data = parsed;
|
|
4117
|
+
}
|
|
4118
|
+
// --expect-version <n> opts the caller in to optimistic concurrency.
|
|
4119
|
+
// Default is bypass (no header) per the I30 spec Decision #4.
|
|
4120
|
+
const apiOpts = {};
|
|
4121
|
+
if (opts.expectVersion !== undefined) {
|
|
4122
|
+
const n = Number(opts.expectVersion);
|
|
4123
|
+
if (!Number.isInteger(n)) {
|
|
4124
|
+
console.error(chalk.red(`--expect-version must be an integer; got "${opts.expectVersion}"`));
|
|
4125
|
+
process.exit(1);
|
|
4126
|
+
}
|
|
4127
|
+
apiOpts.headers = { 'If-Match': String(n) };
|
|
4128
|
+
}
|
|
4129
|
+
const path = `/api/admin/force-transition/${entityType}/${id}`;
|
|
4130
|
+
const data = (await api('POST', path, body, apiOpts));
|
|
4131
|
+
if (opts.json) {
|
|
4132
|
+
console.log(JSON.stringify(data, null, 2));
|
|
4133
|
+
return;
|
|
4134
|
+
}
|
|
4135
|
+
// Human-readable summary. Per-entity payloads diverge (instance / step /
|
|
4136
|
+
// execution) so we surface whichever is present and always echo the
|
|
4137
|
+
// forensic fields (audit_log_id, subscribers_fired, subscriber_failures,
|
|
4138
|
+
// orphan_executions_cancelled).
|
|
4139
|
+
const entityPayload = data?.instance || data?.step || data?.execution || null;
|
|
4140
|
+
const label = entityPayload?.name || entityPayload?.step_name || entityPayload?.id || id;
|
|
4141
|
+
console.log(chalk.green(`Force-transitioned ${entityType} ${label} → ${opts.to}`));
|
|
4142
|
+
if (data?.audit_log_id) {
|
|
4143
|
+
console.log(chalk.gray(` audit_log_id: ${data.audit_log_id}`));
|
|
4144
|
+
}
|
|
4145
|
+
const fired = Array.isArray(data?.subscribers_fired) ? data.subscribers_fired : [];
|
|
4146
|
+
if (fired.length) {
|
|
4147
|
+
console.log(chalk.gray(` subscribers_fired: ${fired.join(', ')}`));
|
|
4148
|
+
}
|
|
4149
|
+
const failures = Array.isArray(data?.subscriber_failures)
|
|
4150
|
+
? data.subscriber_failures
|
|
4151
|
+
: [];
|
|
4152
|
+
if (failures.length) {
|
|
4153
|
+
console.log(chalk.yellow(` subscriber_failures: ${failures.length}`));
|
|
4154
|
+
for (const f of failures) {
|
|
4155
|
+
console.log(chalk.yellow(` - ${f.id || f.subscriber || '?'}: ${f.error}`));
|
|
4156
|
+
}
|
|
4157
|
+
}
|
|
4158
|
+
const orphans = Array.isArray(data?.orphan_executions_cancelled)
|
|
4159
|
+
? data.orphan_executions_cancelled
|
|
4160
|
+
: [];
|
|
4161
|
+
if (orphans.length) {
|
|
4162
|
+
console.log(chalk.gray(` orphan_executions_cancelled: ${orphans.join(', ')}`));
|
|
4163
|
+
}
|
|
4037
4164
|
}
|
|
4038
4165
|
catch (err) {
|
|
4039
4166
|
console.error(chalk.red(err.message));
|
|
@@ -4058,7 +4185,7 @@ pipelines
|
|
|
4058
4185
|
});
|
|
4059
4186
|
pipelines
|
|
4060
4187
|
.command('set-status')
|
|
4061
|
-
.description('Update instance status. Allowed: paused, cancelled. Use "pl force-
|
|
4188
|
+
.description('Update instance status. Allowed: paused, cancelled. Use "pl force-transition pipeline <id> --to cancelled" for stuck instances.')
|
|
4062
4189
|
.argument('<id>', 'Instance ID')
|
|
4063
4190
|
.requiredOption('--status <status>', 'New status (paused or cancelled)')
|
|
4064
4191
|
.action(async (id, opts) => {
|
|
@@ -4066,7 +4193,7 @@ pipelines
|
|
|
4066
4193
|
const valid = ['paused', 'cancelled'];
|
|
4067
4194
|
if (!valid.includes(opts.status)) {
|
|
4068
4195
|
console.error(chalk.red(`Invalid status. Must be one of: ${valid.join(', ')}`));
|
|
4069
|
-
console.error(chalk.yellow('Tip: Use "pl force-
|
|
4196
|
+
console.error(chalk.yellow('Tip: Use "pl force-transition pipeline <id> --to cancelled" to cancel stuck pipelines from any state.'));
|
|
4070
4197
|
process.exit(1);
|
|
4071
4198
|
}
|
|
4072
4199
|
const data = await api('PATCH', `/api/pipeline-instances/${id}/status`, { status: opts.status });
|