jetic-cli 0.2.1 → 0.2.3
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/index.js +553 -60
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -267984,57 +267984,116 @@ var require_workflow_simulator = __commonJS({
|
|
|
267984
267984
|
for (let i = 0; i < workflow.steps.length; i++) {
|
|
267985
267985
|
const step = workflow.steps[i];
|
|
267986
267986
|
const stepStartTime = Date.now();
|
|
267987
|
+
if (step.condition) {
|
|
267988
|
+
const condResult = await this.evaluateCondition(step.condition);
|
|
267989
|
+
if (!condResult.matched) {
|
|
267990
|
+
stepResults.push({
|
|
267991
|
+
stepIndex: i + 1,
|
|
267992
|
+
name: step.name || `${step.method} ${step.path}`,
|
|
267993
|
+
method: step.method,
|
|
267994
|
+
path: step.path,
|
|
267995
|
+
targetUrl: "",
|
|
267996
|
+
expectedStatus: step.expectStatus ?? 200,
|
|
267997
|
+
actualStatus: 0,
|
|
267998
|
+
durationMs: Date.now() - stepStartTime,
|
|
267999
|
+
passed: true,
|
|
268000
|
+
skipped: true,
|
|
268001
|
+
retryCount: 0,
|
|
268002
|
+
conditionResult: condResult,
|
|
268003
|
+
requestHeaders: {},
|
|
268004
|
+
requestBody: {},
|
|
268005
|
+
responseBody: null,
|
|
268006
|
+
capturedMemory: []
|
|
268007
|
+
});
|
|
268008
|
+
continue;
|
|
268009
|
+
}
|
|
268010
|
+
}
|
|
267987
268011
|
const { headers: injectedHeaders, body: injectedBody } = await this.resolveInjections(step.inject);
|
|
267988
268012
|
const resolvedBody = await this.resolveBodyTemplates(step.body || {});
|
|
267989
268013
|
const finalBody = { ...injectedBody, ...resolvedBody };
|
|
267990
268014
|
const inputCaptured = await this.captureInputToMemory(step.captureInput, finalBody);
|
|
267991
268015
|
const resolvedPath = await this.resolvePathParams(step.path, finalBody);
|
|
267992
268016
|
const fullUrl = `${baseUrl.replace(/\/$/, "")}${resolvedPath}`;
|
|
267993
|
-
const expectedStatus = step.expectStatus
|
|
268017
|
+
const expectedStatus = step.expectStatus ?? 200;
|
|
268018
|
+
const maxAttempts = Math.max(1, step.retry?.times ?? workflow.retry?.times ?? 1);
|
|
268019
|
+
const retryDelayMs = step.retry?.delayMs ?? workflow.retry?.delayMs ?? 0;
|
|
268020
|
+
let attempts = 0;
|
|
267994
268021
|
let responseStatus = 0;
|
|
267995
268022
|
let responseBody = null;
|
|
267996
268023
|
let stepPassed = false;
|
|
267997
268024
|
let errorMsg;
|
|
267998
|
-
|
|
267999
|
-
|
|
268000
|
-
|
|
268001
|
-
|
|
268002
|
-
|
|
268003
|
-
|
|
268004
|
-
|
|
268005
|
-
|
|
268006
|
-
|
|
268007
|
-
fetchOptions
|
|
268008
|
-
|
|
268009
|
-
|
|
268010
|
-
|
|
268011
|
-
|
|
268012
|
-
|
|
268013
|
-
|
|
268014
|
-
|
|
268015
|
-
|
|
268016
|
-
|
|
268017
|
-
|
|
268018
|
-
|
|
268019
|
-
|
|
268020
|
-
|
|
268021
|
-
|
|
268022
|
-
|
|
268023
|
-
|
|
268024
|
-
|
|
268025
|
-
|
|
268025
|
+
while (attempts < maxAttempts) {
|
|
268026
|
+
attempts++;
|
|
268027
|
+
if (attempts > 1 && retryDelayMs > 0) {
|
|
268028
|
+
await new Promise((r) => setTimeout(r, retryDelayMs));
|
|
268029
|
+
}
|
|
268030
|
+
responseStatus = 0;
|
|
268031
|
+
responseBody = null;
|
|
268032
|
+
errorMsg = void 0;
|
|
268033
|
+
try {
|
|
268034
|
+
const fetchOptions = {
|
|
268035
|
+
method: step.method.toUpperCase(),
|
|
268036
|
+
headers: { "Content-Type": "application/json", ...injectedHeaders }
|
|
268037
|
+
};
|
|
268038
|
+
if (!["GET", "HEAD"].includes(step.method.toUpperCase()) && Object.keys(finalBody).length > 0) {
|
|
268039
|
+
fetchOptions.body = JSON.stringify(finalBody);
|
|
268040
|
+
}
|
|
268041
|
+
let finalUrl = fullUrl;
|
|
268042
|
+
if (["GET", "HEAD"].includes(step.method.toUpperCase()) && Object.keys(finalBody).length > 0) {
|
|
268043
|
+
const params = new URLSearchParams();
|
|
268044
|
+
for (const [k, v] of Object.entries(finalBody)) {
|
|
268045
|
+
if (v !== void 0 && v !== null)
|
|
268046
|
+
params.set(k, String(v));
|
|
268047
|
+
}
|
|
268048
|
+
finalUrl = `${fullUrl}?${params.toString()}`;
|
|
268049
|
+
}
|
|
268050
|
+
const resp = await fetch(finalUrl, fetchOptions);
|
|
268051
|
+
responseStatus = resp.status;
|
|
268052
|
+
const ct = resp.headers.get("content-type") || "";
|
|
268053
|
+
if (ct.includes("application/json")) {
|
|
268054
|
+
try {
|
|
268055
|
+
responseBody = await resp.json();
|
|
268056
|
+
} catch {
|
|
268057
|
+
responseBody = null;
|
|
268058
|
+
}
|
|
268059
|
+
} else {
|
|
268060
|
+
responseBody = await resp.text();
|
|
268026
268061
|
}
|
|
268027
|
-
|
|
268028
|
-
|
|
268062
|
+
const in2xx = (s) => s >= 200 && s < 300;
|
|
268063
|
+
stepPassed = resp.status === expectedStatus || in2xx(resp.status) && in2xx(expectedStatus) || (step.continueOnStatus || []).includes(resp.status);
|
|
268064
|
+
if (stepPassed)
|
|
268065
|
+
break;
|
|
268066
|
+
} catch (err) {
|
|
268067
|
+
stepPassed = false;
|
|
268068
|
+
errorMsg = err.message || String(err);
|
|
268029
268069
|
}
|
|
268030
|
-
stepPassed = resp.status === expectedStatus || resp.status >= 200 && resp.status < 300 && expectedStatus >= 200 && expectedStatus < 300;
|
|
268031
|
-
} catch (err) {
|
|
268032
|
-
stepPassed = false;
|
|
268033
|
-
errorMsg = err.message || String(err);
|
|
268034
268070
|
}
|
|
268035
268071
|
const responseCaptured = stepPassed ? await this.captureToMemory(step.capture, responseBody) : [];
|
|
268036
268072
|
if (!stepPassed) {
|
|
268037
268073
|
allPassed = false;
|
|
268074
|
+
const onFailure = step.onFailure ?? "abort";
|
|
268075
|
+
stepResults.push({
|
|
268076
|
+
stepIndex: i + 1,
|
|
268077
|
+
name: step.name || `${step.method} ${step.path}`,
|
|
268078
|
+
method: step.method,
|
|
268079
|
+
path: step.path,
|
|
268080
|
+
targetUrl: fullUrl,
|
|
268081
|
+
expectedStatus,
|
|
268082
|
+
actualStatus: responseStatus,
|
|
268083
|
+
durationMs: Date.now() - stepStartTime,
|
|
268084
|
+
passed: false,
|
|
268085
|
+
skipped: false,
|
|
268086
|
+
retryCount: attempts - 1,
|
|
268087
|
+
failureHandling: onFailure === "continue" ? "continued" : "aborted",
|
|
268088
|
+
requestHeaders: injectedHeaders,
|
|
268089
|
+
requestBody: finalBody,
|
|
268090
|
+
responseBody,
|
|
268091
|
+
capturedMemory: [...inputCaptured],
|
|
268092
|
+
error: errorMsg
|
|
268093
|
+
});
|
|
268094
|
+
if (onFailure === "abort")
|
|
268095
|
+
break;
|
|
268096
|
+
continue;
|
|
268038
268097
|
}
|
|
268039
268098
|
stepResults.push({
|
|
268040
268099
|
stepIndex: i + 1,
|
|
@@ -268045,16 +268104,14 @@ var require_workflow_simulator = __commonJS({
|
|
|
268045
268104
|
expectedStatus,
|
|
268046
268105
|
actualStatus: responseStatus,
|
|
268047
268106
|
durationMs: Date.now() - stepStartTime,
|
|
268048
|
-
passed:
|
|
268107
|
+
passed: true,
|
|
268108
|
+
skipped: false,
|
|
268109
|
+
retryCount: attempts - 1,
|
|
268049
268110
|
requestHeaders: injectedHeaders,
|
|
268050
268111
|
requestBody: finalBody,
|
|
268051
268112
|
responseBody,
|
|
268052
|
-
capturedMemory: [...inputCaptured, ...responseCaptured]
|
|
268053
|
-
error: errorMsg
|
|
268113
|
+
capturedMemory: [...inputCaptured, ...responseCaptured]
|
|
268054
268114
|
});
|
|
268055
|
-
if (!stepPassed) {
|
|
268056
|
-
break;
|
|
268057
|
-
}
|
|
268058
268115
|
}
|
|
268059
268116
|
const totalTimeMs = Date.now() - startTime;
|
|
268060
268117
|
return {
|
|
@@ -268078,6 +268135,32 @@ var require_workflow_simulator = __commonJS({
|
|
|
268078
268135
|
results: stepResults
|
|
268079
268136
|
};
|
|
268080
268137
|
}
|
|
268138
|
+
// ── Condition evaluator ────────────────────────────────────────────────────
|
|
268139
|
+
// Resolves the `if` template and applies the declared operator. Returns
|
|
268140
|
+
// `matched: true` when the condition passes (step should execute).
|
|
268141
|
+
async evaluateCondition(condition) {
|
|
268142
|
+
const evaluated = await this.resolveTemplateString(condition.if);
|
|
268143
|
+
let matched;
|
|
268144
|
+
if (condition.exists !== void 0) {
|
|
268145
|
+
const hasValue = evaluated !== "" && evaluated !== "null" && evaluated !== "undefined";
|
|
268146
|
+
matched = condition.exists ? hasValue : !hasValue;
|
|
268147
|
+
} else if (condition.equals !== void 0) {
|
|
268148
|
+
matched = evaluated === condition.equals;
|
|
268149
|
+
} else if (condition.notEquals !== void 0) {
|
|
268150
|
+
matched = evaluated !== condition.notEquals;
|
|
268151
|
+
} else if (condition.contains !== void 0) {
|
|
268152
|
+
matched = evaluated.includes(condition.contains);
|
|
268153
|
+
} else if (condition.greaterThan !== void 0) {
|
|
268154
|
+
matched = Number(evaluated) > condition.greaterThan;
|
|
268155
|
+
} else if (condition.lessThan !== void 0) {
|
|
268156
|
+
matched = Number(evaluated) < condition.lessThan;
|
|
268157
|
+
} else if (condition.in !== void 0) {
|
|
268158
|
+
matched = condition.in.includes(evaluated);
|
|
268159
|
+
} else {
|
|
268160
|
+
matched = evaluated !== "" && evaluated !== "false" && evaluated !== "0" && evaluated !== "null" && evaluated !== "undefined";
|
|
268161
|
+
}
|
|
268162
|
+
return { matched, evaluated };
|
|
268163
|
+
}
|
|
268081
268164
|
async resolveTemplateString(value) {
|
|
268082
268165
|
const templateRe = /\{\{([^}]+)\}\}/g;
|
|
268083
268166
|
let match;
|
|
@@ -269549,28 +269632,47 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
269549
269632
|
var path7 = __importStar(require("path"));
|
|
269550
269633
|
var types_1 = require_types();
|
|
269551
269634
|
var workflow_tools_1 = require_workflow_tools();
|
|
269635
|
+
var stepConditionSchema = zod_1.z.object({
|
|
269636
|
+
if: zod_1.z.string().min(1).describe('Template expression to resolve, e.g. "{{workflow:role}}"'),
|
|
269637
|
+
equals: zod_1.z.string().optional().describe("Resolved value must equal this string"),
|
|
269638
|
+
notEquals: zod_1.z.string().optional().describe("Resolved value must NOT equal this string"),
|
|
269639
|
+
contains: zod_1.z.string().optional().describe("Resolved value must contain this substring"),
|
|
269640
|
+
exists: zod_1.z.boolean().optional().describe("true: value must be non-empty; false: value must be empty"),
|
|
269641
|
+
greaterThan: zod_1.z.number().optional().describe("Numeric greater-than comparison"),
|
|
269642
|
+
lessThan: zod_1.z.number().optional().describe("Numeric less-than comparison"),
|
|
269643
|
+
in: zod_1.z.array(zod_1.z.string()).optional().describe("Resolved value must be one of these strings")
|
|
269644
|
+
}).describe("Skip this step when the condition is not met");
|
|
269645
|
+
var stepRetrySchema = zod_1.z.object({
|
|
269646
|
+
times: zod_1.z.number().int().positive().max(10).describe("Max retry attempts (1\u201310)"),
|
|
269647
|
+
delayMs: zod_1.z.number().int().nonnegative().optional().describe("Delay between retries in ms (default 0)")
|
|
269648
|
+
});
|
|
269552
269649
|
exports2.workflowStepSchema = zod_1.z.object({
|
|
269553
269650
|
name: zod_1.z.string().min(1).describe("Step name (must be unique within the workflow)"),
|
|
269554
269651
|
method: zod_1.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]).describe("HTTP method"),
|
|
269555
269652
|
path: zod_1.z.string().min(1).describe("Route path; :params resolve from body/memory (NOT from {{templates}} \u2014 those only work in body/inject)"),
|
|
269556
269653
|
description: zod_1.z.string().optional().describe("What this step does"),
|
|
269557
|
-
body: zod_1.z.record(zod_1.z.any()).optional().describe("Request payload. Values support {{faker.*}}, {{scope:key}}, {{workflow:key}} and {{human:key}} templates
|
|
269558
|
-
inject: zod_1.z.record(zod_1.z.string()).optional().describe('
|
|
269559
|
-
capture: zod_1.z.record(zod_1.z.string()).optional().describe('Save RESPONSE fields to memory: "scope:key"
|
|
269560
|
-
captureInput: zod_1.z.record(zod_1.z.string()).optional().describe('Save REQUEST body fields to memory BEFORE sending: "scope:key"
|
|
269561
|
-
expectStatus: zod_1.z.number().int().positive().optional().describe("Expected HTTP status (default 200; any 2xx passes when expecting 2xx)")
|
|
269654
|
+
body: zod_1.z.record(zod_1.z.any()).optional().describe("Request payload. Values support {{faker.*}}, {{scope:key}}, {{workflow:key}} and {{human:key}} templates"),
|
|
269655
|
+
inject: zod_1.z.record(zod_1.z.string()).optional().describe('Header/body injection: "header:Name" or "body:field" \u2192 "scope:key" or "{{template}}"'),
|
|
269656
|
+
capture: zod_1.z.record(zod_1.z.string()).optional().describe('Save RESPONSE fields to memory: "scope:key" \u2192 "dot.path.in.response"'),
|
|
269657
|
+
captureInput: zod_1.z.record(zod_1.z.string()).optional().describe('Save REQUEST body fields to memory BEFORE sending: "scope:key" \u2192 "bodyField"'),
|
|
269658
|
+
expectStatus: zod_1.z.number().int().positive().optional().describe("Expected HTTP status (default 200; any 2xx passes when expecting 2xx)"),
|
|
269659
|
+
condition: stepConditionSchema.optional().describe('Skip this step when condition is not met. Use {{workflow:key}} in the "if" field.'),
|
|
269660
|
+
onFailure: zod_1.z.enum(["abort", "continue"]).optional().describe('"abort" (default): stop the workflow on failure. "continue": log failure and proceed (captures skipped).'),
|
|
269661
|
+
continueOnStatus: zod_1.z.array(zod_1.z.number().int()).optional().describe("Additional HTTP status codes treated as pass for this step (e.g. [404])."),
|
|
269662
|
+
retry: stepRetrySchema.optional().describe("Retry on failure \u2014 overrides the workflow-level global retry.")
|
|
269562
269663
|
// NOTE: .passthrough() is intentional — unknown keys must REACH the validator
|
|
269563
|
-
// (MODEL_DIALECT_KEY / UNKNOWN_STEP_KEY diagnostics). Zod
|
|
269664
|
+
// (MODEL_DIALECT_KEY / UNKNOWN_STEP_KEY diagnostics). Zod’s default strip
|
|
269564
269665
|
// behavior would silently swallow the exact mistakes we need to report.
|
|
269565
269666
|
}).passthrough();
|
|
269566
269667
|
var inlineWorkflowSchema = zod_1.z.object({
|
|
269567
269668
|
name: zod_1.z.string().min(1).describe("Workflow name (also used to derive the file slug)"),
|
|
269568
269669
|
description: zod_1.z.string().optional(),
|
|
269569
269670
|
environment: zod_1.z.string().optional().describe("Target environment name from the model (e.g. local)"),
|
|
269671
|
+
retry: stepRetrySchema.optional().describe("Global retry default applied to all steps (overridden per step)"),
|
|
269570
269672
|
steps: zod_1.z.array(exports2.workflowStepSchema).min(1).describe("Ordered steps; later steps can use memory captured by earlier ones")
|
|
269571
269673
|
}).passthrough();
|
|
269572
269674
|
var TEMPLATE_RE = /\{\{\s*([^}]+?)\s*\}\}/g;
|
|
269573
|
-
var KNOWN_WORKFLOW_KEYS = /* @__PURE__ */ new Set(["name", "description", "environment", "steps", "generatedAt"]);
|
|
269675
|
+
var KNOWN_WORKFLOW_KEYS = /* @__PURE__ */ new Set(["name", "description", "environment", "steps", "generatedAt", "retry"]);
|
|
269574
269676
|
var KNOWN_STEP_KEYS = /* @__PURE__ */ new Set([
|
|
269575
269677
|
"name",
|
|
269576
269678
|
"method",
|
|
@@ -269581,7 +269683,10 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
269581
269683
|
"captureInput",
|
|
269582
269684
|
"expectStatus",
|
|
269583
269685
|
"body",
|
|
269584
|
-
"condition"
|
|
269686
|
+
"condition",
|
|
269687
|
+
"onFailure",
|
|
269688
|
+
"continueOnStatus",
|
|
269689
|
+
"retry"
|
|
269585
269690
|
]);
|
|
269586
269691
|
var MODEL_DIALECT_KEYS = /* @__PURE__ */ new Set(["call", "auth", "bind"]);
|
|
269587
269692
|
function err(list, stepIndex, stepName, code, message, fix) {
|
|
@@ -269677,8 +269782,41 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
269677
269782
|
warn(warnings, stepNo, sName, "UNKNOWN_STEP_KEY", `Step ${stepNo} has unknown key "${key}" which the simulator ignores.`, "Remove it or check the field name (valid: name, method, path, description, body, inject, capture, captureInput, expectStatus).");
|
|
269678
269783
|
}
|
|
269679
269784
|
}
|
|
269680
|
-
if ("condition" in step) {
|
|
269681
|
-
|
|
269785
|
+
if ("condition" in step && step.condition !== void 0) {
|
|
269786
|
+
const cond = step.condition;
|
|
269787
|
+
if (!cond || typeof cond !== "object" || Array.isArray(cond)) {
|
|
269788
|
+
err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition" must be an object.`, 'Use { "condition": { "if": "{{workflow:role}}", "equals": "admin" } }.');
|
|
269789
|
+
} else if (!cond.if || typeof cond.if !== "string" || !cond.if.trim()) {
|
|
269790
|
+
err(errors, stepNo, sName, "BAD_CONDITION", `Step ${stepNo} "condition" must have a non-empty "if" field.`, 'Set "if" to a template expression like "{{workflow:role}}".');
|
|
269791
|
+
} else {
|
|
269792
|
+
const operators = ["equals", "notEquals", "contains", "exists", "greaterThan", "lessThan", "in"];
|
|
269793
|
+
const usedOps = operators.filter((op) => op in cond && cond[op] !== void 0);
|
|
269794
|
+
if (usedOps.length === 0) {
|
|
269795
|
+
warn(warnings, stepNo, sName, "CONDITION_NO_OPERATOR", `Step ${stepNo} "condition" has no comparison operator \u2014 uses bare truthy check.`, "Add one of: equals, notEquals, contains, exists, greaterThan, lessThan, in for explicit logic.");
|
|
269796
|
+
}
|
|
269797
|
+
if (!/\{\{[^}]+\}\}/.test(cond.if)) {
|
|
269798
|
+
warn(warnings, stepNo, sName, "CONDITION_BARE_IF", `Step ${stepNo} "condition.if" ("${cond.if}") contains no {{template}} and will be compared literally.`, "Use {{workflow:key}} or {{scope:key}} to reference captured memory values.");
|
|
269799
|
+
}
|
|
269800
|
+
const condRefs = [];
|
|
269801
|
+
collectTemplateRefs(cond.if, condRefs);
|
|
269802
|
+
for (const ref of condRefs) {
|
|
269803
|
+
if (ref.startsWith("faker."))
|
|
269804
|
+
continue;
|
|
269805
|
+
if (!ref.includes(":"))
|
|
269806
|
+
continue;
|
|
269807
|
+
const { scope, key } = splitMemKey(ref);
|
|
269808
|
+
if (!key)
|
|
269809
|
+
continue;
|
|
269810
|
+
if (scope === "human") {
|
|
269811
|
+
humanKeys.add(key);
|
|
269812
|
+
continue;
|
|
269813
|
+
}
|
|
269814
|
+
const full = `${scope}:${key}`;
|
|
269815
|
+
if (!defined.has(full) && !persistedMemory.has(full)) {
|
|
269816
|
+
err(errors, stepNo, sName, "UNDEFINED_MEMORY_REF", `"{{${ref}}}" in condition.if of step ${stepNo} is not captured by any earlier step and is not in memory.json.`, `Capture "${full}" in an earlier step before referencing it in a condition.`);
|
|
269817
|
+
}
|
|
269818
|
+
}
|
|
269819
|
+
}
|
|
269682
269820
|
}
|
|
269683
269821
|
if (!sName || !sName.trim()) {
|
|
269684
269822
|
err(errors, stepNo, void 0, "STEP_MISSING_NAME", `Step ${stepNo} needs a non-empty "name".`, "Give every step a unique descriptive name.");
|
|
@@ -269804,6 +269942,40 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
269804
269942
|
}
|
|
269805
269943
|
}
|
|
269806
269944
|
}
|
|
269945
|
+
if (step.onFailure !== void 0) {
|
|
269946
|
+
if (!["abort", "continue"].includes(String(step.onFailure))) {
|
|
269947
|
+
err(errors, stepNo, sName, "BAD_ON_FAILURE", `Step ${stepNo} "onFailure" must be "abort" or "continue" (got "${step.onFailure}").`, 'Use "abort" (default, stops the workflow) or "continue" (logs failure, proceeds).');
|
|
269948
|
+
}
|
|
269949
|
+
if (step.onFailure === "continue" && step.capture && Object.keys(step.capture).length > 0) {
|
|
269950
|
+
warn(warnings, stepNo, sName, "CONTINUE_WITH_CAPTURE", `Step ${stepNo} uses onFailure:"continue" but has captures \u2014 captures are only saved when the step passes.`, "This is usually fine for optional captures; ensure later steps handle missing memory gracefully.");
|
|
269951
|
+
}
|
|
269952
|
+
}
|
|
269953
|
+
if (step.continueOnStatus !== void 0) {
|
|
269954
|
+
if (!Array.isArray(step.continueOnStatus)) {
|
|
269955
|
+
err(errors, stepNo, sName, "BAD_CONTINUE_ON_STATUS", `Step ${stepNo} "continueOnStatus" must be an array of HTTP status codes.`, "Use [404] or [404, 409].");
|
|
269956
|
+
} else {
|
|
269957
|
+
for (const code of step.continueOnStatus) {
|
|
269958
|
+
if (typeof code !== "number" || code < 100 || code > 599) {
|
|
269959
|
+
err(errors, stepNo, sName, "BAD_CONTINUE_ON_STATUS", `Step ${stepNo} "continueOnStatus" contains invalid status code: ${code}.`, "HTTP status codes must be integers between 100 and 599.");
|
|
269960
|
+
}
|
|
269961
|
+
}
|
|
269962
|
+
if (step.continueOnStatus.some((c3) => c3 >= 500)) {
|
|
269963
|
+
warn(warnings, stepNo, sName, "CONTINUE_ON_5XX", `Step ${stepNo} "continueOnStatus" includes 5xx server error codes \u2014 accepting server errors as pass is unusual.`, "Verify this is intentional (e.g. for idempotency checks or async polling).");
|
|
269964
|
+
}
|
|
269965
|
+
}
|
|
269966
|
+
}
|
|
269967
|
+
if (step.retry !== void 0) {
|
|
269968
|
+
const r = step.retry;
|
|
269969
|
+
if (!r || typeof r !== "object" || Array.isArray(r)) {
|
|
269970
|
+
err(errors, stepNo, sName, "BAD_RETRY", `Step ${stepNo} "retry" must be an object { times, delayMs? }.`, 'Use { "retry": { "times": 3, "delayMs": 1000 } }.');
|
|
269971
|
+
} else {
|
|
269972
|
+
if (typeof r.times !== "number" || r.times < 1) {
|
|
269973
|
+
err(errors, stepNo, sName, "BAD_RETRY", `Step ${stepNo} "retry.times" must be a positive integer.`, "Set retry.times to 1 or more.");
|
|
269974
|
+
} else if (r.times > 5) {
|
|
269975
|
+
warn(warnings, stepNo, sName, "HIGH_RETRY_COUNT", `Step ${stepNo} "retry.times" is ${r.times} \u2014 this may slow down workflows significantly.`, "Keep retry.times \u2264 5 for most use cases.");
|
|
269976
|
+
}
|
|
269977
|
+
}
|
|
269978
|
+
}
|
|
269807
269979
|
for (const field of ["capture", "captureInput"]) {
|
|
269808
269980
|
const cap = step[field];
|
|
269809
269981
|
if (cap && typeof cap === "object" && !Array.isArray(cap)) {
|
|
@@ -284410,6 +284582,284 @@ var require_session_tools = __commonJS({
|
|
|
284410
284582
|
}
|
|
284411
284583
|
});
|
|
284412
284584
|
|
|
284585
|
+
// ../../packages/mcp-server/dist/tools/workflow-designer-tools.js
|
|
284586
|
+
var require_workflow_designer_tools = __commonJS({
|
|
284587
|
+
"../../packages/mcp-server/dist/tools/workflow-designer-tools.js"(exports2) {
|
|
284588
|
+
"use strict";
|
|
284589
|
+
init_cjs_shims();
|
|
284590
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
284591
|
+
exports2.scaffoldWorkflowSchema = void 0;
|
|
284592
|
+
exports2.handleScaffoldWorkflow = handleScaffoldWorkflow;
|
|
284593
|
+
var zod_1 = require("zod");
|
|
284594
|
+
var types_1 = require_types();
|
|
284595
|
+
function fieldToFakerValue(name, field) {
|
|
284596
|
+
if (field.enum && field.enum.length > 0)
|
|
284597
|
+
return field.enum[0];
|
|
284598
|
+
if (field.format === "email")
|
|
284599
|
+
return "{{faker.internet.email}}";
|
|
284600
|
+
if (field.format === "uuid")
|
|
284601
|
+
return "{{faker.string.uuid}}";
|
|
284602
|
+
if (field.format === "date")
|
|
284603
|
+
return "{{faker.date.past}}";
|
|
284604
|
+
if (field.format === "date-time")
|
|
284605
|
+
return "{{faker.date.recent}}";
|
|
284606
|
+
if (field.format === "uri" || field.format === "url")
|
|
284607
|
+
return "{{faker.internet.url}}";
|
|
284608
|
+
if (field.format === "hostname")
|
|
284609
|
+
return "{{faker.internet.domainName}}";
|
|
284610
|
+
if (field.format === "ipv4")
|
|
284611
|
+
return "{{faker.internet.ip}}";
|
|
284612
|
+
const n = name.toLowerCase().replace(/[_-]/g, "");
|
|
284613
|
+
if (n === "email" || n.endsWith("email"))
|
|
284614
|
+
return "{{faker.internet.email}}";
|
|
284615
|
+
if (n.includes("password") || n.includes("pwd"))
|
|
284616
|
+
return "{{faker.internet.password}}";
|
|
284617
|
+
if (n === "phone" || n.includes("phone") || n.includes("mobile"))
|
|
284618
|
+
return "{{faker.phone.number}}";
|
|
284619
|
+
if (n === "name" || n === "fullname" || n === "displayname")
|
|
284620
|
+
return "{{faker.person.fullName}}";
|
|
284621
|
+
if (n === "firstname")
|
|
284622
|
+
return "{{faker.person.firstName}}";
|
|
284623
|
+
if (n === "lastname")
|
|
284624
|
+
return "{{faker.person.lastName}}";
|
|
284625
|
+
if (n === "username")
|
|
284626
|
+
return "{{faker.internet.username}}";
|
|
284627
|
+
if (n === "title")
|
|
284628
|
+
return "{{faker.lorem.sentence}}";
|
|
284629
|
+
if (n === "description" || n === "bio" || n === "summary")
|
|
284630
|
+
return "{{faker.lorem.paragraph}}";
|
|
284631
|
+
if (n === "url" || n.endsWith("url") || n === "avatar")
|
|
284632
|
+
return "{{faker.internet.url}}";
|
|
284633
|
+
if (n === "company" || n.includes("company") || n.includes("organization") || n.includes("workspace"))
|
|
284634
|
+
return "{{faker.company.name}}";
|
|
284635
|
+
if (n === "city")
|
|
284636
|
+
return "{{faker.location.city}}";
|
|
284637
|
+
if (n === "country")
|
|
284638
|
+
return "{{faker.location.country}}";
|
|
284639
|
+
if (n.includes("address"))
|
|
284640
|
+
return "{{faker.location.streetAddress}}";
|
|
284641
|
+
if (n === "zip" || n === "zipcode" || n.includes("postal"))
|
|
284642
|
+
return "{{faker.location.zipCode}}";
|
|
284643
|
+
if (n === "state" || n === "province")
|
|
284644
|
+
return "{{faker.location.state}}";
|
|
284645
|
+
if (n === "latitude" || n === "lat")
|
|
284646
|
+
return "{{faker.location.latitude}}";
|
|
284647
|
+
if (n === "longitude" || n === "lon" || n === "lng")
|
|
284648
|
+
return "{{faker.location.longitude}}";
|
|
284649
|
+
if (n === "color" || n === "colour")
|
|
284650
|
+
return "{{faker.color.human}}";
|
|
284651
|
+
if (n === "amount" || n === "price")
|
|
284652
|
+
return field.min ?? 1;
|
|
284653
|
+
if (n === "quantity" || n === "count" || n === "limit")
|
|
284654
|
+
return field.min ?? 10;
|
|
284655
|
+
if (n === "slug")
|
|
284656
|
+
return "{{faker.lorem.slug}}";
|
|
284657
|
+
if (n === "token" || n === "secret" || n === "key" || n === "otp" || n === "code" || n === "pin")
|
|
284658
|
+
return null;
|
|
284659
|
+
if (field.type === "string")
|
|
284660
|
+
return "{{faker.lorem.word}}";
|
|
284661
|
+
if (field.type === "number" || field.type === "integer")
|
|
284662
|
+
return field.min ?? 1;
|
|
284663
|
+
if (field.type === "boolean")
|
|
284664
|
+
return true;
|
|
284665
|
+
return null;
|
|
284666
|
+
}
|
|
284667
|
+
function tokenMemoryKey(schemeName, fieldPath) {
|
|
284668
|
+
const last = fieldPath.split(".").pop() || schemeName;
|
|
284669
|
+
return last.charAt(0).toLowerCase() + last.slice(1);
|
|
284670
|
+
}
|
|
284671
|
+
function isReuseCandidate(name) {
|
|
284672
|
+
const n = name.toLowerCase().replace(/[_-]/g, "");
|
|
284673
|
+
return ["email", "password", "username", "phone", "name", "id", "slug"].some((k) => n === k || n.endsWith(k));
|
|
284674
|
+
}
|
|
284675
|
+
exports2.scaffoldWorkflowSchema = zod_1.z.object({
|
|
284676
|
+
projectPath: zod_1.z.string().optional().describe("Path to project root or .jetic folder"),
|
|
284677
|
+
endpoints: zod_1.z.array(zod_1.z.union([
|
|
284678
|
+
zod_1.z.string().describe('Method + path string, e.g. "POST /api/auth/login"'),
|
|
284679
|
+
zod_1.z.object({ method: zod_1.z.string(), path: zod_1.z.string() })
|
|
284680
|
+
])).min(1).describe("ORDERED list of endpoints to scaffold into steps. Order matters: token-producer endpoints (e.g. login) must come before consumers (e.g. protected routes)."),
|
|
284681
|
+
workflowName: zod_1.z.string().optional().describe("Suggested workflow name"),
|
|
284682
|
+
environment: zod_1.z.string().optional().describe("Target environment name from model")
|
|
284683
|
+
});
|
|
284684
|
+
function handleScaffoldWorkflow(args) {
|
|
284685
|
+
const { model } = (0, types_1.loadModel)(args.projectPath);
|
|
284686
|
+
const requested = args.endpoints.map((ep) => {
|
|
284687
|
+
if (typeof ep === "string") {
|
|
284688
|
+
const parts = ep.trim().split(/\s+/);
|
|
284689
|
+
return { method: (parts[0] || "GET").toUpperCase(), path: parts.slice(1).join(" ") || "/" };
|
|
284690
|
+
}
|
|
284691
|
+
return { method: ep.method.toUpperCase(), path: ep.path };
|
|
284692
|
+
});
|
|
284693
|
+
const securityMemKeys = {};
|
|
284694
|
+
for (const [schemeName, scheme] of Object.entries(model.securitySchemes || {})) {
|
|
284695
|
+
if (scheme.obtainedFrom) {
|
|
284696
|
+
securityMemKeys[schemeName] = {
|
|
284697
|
+
memKey: tokenMemoryKey(schemeName, scheme.obtainedFrom.field),
|
|
284698
|
+
producedByEndpoint: scheme.obtainedFrom.endpoint.toUpperCase().trim(),
|
|
284699
|
+
fieldPath: scheme.obtainedFrom.field
|
|
284700
|
+
};
|
|
284701
|
+
}
|
|
284702
|
+
}
|
|
284703
|
+
const tokenProducers = {};
|
|
284704
|
+
for (const si of Object.values(securityMemKeys)) {
|
|
284705
|
+
if (!tokenProducers[si.producedByEndpoint])
|
|
284706
|
+
tokenProducers[si.producedByEndpoint] = [];
|
|
284707
|
+
tokenProducers[si.producedByEndpoint].push({ memKey: si.memKey, fieldPath: si.fieldPath });
|
|
284708
|
+
}
|
|
284709
|
+
const draftSteps = [];
|
|
284710
|
+
const missingFields = {};
|
|
284711
|
+
const allHumanKeys = /* @__PURE__ */ new Set();
|
|
284712
|
+
const securityChain = [];
|
|
284713
|
+
const producedKeys = /* @__PURE__ */ new Set();
|
|
284714
|
+
for (let idx = 0; idx < requested.length; idx++) {
|
|
284715
|
+
const req = requested[idx];
|
|
284716
|
+
const routeKey = `${req.method} ${req.path}`;
|
|
284717
|
+
const routeKeyNorm = routeKey.toUpperCase().trim();
|
|
284718
|
+
const endpoint = (model.endpoints || []).find((e) => e.method.toUpperCase() === req.method && e.path === req.path);
|
|
284719
|
+
if (!endpoint) {
|
|
284720
|
+
draftSteps.push({
|
|
284721
|
+
name: routeKey,
|
|
284722
|
+
method: req.method,
|
|
284723
|
+
path: req.path,
|
|
284724
|
+
description: `WARNING: "${routeKey}" not found in model.json \u2014 add it with jetic_add_endpoint first.`,
|
|
284725
|
+
expectStatus: 200
|
|
284726
|
+
});
|
|
284727
|
+
missingFields[routeKey] = [`Endpoint "${routeKey}" not found in model.json.`];
|
|
284728
|
+
continue;
|
|
284729
|
+
}
|
|
284730
|
+
const step = {
|
|
284731
|
+
name: endpoint.name || routeKey,
|
|
284732
|
+
method: req.method,
|
|
284733
|
+
path: endpoint.path
|
|
284734
|
+
};
|
|
284735
|
+
if (endpoint.description)
|
|
284736
|
+
step.description = endpoint.description;
|
|
284737
|
+
const missing = [];
|
|
284738
|
+
if (endpoint.requestBody?.fields && Object.keys(endpoint.requestBody.fields).length > 0) {
|
|
284739
|
+
const body = {};
|
|
284740
|
+
for (const [fname, fdef] of Object.entries(endpoint.requestBody.fields)) {
|
|
284741
|
+
let fromMem = null;
|
|
284742
|
+
if (endpoint.consumes) {
|
|
284743
|
+
for (const c3 of endpoint.consumes) {
|
|
284744
|
+
if (c3.usedAs === fname || c3.variable === fname) {
|
|
284745
|
+
const k = `workflow:${c3.variable}`;
|
|
284746
|
+
if (producedKeys.has(k)) {
|
|
284747
|
+
fromMem = k;
|
|
284748
|
+
break;
|
|
284749
|
+
}
|
|
284750
|
+
}
|
|
284751
|
+
}
|
|
284752
|
+
}
|
|
284753
|
+
if (!fromMem) {
|
|
284754
|
+
for (const pk of producedKeys) {
|
|
284755
|
+
const pkName = pk.split(":").pop() || "";
|
|
284756
|
+
if (pkName.toLowerCase() === fname.toLowerCase()) {
|
|
284757
|
+
fromMem = pk;
|
|
284758
|
+
break;
|
|
284759
|
+
}
|
|
284760
|
+
}
|
|
284761
|
+
}
|
|
284762
|
+
if (fromMem) {
|
|
284763
|
+
body[fname] = `{{${fromMem}}}`;
|
|
284764
|
+
continue;
|
|
284765
|
+
}
|
|
284766
|
+
const val = fieldToFakerValue(fname, fdef);
|
|
284767
|
+
if (val !== null) {
|
|
284768
|
+
body[fname] = val;
|
|
284769
|
+
} else {
|
|
284770
|
+
body[fname] = `{{human:${fname}}}`;
|
|
284771
|
+
allHumanKeys.add(fname);
|
|
284772
|
+
missing.push(`"${fname}" (type: ${fdef.type || "unknown"}) \u2014 no auto-mapping. Using {{human:${fname}}} (will pause for interactive input at runtime).`);
|
|
284773
|
+
}
|
|
284774
|
+
}
|
|
284775
|
+
step.body = body;
|
|
284776
|
+
const ci = {};
|
|
284777
|
+
for (const [fname, fdef] of Object.entries(endpoint.requestBody.fields)) {
|
|
284778
|
+
if (fdef.required && isReuseCandidate(fname)) {
|
|
284779
|
+
const k = `workflow:${fname}`;
|
|
284780
|
+
if (!producedKeys.has(k)) {
|
|
284781
|
+
ci[k] = fname;
|
|
284782
|
+
producedKeys.add(k);
|
|
284783
|
+
}
|
|
284784
|
+
}
|
|
284785
|
+
}
|
|
284786
|
+
if (Object.keys(ci).length > 0)
|
|
284787
|
+
step.captureInput = ci;
|
|
284788
|
+
}
|
|
284789
|
+
if (endpoint.security && endpoint.security.length > 0) {
|
|
284790
|
+
const inject = {};
|
|
284791
|
+
for (const sec of endpoint.security) {
|
|
284792
|
+
if (sec.scheme === "none")
|
|
284793
|
+
continue;
|
|
284794
|
+
const si = securityMemKeys[sec.scheme];
|
|
284795
|
+
if (!si) {
|
|
284796
|
+
missing.push(`Security scheme "${sec.scheme}" has no obtainedFrom in model.securitySchemes \u2014 add inject manually.`);
|
|
284797
|
+
continue;
|
|
284798
|
+
}
|
|
284799
|
+
const tokenKey = `workflow:${si.memKey}`;
|
|
284800
|
+
const available = producedKeys.has(tokenKey) || requested.slice(0, idx).some((r) => `${r.method} ${r.path}`.toUpperCase().trim() === si.producedByEndpoint);
|
|
284801
|
+
if (available) {
|
|
284802
|
+
const rawScheme = model.securitySchemes?.[sec.scheme];
|
|
284803
|
+
const prefix = rawScheme?.valuePrefix != null ? rawScheme.valuePrefix : rawScheme?.scheme?.toLowerCase() === "bearer" ? "Bearer " : "";
|
|
284804
|
+
inject["header:Authorization"] = `${prefix}{{${tokenKey}}}`;
|
|
284805
|
+
securityChain.push(`Step ${idx + 1} (${routeKey}) \u2190 inject ${tokenKey} (produced by ${si.producedByEndpoint})`);
|
|
284806
|
+
} else {
|
|
284807
|
+
missing.push(`Security scheme "${sec.scheme}" requires "${tokenKey}" from "${si.producedByEndpoint}" \u2014 place that endpoint earlier in the list.`);
|
|
284808
|
+
}
|
|
284809
|
+
}
|
|
284810
|
+
if (Object.keys(inject).length > 0)
|
|
284811
|
+
step.inject = inject;
|
|
284812
|
+
}
|
|
284813
|
+
const capture = {};
|
|
284814
|
+
if (endpoint.produces) {
|
|
284815
|
+
for (const p of endpoint.produces) {
|
|
284816
|
+
if (p.variable && p.responseField) {
|
|
284817
|
+
const k = `workflow:${p.variable}`;
|
|
284818
|
+
capture[k] = p.responseField;
|
|
284819
|
+
producedKeys.add(k);
|
|
284820
|
+
}
|
|
284821
|
+
}
|
|
284822
|
+
}
|
|
284823
|
+
if (tokenProducers[routeKeyNorm]) {
|
|
284824
|
+
for (const { memKey, fieldPath } of tokenProducers[routeKeyNorm]) {
|
|
284825
|
+
const k = `workflow:${memKey}`;
|
|
284826
|
+
if (!capture[k]) {
|
|
284827
|
+
capture[k] = fieldPath;
|
|
284828
|
+
producedKeys.add(k);
|
|
284829
|
+
securityChain.push(`Step ${idx + 1} (${routeKey}) \u2192 produces ${k} from response.${fieldPath}`);
|
|
284830
|
+
}
|
|
284831
|
+
}
|
|
284832
|
+
}
|
|
284833
|
+
if (Object.keys(capture).length > 0)
|
|
284834
|
+
step.capture = capture;
|
|
284835
|
+
const codes = Object.keys(endpoint.responses || {}).map(Number).filter(Boolean);
|
|
284836
|
+
const successCode = codes.find((c3) => c3 >= 200 && c3 < 300);
|
|
284837
|
+
step.expectStatus = successCode ?? 200;
|
|
284838
|
+
if (missing.length > 0)
|
|
284839
|
+
missingFields[routeKey] = missing;
|
|
284840
|
+
draftSteps.push(step);
|
|
284841
|
+
}
|
|
284842
|
+
const defaultName = args.workflowName || requested.map((r) => r.path.split("/").filter(Boolean).pop() || r.path).join(" \u2192 ") + " workflow";
|
|
284843
|
+
return {
|
|
284844
|
+
workflowName: defaultName,
|
|
284845
|
+
...args.environment ? { environment: args.environment } : {},
|
|
284846
|
+
draftSteps,
|
|
284847
|
+
stepsCount: draftSteps.length,
|
|
284848
|
+
missingFields: Object.keys(missingFields).length > 0 ? missingFields : void 0,
|
|
284849
|
+
humanKeys: [...allHumanKeys].sort(),
|
|
284850
|
+
securityChain: securityChain.length > 0 ? securityChain : void 0,
|
|
284851
|
+
instructions: [
|
|
284852
|
+
"1. Review draftSteps. Resolve any missingFields entries before proceeding.",
|
|
284853
|
+
"2. humanKeys will pause the workflow at runtime for interactive input \u2014 they are intentional when needed.",
|
|
284854
|
+
'3. Validate: jetic_validate_workflow { definition: { name: "<workflowName>", steps: <draftSteps> } }',
|
|
284855
|
+
'4. Save: jetic_create_workflow { name: "<workflowName>", steps: <draftSteps> }',
|
|
284856
|
+
'5. Run: jetic_simulate_workflow { workflow: "<slug>" }'
|
|
284857
|
+
].join("\n")
|
|
284858
|
+
};
|
|
284859
|
+
}
|
|
284860
|
+
}
|
|
284861
|
+
});
|
|
284862
|
+
|
|
284413
284863
|
// ../../packages/mcp-server/dist/server.js
|
|
284414
284864
|
var require_server3 = __commonJS({
|
|
284415
284865
|
"../../packages/mcp-server/dist/server.js"(exports2) {
|
|
@@ -284471,6 +284921,7 @@ var require_server3 = __commonJS({
|
|
|
284471
284921
|
var workflow_authoring_tools_1 = require_workflow_authoring_tools();
|
|
284472
284922
|
var project_tools_1 = require_project_tools();
|
|
284473
284923
|
var session_tools_1 = require_session_tools();
|
|
284924
|
+
var workflow_designer_tools_1 = require_workflow_designer_tools();
|
|
284474
284925
|
exports2.JETIC_MCP_SERVER_NAME = "jetic-mcp-server";
|
|
284475
284926
|
function getJeticMcpVersion() {
|
|
284476
284927
|
try {
|
|
@@ -284492,7 +284943,7 @@ var require_server3 = __commonJS({
|
|
|
284492
284943
|
"\u2501\u2501 PHASE 2 \u2014 SCAN \u2501\u2501",
|
|
284493
284944
|
"Populate the model with endpoints:",
|
|
284494
284945
|
" \u2022 Express+TypeScript (tsconfig.json present): call `jetic_scan` (static, keyless, no AI provider needed).",
|
|
284495
|
-
" \u2022 Any other stack or scan gaps: read
|
|
284946
|
+
" \u2022 Any other stack or scan gaps: read source code and call `jetic_add_endpoint` for every route (middleware chains, security, constraints, pagination, produces/consumes).",
|
|
284496
284947
|
"Gate: DO NOT advance to Phase 3 until endpointsCount > 0.",
|
|
284497
284948
|
"",
|
|
284498
284949
|
"\u2501\u2501 PHASE 3 \u2014 VERIFY \u2501\u2501",
|
|
@@ -284501,14 +284952,55 @@ var require_server3 = __commonJS({
|
|
|
284501
284952
|
" \u2022 Add at least one `tag` per endpoint.",
|
|
284502
284953
|
" \u2022 Add `description` to every parameter.",
|
|
284503
284954
|
" \u2022 Add `description` to every response status code.",
|
|
284504
|
-
"Use `jetic_update_endpoint` to fill gaps. Call `jetic_verify_model` again after each batch
|
|
284955
|
+
"Use `jetic_update_endpoint` to fill gaps. Call `jetic_verify_model` again after each batch.",
|
|
284505
284956
|
"Gate: DO NOT advance to Phase 4 until jetic_get_session_phase returns phase=4 (modelIsValid AND allFieldsFilled both true).",
|
|
284506
284957
|
"",
|
|
284507
284958
|
"\u2501\u2501 PHASE 4 \u2014 SIMULATE \u2501\u2501",
|
|
284508
|
-
'STOP. Ask the user: "What simulation or workflow do you want to build?"',
|
|
284509
|
-
"Wait for their explicit answer. Then
|
|
284510
|
-
"
|
|
284511
|
-
"
|
|
284959
|
+
'STOP. Ask the user: "What simulation or workflow do you want to build or test?"',
|
|
284960
|
+
"Wait for their explicit answer. Then follow these sub-steps IN ORDER:",
|
|
284961
|
+
"",
|
|
284962
|
+
"STEP 4a \u2014 SCAFFOLD",
|
|
284963
|
+
" Call `jetic_scaffold_workflow` with the ORDERED list of endpoints the user wants to exercise.",
|
|
284964
|
+
" It reads model.json and returns `draftSteps` with:",
|
|
284965
|
+
" \u2022 body derived from requestBody.fields (faker-mapped by type / format / field name)",
|
|
284966
|
+
" \u2022 inject derived from securitySchemes.obtainedFrom (token producer \u2192 consumer chain)",
|
|
284967
|
+
" \u2022 capture derived from endpoint.produces",
|
|
284968
|
+
" \u2022 captureInput for required fields that later steps will reuse",
|
|
284969
|
+
" Review draftSteps. Fix any `missingFields` entries before proceeding.",
|
|
284970
|
+
"",
|
|
284971
|
+
"STEP 4b \u2014 HUMAN INPUT PROTOCOL",
|
|
284972
|
+
" Use {{human:key}} ONLY when the value is truly unknowable before runtime:",
|
|
284973
|
+
" \u2022 One-time secrets: OTP codes, 2FA tokens, CAPTCHA answers",
|
|
284974
|
+
" \u2022 Real credentials that must be typed at execution time",
|
|
284975
|
+
" NEVER use {{human:key}} for things faker can generate (emails, names, UUIDs, passwords).",
|
|
284976
|
+
" The scaffold tool already handles faker mapping \u2014 only override to {{human:key}} when a",
|
|
284977
|
+
" field appears in missingFields AND genuinely requires interactive user input.",
|
|
284978
|
+
"",
|
|
284979
|
+
"STEP 4c \u2014 CONDITIONS (branching / skipping steps)",
|
|
284980
|
+
" Add `condition` to skip a step when a memory value does not match:",
|
|
284981
|
+
' { "condition": { "if": "{{workflow:userRole}}", "equals": "admin" } }',
|
|
284982
|
+
" Operators: equals, notEquals, contains, exists (bool), greaterThan, lessThan, in (string[]).",
|
|
284983
|
+
" Skipped steps count as passed \u2014 the workflow continues.",
|
|
284984
|
+
' Pair with onFailure:"continue" for negative/branching flows.',
|
|
284985
|
+
"",
|
|
284986
|
+
"STEP 4d \u2014 FAILURE MODES & RETRY",
|
|
284987
|
+
" onFailure (per step):",
|
|
284988
|
+
' \u2022 "abort" (default) \u2014 stop the workflow on first failure',
|
|
284989
|
+
' \u2022 "continue" \u2014 log the failure and proceed (captures are skipped for that step)',
|
|
284990
|
+
" continueOnStatus (per step): extra codes treated as pass, e.g. [404] or [409].",
|
|
284991
|
+
" retry (per step OR workflow-level global default):",
|
|
284992
|
+
' \u2022 { "times": 3, "delayMs": 1000 } \u2014 retries up to 3\xD7 with 1 s between each attempt',
|
|
284993
|
+
" \u2022 Per-step retry overrides the workflow-level default. Keep times \u2264 5.",
|
|
284994
|
+
"",
|
|
284995
|
+
"STEP 4e \u2014 VALIDATE (dry run, no save)",
|
|
284996
|
+
' jetic_validate_workflow { "definition": { "name": "...", "steps": [...] } }',
|
|
284997
|
+
" Fix all errors. Warnings are informational.",
|
|
284998
|
+
"",
|
|
284999
|
+
"STEP 4f \u2014 SAVE",
|
|
285000
|
+
' jetic_create_workflow { "name": "...", "steps": [...] }',
|
|
285001
|
+
"",
|
|
285002
|
+
"STEP 4g \u2014 RUN",
|
|
285003
|
+
' jetic_simulate_workflow { "workflow": "<slug>" }',
|
|
284512
285004
|
"",
|
|
284513
285005
|
"\u2501\u2501 GENERAL RULES \u2501\u2501",
|
|
284514
285006
|
"No AI-provider setup is needed: never ask for OpenRouter/OpenAI keys and never use `jetic config ai`.",
|
|
@@ -284576,8 +285068,9 @@ var require_server3 = __commonJS({
|
|
|
284576
285068
|
register("jetic_list_workflows", "List workflows", "Lists multi-step integration test workflows from .jetic/workflows/*.json, .jetic/workflow.json, or embedded model.json workflows.", workflow_tools_1.listWorkflowsSchema.shape, READ_ONLY, workflow_tools_1.handleListWorkflows);
|
|
284577
285069
|
register("jetic_simulate_workflow", "Simulate workflow", "Executes a full end-to-end multi-step workflow simulation with dynamic memory capture, header injection, and schema validation against the live server.", workflow_tools_1.simulateWorkflowSchema.shape, READ_LIVE, workflow_tools_1.handleSimulateWorkflow);
|
|
284578
285070
|
register("jetic_simulate", "Simulate workflow (alias)", "Alias of jetic_simulate_workflow: runs a multi-step user-journey workflow against the live server. Prefer jetic_simulate_workflow in new prompts.", workflow_tools_1.simulateWorkflowSchema.shape, READ_LIVE, workflow_tools_1.handleSimulateWorkflow);
|
|
284579
|
-
register("
|
|
284580
|
-
register("
|
|
285071
|
+
register("jetic_scaffold_workflow", "Scaffold workflow steps from model", "PHASE 4a \u2014 FIRST STEP when building a workflow. Given an ordered endpoint list, reads model.json and returns ready-made `draftSteps`: body fields faker-mapped by type/format/name, inject derived from securitySchemes.obtainedFrom token-producer chains, capture from endpoint.produces, captureInput for reusable credentials. Returns missingFields (fields needing human input or manual fix) and securityChain analysis. Does NOT save. Review draftSteps, then call jetic_validate_workflow \u2192 jetic_create_workflow.", workflow_designer_tools_1.scaffoldWorkflowSchema.shape, READ_ONLY, workflow_designer_tools_1.handleScaffoldWorkflow);
|
|
285072
|
+
register("jetic_validate_workflow", "Validate workflow (dry run)", "Deep-validates a workflow WITHOUT saving: step structure, endpoint cross-checks against model.json, full memory data-flow analysis. Now also validates condition (including condition.if template refs), onFailure, continueOnStatus, and retry fields. Accepts a saved workflow slug/name or an inline definition.", workflow_authoring_tools_1.validateWorkflowSchema.shape, READ_ONLY, workflow_authoring_tools_1.handleValidateWorkflow);
|
|
285073
|
+
register("jetic_create_workflow", "Create workflow", "Creates a .jetic/workflows/<slug>.json workflow. Supports: condition (skip steps based on memory), onFailure (abort|continue), continueOnStatus (extra pass codes), retry (per-step or global default). Refuses to save on validation errors.", workflow_authoring_tools_1.createWorkflowSchema.shape, WRITES_MODEL, workflow_authoring_tools_1.handleCreateWorkflow);
|
|
284581
285074
|
register("jetic_update_workflow", "Update workflow", "Updates a saved file workflow: rename, description/environment, full steps replacement, or appendSteps. Validates before writing and refuses to save on errors.", workflow_authoring_tools_1.updateWorkflowSchema.shape, WRITES_MODEL, workflow_authoring_tools_1.handleUpdateWorkflow);
|
|
284582
285075
|
register("jetic_delete_workflow", "Delete workflow", "Deletes a saved .jetic/workflows/*.json workflow file by slug, name, or path. Destructive: confirm with the user before calling. Cannot delete model.json-embedded workflows.", workflow_authoring_tools_1.deleteWorkflowSchema.shape, DESTRUCTIVE, workflow_authoring_tools_1.handleDeleteWorkflow);
|
|
284583
285076
|
return server;
|
|
@@ -287476,7 +287969,7 @@ var mcpCommand = new import_commander10.Command("mcp").description("Launch Model
|
|
|
287476
287969
|
|
|
287477
287970
|
// src/index.ts
|
|
287478
287971
|
var program = new import_commander11.Command();
|
|
287479
|
-
program.name("jetic").description("AI-Native API Behavior Testing").version("0.2.
|
|
287972
|
+
program.name("jetic").description("AI-Native API Behavior Testing").version("0.2.3");
|
|
287480
287973
|
program.addCommand(initCommand);
|
|
287481
287974
|
program.addCommand(scanCommand);
|
|
287482
287975
|
program.addCommand(inspectCommand);
|