jetic-cli 0.2.2 → 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.
Files changed (2) hide show
  1. package/dist/index.js +553 -60
  2. 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 || 200;
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
- try {
267999
- const fetchOptions = {
268000
- method: step.method.toUpperCase(),
268001
- headers: {
268002
- "Content-Type": "application/json",
268003
- ...injectedHeaders
268004
- }
268005
- };
268006
- if (!["GET", "HEAD"].includes(step.method.toUpperCase()) && Object.keys(finalBody).length > 0) {
268007
- fetchOptions.body = JSON.stringify(finalBody);
268008
- }
268009
- let finalUrl = fullUrl;
268010
- if (["GET", "HEAD"].includes(step.method.toUpperCase()) && Object.keys(finalBody).length > 0) {
268011
- const params = new URLSearchParams();
268012
- for (const [k, v] of Object.entries(finalBody)) {
268013
- if (v !== void 0 && v !== null)
268014
- params.set(k, String(v));
268015
- }
268016
- finalUrl = `${fullUrl}?${params.toString()}`;
268017
- }
268018
- const resp = await fetch(finalUrl, fetchOptions);
268019
- responseStatus = resp.status;
268020
- const ct = resp.headers.get("content-type") || "";
268021
- if (ct.includes("application/json")) {
268022
- try {
268023
- responseBody = await resp.json();
268024
- } catch {
268025
- responseBody = null;
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
- } else {
268028
- responseBody = await resp.text();
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: stepPassed,
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 (human pauses for interactive input at runtime)"),
269558
- inject: zod_1.z.record(zod_1.z.string()).optional().describe('Memory/header injection: "header:Name" or "body:field" (or bare header name) -> "scope:key" or "{{template}}" ({{human:key}} supported)'),
269559
- capture: zod_1.z.record(zod_1.z.string()).optional().describe('Save RESPONSE fields to memory: "scope:key" -> "dot.path.in.response"'),
269560
- captureInput: zod_1.z.record(zod_1.z.string()).optional().describe('Save REQUEST body fields to memory BEFORE sending: "scope:key" -> "bodyField"'),
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's default strip
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
- warn(warnings, stepNo, sName, "CONDITION_IGNORED", `"condition" is accepted but currently ignored by the simulator \u2014 the step always runs.`, "Encode branching as separate workflows, or gate with expectStatus per step.");
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 the source code and call `jetic_add_endpoint` for every route (include middleware chains, security, constraints, pagination, produces/consumes).",
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 of updates.",
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 and only then: author the workflow with `jetic_validate_workflow` (dry run) \u2192 `jetic_create_workflow` \u2192 `jetic_simulate_workflow`.",
284510
- "Chain steps using capture/captureInput (producer) + inject or {{workflow:key}} (consumer). Use {{human:key}} ONLY for values unknowable beforehand (OTP codes, real secrets).",
284511
- "Never start Phase 4 without an explicit user-specified goal.",
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("jetic_validate_workflow", "Validate workflow", "Deep-validates a workflow WITHOUT saving or running it: step structure, endpoint cross-checks against model.json, and memory data-flow analysis (every {{scope:key}} except {{human:key}} must be captured by an earlier step). Accepts a saved workflow reference or an inline definition for pre-save dry runs.", workflow_authoring_tools_1.validateWorkflowSchema.shape, READ_ONLY, workflow_authoring_tools_1.handleValidateWorkflow);
284580
- register("jetic_create_workflow", "Create workflow", "Creates a .jetic/workflows/<slug>.json workflow from ordered steps with capture/inject memory chaining. Refuses to save when validation errors exist (error messages say exactly how to fix); use validateOnly for a dry run. Can also migrate an embedded model.json workflow via fromModelWorkflow.", workflow_authoring_tools_1.createWorkflowSchema.shape, WRITES_MODEL, workflow_authoring_tools_1.handleCreateWorkflow);
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.1");
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jetic-cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "AI-Native API Behavior Testing & Discovery Tool — CLI and Local Studio Dashboard",
5
5
  "main": "dist/index.js",
6
6
  "bin": {