jetic-cli 0.1.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +101 -1
- package/dist/dashboard/assets/index-8Xh271uc.js +384 -0
- package/dist/dashboard/assets/index-ANQK1AeT.css +1 -0
- package/dist/dashboard/index.html +2 -2
- package/dist/index.js +606 -133
- package/package.json +3 -2
- package/dist/dashboard/assets/index-DEc0dXNe.css +0 -1
- package/dist/dashboard/assets/index-DZwpY2T2.js +0 -384
package/dist/index.js
CHANGED
|
@@ -268088,6 +268088,13 @@ var require_workflow_simulator = __commonJS({
|
|
|
268088
268088
|
const colonIdx = expr.indexOf(":");
|
|
268089
268089
|
const scope = expr.slice(0, colonIdx);
|
|
268090
268090
|
const key = expr.slice(colonIdx + 1);
|
|
268091
|
+
if (scope === "human") {
|
|
268092
|
+
const envVal = process.env[`JETIC_HUMAN_${key.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`];
|
|
268093
|
+
if (envVal !== void 0 && envVal !== "") {
|
|
268094
|
+
replacements.push({ placeholder: match[0], resolved: envVal });
|
|
268095
|
+
continue;
|
|
268096
|
+
}
|
|
268097
|
+
}
|
|
268091
268098
|
const mem = new memory_1.JeticMemory({ scope });
|
|
268092
268099
|
const memVal = await mem.get(key);
|
|
268093
268100
|
replacements.push({ placeholder: match[0], resolved: memVal != null ? String(memVal) : "" });
|
|
@@ -269477,8 +269484,8 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
269477
269484
|
method: zod_1.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]).describe("HTTP method"),
|
|
269478
269485
|
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)"),
|
|
269479
269486
|
description: zod_1.z.string().optional().describe("What this step does"),
|
|
269480
|
-
body: zod_1.z.record(zod_1.z.any()).optional().describe("Request payload. Values support {{faker.*}}, {{scope:key}} and {{
|
|
269481
|
-
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}}"'),
|
|
269487
|
+
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)"),
|
|
269488
|
+
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)'),
|
|
269482
269489
|
capture: zod_1.z.record(zod_1.z.string()).optional().describe('Save RESPONSE fields to memory: "scope:key" -> "dot.path.in.response"'),
|
|
269483
269490
|
captureInput: zod_1.z.record(zod_1.z.string()).optional().describe('Save REQUEST body fields to memory BEFORE sending: "scope:key" -> "bodyField"'),
|
|
269484
269491
|
expectStatus: zod_1.z.number().int().positive().optional().describe("Expected HTTP status (default 200; any 2xx passes when expecting 2xx)")
|
|
@@ -269584,6 +269591,7 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
269584
269591
|
}
|
|
269585
269592
|
const seenNames = /* @__PURE__ */ new Set();
|
|
269586
269593
|
const defined = /* @__PURE__ */ new Set();
|
|
269594
|
+
const humanKeys = /* @__PURE__ */ new Set();
|
|
269587
269595
|
wf.steps.forEach((rawStep, i) => {
|
|
269588
269596
|
const stepNo = i + 1;
|
|
269589
269597
|
const sName = typeof rawStep?.name === "string" ? rawStep.name : void 0;
|
|
@@ -269685,6 +269693,11 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
269685
269693
|
err(errors, stepNo, sName, "BAD_TEMPLATE", `"{{${ref}}}" has an empty key.`, "Use {{scope:key}} with a real key.");
|
|
269686
269694
|
continue;
|
|
269687
269695
|
}
|
|
269696
|
+
if (scope === "human") {
|
|
269697
|
+
if (key)
|
|
269698
|
+
humanKeys.add(key);
|
|
269699
|
+
continue;
|
|
269700
|
+
}
|
|
269688
269701
|
const full = `${scope}:${key}`;
|
|
269689
269702
|
if (!defined.has(full) && !persistedMemory.has(full)) {
|
|
269690
269703
|
err(errors, stepNo, sName, "UNDEFINED_MEMORY_REF", `"{{${ref}}}" in step ${stepNo} is never captured by an earlier step and is not in memory.json \u2014 it resolves to "".`, `Capture "${full}" in an earlier step (capture/captureInput), or seed it first.`);
|
|
@@ -269732,15 +269745,16 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
269732
269745
|
}
|
|
269733
269746
|
}
|
|
269734
269747
|
});
|
|
269735
|
-
return toReport(errors, warnings);
|
|
269748
|
+
return toReport(errors, warnings, humanKeys);
|
|
269736
269749
|
}
|
|
269737
|
-
function toReport(errors, warnings) {
|
|
269750
|
+
function toReport(errors, warnings, humanKeys) {
|
|
269738
269751
|
return {
|
|
269739
269752
|
valid: errors.length === 0,
|
|
269740
269753
|
errorsCount: errors.length,
|
|
269741
269754
|
warningsCount: warnings.length,
|
|
269742
269755
|
errors,
|
|
269743
|
-
warnings
|
|
269756
|
+
warnings,
|
|
269757
|
+
humanKeys: [...humanKeys || []].sort()
|
|
269744
269758
|
};
|
|
269745
269759
|
}
|
|
269746
269760
|
function slugifyWorkflowName(name) {
|
|
@@ -269897,6 +269911,7 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
269897
269911
|
slug,
|
|
269898
269912
|
filePath,
|
|
269899
269913
|
stepsCount: def.steps.length,
|
|
269914
|
+
needsHuman: report.humanKeys,
|
|
269900
269915
|
validation: { ...report, warnings }
|
|
269901
269916
|
};
|
|
269902
269917
|
}
|
|
@@ -269911,6 +269926,10 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
269911
269926
|
slug,
|
|
269912
269927
|
filePath,
|
|
269913
269928
|
stepsCount: def.steps.length,
|
|
269929
|
+
needsHuman: report.humanKeys,
|
|
269930
|
+
...report.humanKeys.length > 0 ? {
|
|
269931
|
+
humanNote: `Steps need interactive human input for: ${report.humanKeys.map((k) => `{{human:${k}}}`).join(", ")}. Runners pause for it (CLI terminal prompt / dashboard dialog); pre-seed with ${report.humanKeys.map((k) => `JETIC_HUMAN_${k.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`).join(", ")} env or human: memory. Tell the user.`
|
|
269932
|
+
} : {},
|
|
269914
269933
|
validation: { ...report, warnings },
|
|
269915
269934
|
next: `Run it with jetic_simulate_workflow { "workflow": "${slug}" }.`
|
|
269916
269935
|
};
|
|
@@ -269948,7 +269967,7 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
269948
269967
|
` + report.errors.map((e) => `- [step ${e.stepIndex ?? "-"}] ${e.message} Fix: ${e.fix}`).join("\n"));
|
|
269949
269968
|
}
|
|
269950
269969
|
if (args.validateOnly === true) {
|
|
269951
|
-
return { success: true, action: "validated", saved: false, slug: resolved.slug, filePath: resolved.filePath, stepsCount: def.steps.length, validation: report };
|
|
269970
|
+
return { success: true, action: "validated", saved: false, slug: resolved.slug, filePath: resolved.filePath, stepsCount: def.steps.length, needsHuman: report.humanKeys, validation: report };
|
|
269952
269971
|
}
|
|
269953
269972
|
writeWorkflowFile(resolved.filePath, def);
|
|
269954
269973
|
return {
|
|
@@ -269957,6 +269976,7 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
269957
269976
|
slug: resolved.slug,
|
|
269958
269977
|
filePath: resolved.filePath,
|
|
269959
269978
|
stepsCount: def.steps.length,
|
|
269979
|
+
needsHuman: report.humanKeys,
|
|
269960
269980
|
validation: report,
|
|
269961
269981
|
next: `Run it with jetic_simulate_workflow { "workflow": "${resolved.slug}" }.`
|
|
269962
269982
|
};
|
|
@@ -269981,6 +270001,229 @@ var require_workflow_authoring_tools = __commonJS({
|
|
|
269981
270001
|
}
|
|
269982
270002
|
});
|
|
269983
270003
|
|
|
270004
|
+
// ../../packages/mcp-server/dist/tools/project-tools.js
|
|
270005
|
+
var require_project_tools = __commonJS({
|
|
270006
|
+
"../../packages/mcp-server/dist/tools/project-tools.js"(exports2) {
|
|
270007
|
+
"use strict";
|
|
270008
|
+
init_cjs_shims();
|
|
270009
|
+
var __createBinding = exports2 && exports2.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
270010
|
+
if (k2 === void 0) k2 = k;
|
|
270011
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
270012
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
270013
|
+
desc = { enumerable: true, get: function() {
|
|
270014
|
+
return m[k];
|
|
270015
|
+
} };
|
|
270016
|
+
}
|
|
270017
|
+
Object.defineProperty(o, k2, desc);
|
|
270018
|
+
}) : (function(o, m, k, k2) {
|
|
270019
|
+
if (k2 === void 0) k2 = k;
|
|
270020
|
+
o[k2] = m[k];
|
|
270021
|
+
}));
|
|
270022
|
+
var __setModuleDefault = exports2 && exports2.__setModuleDefault || (Object.create ? (function(o, v) {
|
|
270023
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
270024
|
+
}) : function(o, v) {
|
|
270025
|
+
o["default"] = v;
|
|
270026
|
+
});
|
|
270027
|
+
var __importStar = exports2 && exports2.__importStar || /* @__PURE__ */ (function() {
|
|
270028
|
+
var ownKeys = function(o) {
|
|
270029
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
270030
|
+
var ar = [];
|
|
270031
|
+
for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
|
|
270032
|
+
return ar;
|
|
270033
|
+
};
|
|
270034
|
+
return ownKeys(o);
|
|
270035
|
+
};
|
|
270036
|
+
return function(mod) {
|
|
270037
|
+
if (mod && mod.__esModule) return mod;
|
|
270038
|
+
var result = {};
|
|
270039
|
+
if (mod != null) {
|
|
270040
|
+
for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
270041
|
+
}
|
|
270042
|
+
__setModuleDefault(result, mod);
|
|
270043
|
+
return result;
|
|
270044
|
+
};
|
|
270045
|
+
})();
|
|
270046
|
+
Object.defineProperty(exports2, "__esModule", { value: true });
|
|
270047
|
+
exports2.scanProjectSchema = exports2.initProjectSchema = void 0;
|
|
270048
|
+
exports2.handleInitProject = handleInitProject;
|
|
270049
|
+
exports2.handleScanProject = handleScanProject;
|
|
270050
|
+
var zod_1 = require("zod");
|
|
270051
|
+
var fs4 = __importStar(require("fs"));
|
|
270052
|
+
var path7 = __importStar(require("path"));
|
|
270053
|
+
var types_1 = require_types();
|
|
270054
|
+
var model_1 = require_dist2();
|
|
270055
|
+
exports2.initProjectSchema = zod_1.z.object({
|
|
270056
|
+
projectPath: zod_1.z.string().optional().describe("Path to the project root to initialize (defaults to JETIC_PROJECT_PATH / cwd)"),
|
|
270057
|
+
projectName: zod_1.z.string().optional().describe("Project name (default: directory basename)"),
|
|
270058
|
+
language: zod_1.z.string().optional().describe('Primary language (default: "typescript" when tsconfig.json exists, else omitted)'),
|
|
270059
|
+
framework: zod_1.z.string().optional().describe('Framework (default: "express" when express is a dependency, else omitted)'),
|
|
270060
|
+
environmentName: zod_1.z.string().optional().describe('First environment name (default "local")'),
|
|
270061
|
+
baseUrl: zod_1.z.string().optional().describe('First environment base URL (default "http://localhost:3000")'),
|
|
270062
|
+
overwrite: zod_1.z.boolean().optional().describe("Replace an existing .jetic/model.json (default false \u2014 never clobber silently)")
|
|
270063
|
+
});
|
|
270064
|
+
function detectStack(projectRoot) {
|
|
270065
|
+
let hasExpress = false;
|
|
270066
|
+
let hasPackageJson = false;
|
|
270067
|
+
try {
|
|
270068
|
+
const pkgPath = path7.join(projectRoot, "package.json");
|
|
270069
|
+
if (fs4.existsSync(pkgPath)) {
|
|
270070
|
+
hasPackageJson = true;
|
|
270071
|
+
const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
|
|
270072
|
+
const deps = { ...pkg.dependencies || {}, ...pkg.devDependencies || {}, ...pkg.peerDependencies || {} };
|
|
270073
|
+
hasExpress = "express" in deps;
|
|
270074
|
+
}
|
|
270075
|
+
} catch {
|
|
270076
|
+
}
|
|
270077
|
+
return { hasPackageJson, hasExpress, hasTsConfig: fs4.existsSync(path7.join(projectRoot, "tsconfig.json")) };
|
|
270078
|
+
}
|
|
270079
|
+
function handleInitProject(args) {
|
|
270080
|
+
const filePath = (0, types_1.resolveModelPath)(args.projectPath);
|
|
270081
|
+
const projectRoot = (0, types_1.projectRootOfModel)(filePath);
|
|
270082
|
+
const overwrite = args.overwrite === true;
|
|
270083
|
+
if (fs4.existsSync(filePath) && !overwrite) {
|
|
270084
|
+
const { model: model2 } = (0, types_1.loadModel)(args.projectPath);
|
|
270085
|
+
return {
|
|
270086
|
+
success: false,
|
|
270087
|
+
action: "already-initialized",
|
|
270088
|
+
filePath,
|
|
270089
|
+
modelExists: true,
|
|
270090
|
+
endpointsCount: model2.endpoints?.length || 0,
|
|
270091
|
+
message: `Model already exists at ${filePath} \u2014 nothing was overwritten. Continue with step 2 (jetic_scan for Express, or jetic_add_endpoint per endpoint).`,
|
|
270092
|
+
next: "If this is an Express+TypeScript project, run jetic_scan next. Otherwise read the code and add endpoints with jetic_add_endpoint, then jetic_verify_model."
|
|
270093
|
+
};
|
|
270094
|
+
}
|
|
270095
|
+
const stack = detectStack(projectRoot);
|
|
270096
|
+
const model = {
|
|
270097
|
+
version: model_1.CURRENT_MODEL_VERSION,
|
|
270098
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
270099
|
+
project: {
|
|
270100
|
+
name: args.projectName || path7.basename(projectRoot) || "jetic-project",
|
|
270101
|
+
...args.language || (stack.hasTsConfig ? "typescript" : void 0) ? { language: args.language || "typescript" } : {},
|
|
270102
|
+
...args.framework || (stack.hasExpress ? "express" : void 0) ? { framework: args.framework || "express" } : {}
|
|
270103
|
+
},
|
|
270104
|
+
environments: [
|
|
270105
|
+
{ name: args.environmentName || "local", baseUrl: args.baseUrl || "http://localhost:3000" }
|
|
270106
|
+
],
|
|
270107
|
+
securitySchemes: {},
|
|
270108
|
+
resources: [],
|
|
270109
|
+
endpoints: [],
|
|
270110
|
+
dependencies: [],
|
|
270111
|
+
workflows: [],
|
|
270112
|
+
stateMachines: []
|
|
270113
|
+
};
|
|
270114
|
+
(0, types_1.saveModel)(filePath, model);
|
|
270115
|
+
fs4.mkdirSync(path7.join(path7.dirname(filePath), "workflows"), { recursive: true });
|
|
270116
|
+
const configPath = path7.join(path7.dirname(filePath), "config.json");
|
|
270117
|
+
if (!fs4.existsSync(configPath)) {
|
|
270118
|
+
fs4.writeFileSync(configPath, JSON.stringify({}, null, 2), "utf-8");
|
|
270119
|
+
}
|
|
270120
|
+
const scanRecommended = stack.hasExpress && stack.hasTsConfig;
|
|
270121
|
+
return {
|
|
270122
|
+
success: true,
|
|
270123
|
+
action: overwrite ? "overwritten" : "created",
|
|
270124
|
+
filePath,
|
|
270125
|
+
project: model.project,
|
|
270126
|
+
detected: stack,
|
|
270127
|
+
scanRecommended,
|
|
270128
|
+
next: scanRecommended ? "Step 2: this looks like an Express+TypeScript project \u2014 run jetic_scan next to auto-fill model.json (no AI key needed), then refine with jetic_add_endpoint and jetic_verify_model." : "Step 2: no auto-scan applies here \u2014 read the backend code and add each endpoint with jetic_add_endpoint (middleware, security, parameters, responses), then jetic_verify_model until clean. Step 3 (only on request): author workflows with jetic_validate_workflow \u2192 jetic_create_workflow \u2192 jetic_simulate_workflow."
|
|
270129
|
+
};
|
|
270130
|
+
}
|
|
270131
|
+
exports2.scanProjectSchema = zod_1.z.object({
|
|
270132
|
+
projectPath: zod_1.z.string().optional().describe("Path to the Express project root (must contain tsconfig.json). Defaults to JETIC_PROJECT_PATH / cwd."),
|
|
270133
|
+
mode: zod_1.z.enum(["merge", "overwrite"]).optional().describe("merge (default): upsert scanned routes, keep hand-added endpoints. overwrite: replace the whole endpoints array.")
|
|
270134
|
+
});
|
|
270135
|
+
async function quietly(fn) {
|
|
270136
|
+
const origStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
270137
|
+
const origLog = console.log;
|
|
270138
|
+
const origInfo = console.info;
|
|
270139
|
+
const origDebug = console.debug?.bind(console);
|
|
270140
|
+
const noop = () => {
|
|
270141
|
+
};
|
|
270142
|
+
process.stdout.write = noop;
|
|
270143
|
+
console.log = noop;
|
|
270144
|
+
console.info = noop;
|
|
270145
|
+
if (console.debug)
|
|
270146
|
+
console.debug = noop;
|
|
270147
|
+
try {
|
|
270148
|
+
return await fn();
|
|
270149
|
+
} finally {
|
|
270150
|
+
process.stdout.write = origStdoutWrite;
|
|
270151
|
+
console.log = origLog;
|
|
270152
|
+
console.info = origInfo;
|
|
270153
|
+
if (origDebug)
|
|
270154
|
+
console.debug = origDebug;
|
|
270155
|
+
}
|
|
270156
|
+
}
|
|
270157
|
+
async function handleScanProject(args) {
|
|
270158
|
+
const filePath = (0, types_1.resolveModelPath)(args.projectPath);
|
|
270159
|
+
const projectRoot = (0, types_1.projectRootOfModel)(filePath);
|
|
270160
|
+
const mode = args.mode || "merge";
|
|
270161
|
+
if (!fs4.existsSync(path7.join(projectRoot, "tsconfig.json"))) {
|
|
270162
|
+
throw new Error(`Cannot scan ${projectRoot}: no tsconfig.json found. jetic_scan only supports Express+TypeScript projects. For any other stack, read the code and model each endpoint with jetic_add_endpoint instead.`);
|
|
270163
|
+
}
|
|
270164
|
+
const { ExpressScanner: ExpressScanner2 } = await Promise.resolve().then(() => __importStar(require_dist3()));
|
|
270165
|
+
const config = {
|
|
270166
|
+
projectRoot,
|
|
270167
|
+
jeticDir: path7.dirname(filePath)
|
|
270168
|
+
// No `ai` key on purpose: static discovery only, no provider, no keys.
|
|
270169
|
+
};
|
|
270170
|
+
let scanned;
|
|
270171
|
+
try {
|
|
270172
|
+
scanned = await quietly(() => new ExpressScanner2(config).scan());
|
|
270173
|
+
} catch (err) {
|
|
270174
|
+
throw new Error(`Scan failed for ${projectRoot}: ${err?.message || err}. If this is not an Express+TypeScript codebase, model endpoints manually with jetic_add_endpoint.`);
|
|
270175
|
+
}
|
|
270176
|
+
const { model: existing, exists } = (0, types_1.loadModel)(args.projectPath);
|
|
270177
|
+
let added = 0;
|
|
270178
|
+
let updated = 0;
|
|
270179
|
+
let preserved = 0;
|
|
270180
|
+
if (!exists || mode === "overwrite") {
|
|
270181
|
+
if (mode === "overwrite" && exists)
|
|
270182
|
+
preserved = 0;
|
|
270183
|
+
existing.endpoints = scanned.endpoints;
|
|
270184
|
+
added = scanned.endpoints.length;
|
|
270185
|
+
if (!existing.environments || existing.environments.length === 0) {
|
|
270186
|
+
existing.environments = scanned.environments;
|
|
270187
|
+
}
|
|
270188
|
+
} else {
|
|
270189
|
+
const index = /* @__PURE__ */ new Map();
|
|
270190
|
+
existing.endpoints.forEach((e, i) => index.set(`${e.method.toUpperCase()} ${e.path}`, i));
|
|
270191
|
+
for (const s of scanned.endpoints) {
|
|
270192
|
+
const key = `${s.method.toUpperCase()} ${s.path}`;
|
|
270193
|
+
const at = index.get(key);
|
|
270194
|
+
if (at === void 0) {
|
|
270195
|
+
existing.endpoints.push(s);
|
|
270196
|
+
added++;
|
|
270197
|
+
} else {
|
|
270198
|
+
existing.endpoints[at] = s;
|
|
270199
|
+
updated++;
|
|
270200
|
+
}
|
|
270201
|
+
}
|
|
270202
|
+
preserved = existing.endpoints.length - added - updated;
|
|
270203
|
+
if (!existing.environments || existing.environments.length === 0) {
|
|
270204
|
+
existing.environments = scanned.environments;
|
|
270205
|
+
}
|
|
270206
|
+
}
|
|
270207
|
+
if (!existing.project?.framework && scanned.project?.framework) {
|
|
270208
|
+
existing.project = { ...scanned.project, ...existing.project };
|
|
270209
|
+
}
|
|
270210
|
+
(0, types_1.saveModel)(filePath, existing);
|
|
270211
|
+
const total = existing.endpoints.length;
|
|
270212
|
+
return {
|
|
270213
|
+
success: true,
|
|
270214
|
+
action: "scanned",
|
|
270215
|
+
filePath,
|
|
270216
|
+
mode,
|
|
270217
|
+
stats: { scanned: scanned.endpoints.length, added, updated, preserved, total },
|
|
270218
|
+
...scanned.endpoints.length === 0 ? {
|
|
270219
|
+
warning: "Static scan found 0 endpoints. Check that Express routes live under this project root (custom entry points or routers may need manual modeling with jetic_add_endpoint)."
|
|
270220
|
+
} : {},
|
|
270221
|
+
next: total === 0 ? "No endpoints yet \u2014 model them manually with jetic_add_endpoint, then jetic_verify_model." : `Next: refine with jetic_add_endpoint / jetic_update_endpoint (AI enrichment is skipped in scans \u2014 add constraints, examples, ownership by hand), then jetic_verify_model until clean. Step 3 (only on request): workflows via jetic_validate_workflow \u2192 jetic_create_workflow \u2192 jetic_simulate_workflow.`
|
|
270222
|
+
};
|
|
270223
|
+
}
|
|
270224
|
+
}
|
|
270225
|
+
});
|
|
270226
|
+
|
|
269984
270227
|
// ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/zod-compat.js
|
|
269985
270228
|
var require_zod_compat = __commonJS({
|
|
269986
270229
|
"../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/cjs/server/zod-compat.js"(exports2) {
|
|
@@ -284050,6 +284293,7 @@ var require_server3 = __commonJS({
|
|
|
284050
284293
|
var environment_tools_1 = require_environment_tools();
|
|
284051
284294
|
var workflow_tools_1 = require_workflow_tools();
|
|
284052
284295
|
var workflow_authoring_tools_1 = require_workflow_authoring_tools();
|
|
284296
|
+
var project_tools_1 = require_project_tools();
|
|
284053
284297
|
exports2.JETIC_MCP_SERVER_NAME = "jetic-mcp-server";
|
|
284054
284298
|
function getJeticMcpVersion() {
|
|
284055
284299
|
try {
|
|
@@ -284062,14 +284306,12 @@ var require_server3 = __commonJS({
|
|
|
284062
284306
|
return "0.1.0";
|
|
284063
284307
|
}
|
|
284064
284308
|
exports2.JETIC_MCP_INSTRUCTIONS = [
|
|
284065
|
-
"Jetic MCP manages API behavioral models
|
|
284066
|
-
"
|
|
284067
|
-
"
|
|
284068
|
-
"
|
|
284069
|
-
"
|
|
284070
|
-
"
|
|
284071
|
-
"5. Use `jetic_test_endpoint` / `jetic_simulate_workflow` only against a running server (local/staging); pass `baseUrl` to override the stored environment URL.",
|
|
284072
|
-
"Authoring workflows: NEVER hand-write workflow files blind. Build the steps, then run `jetic_validate_workflow` with the inline definition (dry run), fix reported errors, save with `jetic_create_workflow`, and finally execute with `jetic_simulate_workflow`. Chain steps with capture/captureInput (producer) + inject or {{workflow:key}} templates (consumer).",
|
|
284309
|
+
"Jetic MCP manages API behavioral models in `.jetic/`. Work in this structured order:",
|
|
284310
|
+
"1. INITIALIZE: if there is no `.jetic/model.json` (read tools report modelExists:false), call `jetic_init` first. It scaffolds the folder + model and tells you whether auto-scan applies. Never invent a model file by hand.",
|
|
284311
|
+
"2. MODEL: Express+TypeScript project with tsconfig.json \u2192 run `jetic_scan` (static, keyless) to fill model.json, then refine with `jetic_add_endpoint` / `jetic_update_endpoint`. Any other stack \u2014 or scan gaps \u2014 read the code and model each endpoint yourself with `jetic_add_endpoint` (full fidelity: middleware chains, security, constraints, pagination, produces/consumes). Run `jetic_verify_model` until clean.",
|
|
284312
|
+
"3. SIMULATE (only when the user asks): author workflows with `jetic_validate_workflow` (dry run) \u2192 `jetic_create_workflow` \u2192 `jetic_simulate_workflow`. Chain steps with capture/captureInput (producer) + inject or {{workflow:key}} templates (consumer). Use {{human:key}} ONLY for values unknowable beforehand (OTP codes, real secrets) \u2014 runners pause for interactive input and the create response lists them under needsHuman so you can warn the user.",
|
|
284313
|
+
"No AI-provider setup is needed for any of this: never ask for OpenRouter/OpenAI keys and never use `jetic config ai`.",
|
|
284314
|
+
"Mutating tools (`jetic_add_endpoint`, `jetic_update_endpoint`, `jetic_delete_endpoint`, `jetic_manage_environment`, workflow create/update) write to disk \u2014 confirm intent with the user first when the change is destructive (delete/overwrite).",
|
|
284073
284315
|
"Project resolution: every tool accepts an optional `projectPath`. If omitted, the server uses the `JETIC_PROJECT_PATH` environment variable, falling back to its own working directory. Set one of them to the project root so the server edits the right `.jetic/model.json`."
|
|
284074
284316
|
].join("\n");
|
|
284075
284317
|
var READ_ONLY = {
|
|
@@ -284130,10 +284372,12 @@ var require_server3 = __commonJS({
|
|
|
284130
284372
|
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);
|
|
284131
284373
|
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);
|
|
284132
284374
|
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);
|
|
284133
|
-
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}} 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);
|
|
284375
|
+
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);
|
|
284134
284376
|
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);
|
|
284135
284377
|
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);
|
|
284136
284378
|
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);
|
|
284379
|
+
register("jetic_init", "Initialize Jetic project", "Step 1 of the structured order: scaffolds .jetic/model.json (+ workflows/, config.json) for repos that have none. Refuses to clobber an existing model (pass overwrite:true to replace). Reports whether auto-scan applies (Express+TypeScript) and what to do next.", project_tools_1.initProjectSchema.shape, WRITES_MODEL, project_tools_1.handleInitProject);
|
|
284380
|
+
register("jetic_scan", "Scan Express project", "Step 2a for Express+TypeScript projects (requires tsconfig.json): static AST scan that fills model.json with routes, middleware, and auth heuristics. Keyless \u2014 no AI provider involved. Default merge upserts scanned routes and keeps hand-added endpoints; overwrite replaces all endpoints. Fails with guidance for non-Express stacks (model those manually).", project_tools_1.scanProjectSchema.shape, WRITES_MODEL, project_tools_1.handleScanProject);
|
|
284137
284381
|
return server;
|
|
284138
284382
|
}
|
|
284139
284383
|
async function runMcpServer2() {
|
|
@@ -284200,6 +284444,7 @@ var require_dist7 = __commonJS({
|
|
|
284200
284444
|
__exportStar(require_environment_tools(), exports2);
|
|
284201
284445
|
__exportStar(require_workflow_tools(), exports2);
|
|
284202
284446
|
__exportStar(require_workflow_authoring_tools(), exports2);
|
|
284447
|
+
__exportStar(require_project_tools(), exports2);
|
|
284203
284448
|
__exportStar(require_server3(), exports2);
|
|
284204
284449
|
__exportStar(require_serial_transport(), exports2);
|
|
284205
284450
|
var fs4 = __importStar(require("fs"));
|
|
@@ -284663,9 +284908,15 @@ Use EXACTLY these placeholders in body/inject values:
|
|
|
284663
284908
|
{{faker.commerce.productName}} product name
|
|
284664
284909
|
{{faker.phone.number}} phone number
|
|
284665
284910
|
{{workflow:KEY}} value captured from a previous step
|
|
284911
|
+
{{human:KEY}} ask the human at runtime (interactive prompt, waits for input).
|
|
284912
|
+
Use ONLY for values unknowable beforehand (OTP/2FA codes,
|
|
284913
|
+
CAPTCHAs, real personal secrets). First entry is saved to
|
|
284914
|
+
human:KEY memory; JETIC_HUMAN_KEY env var skips the prompt.
|
|
284666
284915
|
|
|
284667
|
-
\u2501\u2501\u2501 FIELD RULES \u2501\u2501\u2501
|
|
284916
|
+
\u2501\u2501\u2501 FIELD RULES \u2501\u2501\u2501
|
|
284668
284917
|
"body" \u2014 request body. Use {{faker.*}} for generated fields, {{workflow:KEY}} for previously captured values.
|
|
284918
|
+
Use {{human:KEY}} SPARINGLY and only when no other source exists (OTP codes, real secrets) \u2014
|
|
284919
|
+
it pauses execution waiting for a person to type a value.
|
|
284669
284920
|
|
|
284670
284921
|
"captureInput" \u2014 save RESOLVED body field values to memory BEFORE the HTTP call.
|
|
284671
284922
|
Only for faker-generated fields you need to re-use in later steps.
|
|
@@ -284716,7 +284967,7 @@ all [requiresAuth] endpoints inject Authorization, captures wired correctly betw
|
|
|
284716
284967
|
method: import_zod.z.string().describe("HTTP method in uppercase: GET, POST, PUT, PATCH, DELETE"),
|
|
284717
284968
|
path: import_zod.z.string().describe("Exact endpoint path, e.g. /api/auth/login"),
|
|
284718
284969
|
description: import_zod.z.string().optional().describe("One sentence describing what this step does"),
|
|
284719
|
-
body: import_zod.z.record(import_zod.z.string(), import_zod.z.any()).optional().describe("Request body. Use {{faker.X}} for generated values, {{workflow:KEY}} for captured values"),
|
|
284970
|
+
body: import_zod.z.record(import_zod.z.string(), import_zod.z.any()).optional().describe("Request body. Use {{faker.X}} for generated values, {{workflow:KEY}} for captured values, {{human:KEY}} only for OTP/secret values a person must type at runtime"),
|
|
284720
284971
|
captureInput: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional().describe('Save resolved request body fields to memory BEFORE the HTTP call. Format: { "workflow:KEY": "bodyFieldName" }'),
|
|
284721
284972
|
capture: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional().describe('Save response body fields to memory AFTER success. Format: { "workflow:KEY": "dot.path" }'),
|
|
284722
284973
|
inject: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).optional().describe('Inject memory values into headers/body. Use "header:Authorization" for Bearer auth'),
|
|
@@ -284748,12 +284999,12 @@ function deepGet(obj, dotPath) {
|
|
|
284748
284999
|
}
|
|
284749
285000
|
return current;
|
|
284750
285001
|
}
|
|
284751
|
-
async function resolveConditionRef(ref) {
|
|
284752
|
-
return ref.replace(/\{\{([^}]+)\}\}/g, () => "").trim() === ref.trim() ? ref : await resolveTemplateString(ref);
|
|
285002
|
+
async function resolveConditionRef(ref, context = "condition") {
|
|
285003
|
+
return ref.replace(/\{\{([^}]+)\}\}/g, () => "").trim() === ref.trim() ? ref : await resolveTemplateString(ref, context);
|
|
284753
285004
|
}
|
|
284754
|
-
async function evalConditionRule(rule) {
|
|
284755
|
-
const left = await resolveConditionRef(rule.left);
|
|
284756
|
-
const right = rule.right !== void 0 ? await resolveConditionRef(rule.right) : void 0;
|
|
285005
|
+
async function evalConditionRule(rule, context = "condition") {
|
|
285006
|
+
const left = await resolveConditionRef(rule.left, context);
|
|
285007
|
+
const right = rule.right !== void 0 ? await resolveConditionRef(rule.right, context) : void 0;
|
|
284757
285008
|
const op = rule.operator;
|
|
284758
285009
|
switch (op) {
|
|
284759
285010
|
case "equals":
|
|
@@ -284788,14 +285039,14 @@ async function evalConditionRule(rule) {
|
|
|
284788
285039
|
return false;
|
|
284789
285040
|
}
|
|
284790
285041
|
}
|
|
284791
|
-
async function evaluateStepCondition(condition) {
|
|
285042
|
+
async function evaluateStepCondition(condition, context = "condition") {
|
|
284792
285043
|
const { rules } = condition;
|
|
284793
285044
|
const ruleList = rules.all ?? rules.any ?? [];
|
|
284794
285045
|
const isAnd = !!rules.all;
|
|
284795
285046
|
if (ruleList.length === 0) return { passed: true, reason: "No rules defined" };
|
|
284796
285047
|
const results = [];
|
|
284797
285048
|
for (const rule of ruleList) {
|
|
284798
|
-
results.push({ rule, ok: await evalConditionRule(rule) });
|
|
285049
|
+
results.push({ rule, ok: await evalConditionRule(rule, context) });
|
|
284799
285050
|
}
|
|
284800
285051
|
const passed = isAnd ? results.every((r) => r.ok) : results.some((r) => r.ok);
|
|
284801
285052
|
const failing = results.filter((r) => !r.ok);
|
|
@@ -284804,12 +285055,89 @@ async function evaluateStepCondition(condition) {
|
|
|
284804
285055
|
).join(", ")}`;
|
|
284805
285056
|
return { passed, reason };
|
|
284806
285057
|
}
|
|
284807
|
-
|
|
285058
|
+
function envKeyForHuman(key) {
|
|
285059
|
+
return `JETIC_HUMAN_${key.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
285060
|
+
}
|
|
285061
|
+
var HumanInputError = class extends Error {
|
|
285062
|
+
constructor(message) {
|
|
285063
|
+
super(message);
|
|
285064
|
+
this.name = "HumanInputError";
|
|
285065
|
+
}
|
|
285066
|
+
};
|
|
285067
|
+
function isSecretHumanKey(key) {
|
|
285068
|
+
return /pass|secret|token|pwd|private|credential/i.test(key);
|
|
285069
|
+
}
|
|
285070
|
+
async function promptLine(question) {
|
|
285071
|
+
const readline3 = await new Function("modulePath", "return import(modulePath)")("node:readline/promises");
|
|
285072
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
285073
|
+
try {
|
|
285074
|
+
return await rl.question(question);
|
|
285075
|
+
} finally {
|
|
285076
|
+
rl.close();
|
|
285077
|
+
}
|
|
285078
|
+
}
|
|
285079
|
+
async function promptSecret(question) {
|
|
285080
|
+
const readline3 = await new Function("modulePath", "return import(modulePath)")("node:readline");
|
|
285081
|
+
return new Promise((resolve2) => {
|
|
285082
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
285083
|
+
process.stdout.write(question);
|
|
285084
|
+
const w = rl;
|
|
285085
|
+
const orig = w._writeToOutput.bind(rl);
|
|
285086
|
+
w._writeToOutput = (s) => {
|
|
285087
|
+
if (s === "\n" || s === "\r\n" || s === "\r") orig(s);
|
|
285088
|
+
else w.output.write("*".repeat(s.length));
|
|
285089
|
+
};
|
|
285090
|
+
rl.question("", (answer) => {
|
|
285091
|
+
w._writeToOutput = orig;
|
|
285092
|
+
process.stdout.write("\n");
|
|
285093
|
+
rl.close();
|
|
285094
|
+
resolve2(answer);
|
|
285095
|
+
});
|
|
285096
|
+
});
|
|
285097
|
+
}
|
|
285098
|
+
async function askHuman(key, context) {
|
|
285099
|
+
const envName = envKeyForHuman(key);
|
|
285100
|
+
const fromEnv = process.env[envName];
|
|
285101
|
+
if (fromEnv !== void 0 && fromEnv !== "") return fromEnv;
|
|
285102
|
+
const mem = new import_memory2.JeticMemory({ scope: "human" });
|
|
285103
|
+
const cached = await mem.get(key);
|
|
285104
|
+
if (cached !== null && cached !== void 0 && String(cached) !== "") {
|
|
285105
|
+
console.log(` ${c.dim}\u21B3 using saved human value for "${key}" (jetic memory set human:${key} \u2026 to change)${c.reset}`);
|
|
285106
|
+
return String(cached);
|
|
285107
|
+
}
|
|
285108
|
+
if (!process.stdin.isTTY) {
|
|
285109
|
+
throw new HumanInputError(
|
|
285110
|
+
`Workflow needs human input for "{{human:${key}}}"${context ? ` (${context})` : ""} but the terminal is not interactive. Pre-seed it with: jetic memory set human:${key} <value> \u2014 or export ${envName}=<value>`
|
|
285111
|
+
);
|
|
285112
|
+
}
|
|
285113
|
+
console.log(`
|
|
285114
|
+
${c.yellow}${c.bold}\u25C9 Human input required${c.reset}${context ? ` ${c.dim}${context}${c.reset}` : ""}`);
|
|
285115
|
+
const question = ` ${c.cyan}Enter value for "${key}":${c.reset} `;
|
|
285116
|
+
const value = isSecretHumanKey(key) ? await promptSecret(question) : await promptLine(question);
|
|
285117
|
+
if (value !== "") {
|
|
285118
|
+
await mem.set(key, value);
|
|
285119
|
+
console.log(` ${c.dim}\u21B3 saved to human:${key} memory (reused automatically next time)${c.reset}
|
|
285120
|
+
`);
|
|
285121
|
+
} else {
|
|
285122
|
+
console.log(` ${c.dim}\u21B3 empty value accepted for "${key}" (not saved)${c.reset}
|
|
285123
|
+
`);
|
|
285124
|
+
}
|
|
285125
|
+
return value;
|
|
285126
|
+
}
|
|
285127
|
+
async function resolveTemplateString(value, context) {
|
|
284808
285128
|
const templateRe = /\{\{([^}]+)\}\}/g;
|
|
284809
285129
|
let match;
|
|
284810
285130
|
const replacements = [];
|
|
284811
285131
|
while ((match = templateRe.exec(value)) !== null) {
|
|
284812
285132
|
const expr = match[1].trim();
|
|
285133
|
+
if (!expr.startsWith("faker.")) {
|
|
285134
|
+
const colonIdx = expr.indexOf(":");
|
|
285135
|
+
if (colonIdx > 0 && expr.slice(0, colonIdx) === "human") {
|
|
285136
|
+
const key = expr.slice(colonIdx + 1);
|
|
285137
|
+
replacements.push({ placeholder: match[0], resolved: await askHuman(key, context) });
|
|
285138
|
+
continue;
|
|
285139
|
+
}
|
|
285140
|
+
}
|
|
284813
285141
|
if (expr.includes(":") && !expr.startsWith("faker.")) {
|
|
284814
285142
|
const colonIdx = expr.indexOf(":");
|
|
284815
285143
|
const scope = expr.slice(0, colonIdx);
|
|
@@ -284839,21 +285167,21 @@ async function resolveTemplateString(value) {
|
|
|
284839
285167
|
}
|
|
284840
285168
|
return result;
|
|
284841
285169
|
}
|
|
284842
|
-
async function resolveBodyTemplates(body) {
|
|
285170
|
+
async function resolveBodyTemplates(body, context) {
|
|
284843
285171
|
const resolved = {};
|
|
284844
285172
|
for (const [key, value] of Object.entries(body)) {
|
|
284845
|
-
resolved[key] = typeof value === "string" ? await resolveTemplateString(value) : value;
|
|
285173
|
+
resolved[key] = typeof value === "string" ? await resolveTemplateString(value, context) : value;
|
|
284846
285174
|
}
|
|
284847
285175
|
return resolved;
|
|
284848
285176
|
}
|
|
284849
|
-
async function resolveInjections(inject, memory, allMemory) {
|
|
285177
|
+
async function resolveInjections(inject, memory, allMemory, context) {
|
|
284850
285178
|
const headers = {};
|
|
284851
285179
|
const body = {};
|
|
284852
285180
|
if (!inject) return { headers, body };
|
|
284853
285181
|
for (const [target, memKeyOrTemplate] of Object.entries(inject)) {
|
|
284854
285182
|
let strValue;
|
|
284855
285183
|
if (memKeyOrTemplate.includes("{{")) {
|
|
284856
|
-
strValue = await resolveTemplateString(memKeyOrTemplate);
|
|
285184
|
+
strValue = await resolveTemplateString(memKeyOrTemplate, context);
|
|
284857
285185
|
} else {
|
|
284858
285186
|
const [scope, key] = memKeyOrTemplate.includes(":") ? memKeyOrTemplate.split(":", 2) : ["workflow", memKeyOrTemplate];
|
|
284859
285187
|
const scopedMemory = new import_memory2.JeticMemory({ scope });
|
|
@@ -284906,8 +285234,9 @@ async function resolvePathParams(urlPath, body) {
|
|
|
284906
285234
|
async function executeStep(step, baseUrl, defaultBody) {
|
|
284907
285235
|
const startTime = Date.now();
|
|
284908
285236
|
const memory = new import_memory2.JeticMemory({ scope: "workflow" });
|
|
284909
|
-
const
|
|
284910
|
-
const
|
|
285237
|
+
const stepLabel = step.name || `${step.method} ${step.path}`;
|
|
285238
|
+
const { headers, body: injectedBody } = await resolveInjections(step.inject, memory, memory, stepLabel);
|
|
285239
|
+
const resolvedStepBody = await resolveBodyTemplates(step.body || {}, stepLabel);
|
|
284911
285240
|
const requestBody = { ...defaultBody, ...injectedBody, ...resolvedStepBody };
|
|
284912
285241
|
const inputCaptured = await captureInputToMemory(step.captureInput, requestBody);
|
|
284913
285242
|
const resolvedPath = await resolvePathParams(step.path, requestBody);
|
|
@@ -285251,71 +285580,86 @@ var simulateWorkflowCommand = new import_commander6.Command("workflow").descript
|
|
|
285251
285580
|
const stepLabel = `${c.dim}${i + 1}/${workflow.steps.length}${c.reset} ${getMethodColor(step.method)}${step.method}${c.reset} ${step.path}`;
|
|
285252
285581
|
const spinner = new Spinner();
|
|
285253
285582
|
spinner.start(`${stepLabel} ${c.dim}${step.name}${c.reset}...`);
|
|
285254
|
-
|
|
285255
|
-
|
|
285256
|
-
|
|
285257
|
-
|
|
285258
|
-
|
|
285259
|
-
|
|
285260
|
-
|
|
285261
|
-
|
|
285262
|
-
|
|
285263
|
-
|
|
285264
|
-
|
|
285265
|
-
|
|
285583
|
+
let result;
|
|
285584
|
+
try {
|
|
285585
|
+
result = await executeStep(step, baseUrl, {});
|
|
285586
|
+
if (step.condition) {
|
|
285587
|
+
const condResult = await evaluateStepCondition(step.condition, `condition of step "${step.name || step.path}"`);
|
|
285588
|
+
result.conditionResult = condResult;
|
|
285589
|
+
if (!condResult.passed) {
|
|
285590
|
+
const { onFail, switchToWorkflow } = step.condition;
|
|
285591
|
+
results.push(result);
|
|
285592
|
+
const icon2 = result.passed ? TICK : CROSS;
|
|
285593
|
+
spinner.stop(` ${icon2} ${stepLabel} ${formatStatus(result.status)} ${c.dim}${result.durationMs}ms${c.reset}`);
|
|
285594
|
+
renderStepResult(result, i, workflow.steps.length);
|
|
285595
|
+
if (onFail === "abort") {
|
|
285596
|
+
console.log(` ${c.red}${c.bold}\u26A0 Condition failed \u2014 aborting workflow.${c.reset}
|
|
285266
285597
|
`);
|
|
285267
|
-
|
|
285268
|
-
|
|
285269
|
-
|
|
285270
|
-
|
|
285598
|
+
stopOnFailure = true;
|
|
285599
|
+
break;
|
|
285600
|
+
} else if (onFail === "continue") {
|
|
285601
|
+
console.log(` ${c.yellow}\u2298 Condition failed \u2014 skipping remaining steps.${c.reset}
|
|
285271
285602
|
`);
|
|
285272
|
-
|
|
285273
|
-
|
|
285274
|
-
|
|
285275
|
-
|
|
285603
|
+
stopOnFailure = true;
|
|
285604
|
+
break;
|
|
285605
|
+
} else if (onFail === "switch" && switchToWorkflow) {
|
|
285606
|
+
console.log(` ${ARROW} Condition failed \u2014 switching to workflow: ${c.bold}${switchToWorkflow}${c.reset}
|
|
285276
285607
|
`);
|
|
285277
|
-
|
|
285278
|
-
|
|
285279
|
-
|
|
285280
|
-
|
|
285281
|
-
|
|
285608
|
+
const switchPath = path3.join(workflowsDir, `${switchToWorkflow}.json`);
|
|
285609
|
+
if (fs.existsSync(switchPath)) {
|
|
285610
|
+
try {
|
|
285611
|
+
const switchedWorkflow = JSON.parse(fs.readFileSync(switchPath, "utf-8"));
|
|
285612
|
+
console.log(` ${TICK} Loaded switch workflow: ${c.bold}${switchedWorkflow.name}${c.reset}
|
|
285282
285613
|
`);
|
|
285283
|
-
|
|
285284
|
-
|
|
285614
|
+
renderWorkflowHeader(switchedWorkflow);
|
|
285615
|
+
console.log(` ${c.magenta}\u{1F680}${c.reset} Executing sub-workflow...
|
|
285616
|
+
`);
|
|
285617
|
+
for (let si = 0; si < switchedWorkflow.steps.length; si++) {
|
|
285618
|
+
const switchStep = switchedWorkflow.steps[si];
|
|
285619
|
+
const switchLabel = `${c.dim}${si + 1}/${switchedWorkflow.steps.length}${c.reset} ${getMethodColor(switchStep.method)}${switchStep.method}${c.reset} ${switchStep.path}`;
|
|
285620
|
+
const switchSpinner = new Spinner();
|
|
285621
|
+
switchSpinner.start(`${switchLabel} ${c.dim}${switchStep.name}${c.reset}...`);
|
|
285622
|
+
const switchResult = await executeStep(switchStep, baseUrl, {});
|
|
285623
|
+
if (switchStep.condition) {
|
|
285624
|
+
switchResult.conditionResult = await evaluateStepCondition(
|
|
285625
|
+
switchStep.condition,
|
|
285626
|
+
`condition of step "${switchStep.name || switchStep.path}"`
|
|
285627
|
+
);
|
|
285628
|
+
}
|
|
285629
|
+
results.push(switchResult);
|
|
285630
|
+
const switchIcon = switchResult.passed ? TICK : CROSS;
|
|
285631
|
+
switchSpinner.stop(` ${switchIcon} ${switchLabel} ${formatStatus(switchResult.status)} ${c.dim}${switchResult.durationMs}ms${c.reset}`);
|
|
285632
|
+
renderStepResult(switchResult, si, switchedWorkflow.steps.length);
|
|
285633
|
+
}
|
|
285634
|
+
} catch (e) {
|
|
285635
|
+
console.log(` ${c.red}\u2717 Failed to load switch workflow: ${e.message}${c.reset}
|
|
285285
285636
|
`);
|
|
285286
|
-
for (let si = 0; si < switchedWorkflow.steps.length; si++) {
|
|
285287
|
-
const switchStep = switchedWorkflow.steps[si];
|
|
285288
|
-
const switchLabel = `${c.dim}${si + 1}/${switchedWorkflow.steps.length}${c.reset} ${getMethodColor(switchStep.method)}${switchStep.method}${c.reset} ${switchStep.path}`;
|
|
285289
|
-
const switchSpinner = new Spinner();
|
|
285290
|
-
switchSpinner.start(`${switchLabel} ${c.dim}${switchStep.name}${c.reset}...`);
|
|
285291
|
-
const switchResult = await executeStep(switchStep, baseUrl, {});
|
|
285292
|
-
if (switchStep.condition) {
|
|
285293
|
-
switchResult.conditionResult = await evaluateStepCondition(switchStep.condition);
|
|
285294
|
-
}
|
|
285295
|
-
results.push(switchResult);
|
|
285296
|
-
const switchIcon = switchResult.passed ? TICK : CROSS;
|
|
285297
|
-
switchSpinner.stop(` ${switchIcon} ${switchLabel} ${formatStatus(switchResult.status)} ${c.dim}${switchResult.durationMs}ms${c.reset}`);
|
|
285298
|
-
renderStepResult(switchResult, si, switchedWorkflow.steps.length);
|
|
285299
285637
|
}
|
|
285300
|
-
}
|
|
285301
|
-
console.log(` ${c.
|
|
285638
|
+
} else {
|
|
285639
|
+
console.log(` ${c.yellow}\u26A0 Switch workflow not found: ${switchPath}${c.reset}
|
|
285302
285640
|
`);
|
|
285303
285641
|
}
|
|
285304
|
-
|
|
285305
|
-
|
|
285306
|
-
`);
|
|
285307
|
-
}
|
|
285308
|
-
if (step.condition.returnOnComplete) {
|
|
285309
|
-
console.log(` ${c.green}\u21A9 Sub-workflow complete. Returning to main workflow...${c.reset}
|
|
285642
|
+
if (step.condition.returnOnComplete) {
|
|
285643
|
+
console.log(` ${c.green}\u21A9 Sub-workflow complete. Returning to main workflow...${c.reset}
|
|
285310
285644
|
`);
|
|
285311
|
-
|
|
285312
|
-
|
|
285313
|
-
|
|
285314
|
-
|
|
285645
|
+
continue;
|
|
285646
|
+
} else {
|
|
285647
|
+
stopOnFailure = true;
|
|
285648
|
+
break;
|
|
285649
|
+
}
|
|
285315
285650
|
}
|
|
285651
|
+
continue;
|
|
285316
285652
|
}
|
|
285317
|
-
continue;
|
|
285318
285653
|
}
|
|
285654
|
+
} catch (err) {
|
|
285655
|
+
if (err instanceof HumanInputError || err?.name === "HumanInputError") {
|
|
285656
|
+
spinner.stop(` ${c.red}\u2717${c.reset} ${stepLabel} ${c.dim}needs human input${c.reset}`);
|
|
285657
|
+
console.error(`
|
|
285658
|
+
${c.red}${err.message}${c.reset}
|
|
285659
|
+
`);
|
|
285660
|
+
process.exit(1);
|
|
285661
|
+
}
|
|
285662
|
+
throw err;
|
|
285319
285663
|
}
|
|
285320
285664
|
results.push(result);
|
|
285321
285665
|
const icon = result.passed ? TICK : CROSS;
|
|
@@ -286228,8 +286572,29 @@ Output valid JSON only.
|
|
|
286228
286572
|
res.status(500).json({ error: err.message });
|
|
286229
286573
|
}
|
|
286230
286574
|
});
|
|
286575
|
+
const humanInputWaiters = /* @__PURE__ */ new Map();
|
|
286576
|
+
const HUMAN_INPUT_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
286577
|
+
class HumanInputAbort extends Error {
|
|
286578
|
+
constructor(key, reason) {
|
|
286579
|
+
super(`Human input for "{{human:${key}}}" ${reason}`);
|
|
286580
|
+
this.name = "HumanInputAbort";
|
|
286581
|
+
}
|
|
286582
|
+
}
|
|
286583
|
+
app.post("/api/workflows/human-input", (req, res) => {
|
|
286584
|
+
const { runId, key, value } = req.body;
|
|
286585
|
+
if (!runId || !key) {
|
|
286586
|
+
return res.status(400).json({ error: "runId and key are required" });
|
|
286587
|
+
}
|
|
286588
|
+
const waiter = humanInputWaiters.get(runId);
|
|
286589
|
+
if (!waiter || waiter.key !== key) {
|
|
286590
|
+
return res.status(404).json({ error: "no pending human input for this run/key (answered, timed out, or run ended)" });
|
|
286591
|
+
}
|
|
286592
|
+
humanInputWaiters.delete(runId);
|
|
286593
|
+
waiter.resolve(typeof value === "string" ? value : null);
|
|
286594
|
+
res.json({ ok: true });
|
|
286595
|
+
});
|
|
286231
286596
|
app.post("/api/workflows/run", async (req, res) => {
|
|
286232
|
-
const { file } = req.body;
|
|
286597
|
+
const { file, humanInputs: preseededHuman } = req.body;
|
|
286233
286598
|
if (!file) {
|
|
286234
286599
|
return res.status(400).json({ error: "file is required" });
|
|
286235
286600
|
}
|
|
@@ -286270,10 +286635,46 @@ Output valid JSON only.
|
|
|
286270
286635
|
}
|
|
286271
286636
|
}
|
|
286272
286637
|
if (!baseUrl) baseUrl = "http://localhost:4000";
|
|
286273
|
-
|
|
286638
|
+
const runId = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
286639
|
+
const runHumanCache = new Map(Object.entries(preseededHuman ?? {}));
|
|
286640
|
+
const HUMAN_KEY_RE = /\{\{\s*human:([^}]+?)\s*\}\}/g;
|
|
286641
|
+
const collectHumanRefs = () => {
|
|
286642
|
+
const keys = /* @__PURE__ */ new Set();
|
|
286643
|
+
for (const step of workflow.steps || []) {
|
|
286644
|
+
for (const hay of [step?.path, JSON.stringify(step?.body ?? {}), JSON.stringify(step?.inject ?? {}), JSON.stringify(step?.condition ?? {})]) {
|
|
286645
|
+
if (!hay) continue;
|
|
286646
|
+
HUMAN_KEY_RE.lastIndex = 0;
|
|
286647
|
+
let m;
|
|
286648
|
+
while ((m = HUMAN_KEY_RE.exec(hay)) !== null) {
|
|
286649
|
+
const k = (m[1] || "").trim();
|
|
286650
|
+
if (k) keys.add(k);
|
|
286651
|
+
}
|
|
286652
|
+
}
|
|
286653
|
+
}
|
|
286654
|
+
return [...keys];
|
|
286655
|
+
};
|
|
286656
|
+
const humanEnvName = (key) => `JETIC_HUMAN_${key.toUpperCase().replace(/[^A-Z0-9]/g, "_")}`;
|
|
286657
|
+
const humanSource = async (key) => {
|
|
286658
|
+
if (runHumanCache.has(key)) return "run";
|
|
286659
|
+
if (process.env[humanEnvName(key)]) return "env";
|
|
286660
|
+
try {
|
|
286661
|
+
const v = await new Mem({ scope: "human" }).get(key);
|
|
286662
|
+
if (v != null && String(v) !== "") return "memory";
|
|
286663
|
+
} catch {
|
|
286664
|
+
}
|
|
286665
|
+
return "prompt";
|
|
286666
|
+
};
|
|
286667
|
+
const neededHuman = [];
|
|
286668
|
+
for (const k of collectHumanRefs()) neededHuman.push({ key: k, source: await humanSource(k) });
|
|
286669
|
+
send("start", { name: workflow.name, totalSteps: workflow.steps.length, baseUrl, runId, humanKeys: neededHuman });
|
|
286274
286670
|
let aborted = false;
|
|
286275
286671
|
req.on("close", () => {
|
|
286276
286672
|
aborted = true;
|
|
286673
|
+
const waiter = humanInputWaiters.get(runId);
|
|
286674
|
+
if (waiter) {
|
|
286675
|
+
humanInputWaiters.delete(runId);
|
|
286676
|
+
waiter.resolve(null);
|
|
286677
|
+
}
|
|
286277
286678
|
});
|
|
286278
286679
|
function deepGet2(obj, dotPath) {
|
|
286279
286680
|
return dotPath.replace(/\[(\d+)\]/g, ".$1").split(".").reduce((o, k) => o?.[k], obj);
|
|
@@ -286293,42 +286694,101 @@ Output valid JSON only.
|
|
|
286293
286694
|
return _;
|
|
286294
286695
|
});
|
|
286295
286696
|
}
|
|
286296
|
-
async function resolveMemoryTemplates(value) {
|
|
286697
|
+
async function resolveMemoryTemplates(value, stepIndex = -1, stepName = "") {
|
|
286297
286698
|
const matches = [...value.matchAll(/\{\{([^}]+)\}\}/g)];
|
|
286298
286699
|
let result = value;
|
|
286299
286700
|
for (const [placeholder, expr] of matches.map((m) => [m[0], m[1].trim()])) {
|
|
286300
286701
|
if (expr.includes(":") && !expr.startsWith("faker.")) {
|
|
286301
286702
|
const [scope, key] = expr.split(":", 2);
|
|
286302
|
-
const
|
|
286303
|
-
|
|
286304
|
-
result = result.replace(placeholder, val != null ? String(val) : "");
|
|
286703
|
+
const val = await resolveMemValue(scope, key, stepIndex, stepName);
|
|
286704
|
+
result = result.replace(placeholder, val);
|
|
286305
286705
|
}
|
|
286306
286706
|
}
|
|
286307
286707
|
return result;
|
|
286308
286708
|
}
|
|
286309
|
-
async function resolveStr(v) {
|
|
286709
|
+
async function resolveStr(v, stepIndex = -1, stepName = "") {
|
|
286310
286710
|
let s = await resolveTemplate(v);
|
|
286311
|
-
s = await resolveMemoryTemplates(s);
|
|
286711
|
+
s = await resolveMemoryTemplates(s, stepIndex, stepName);
|
|
286312
286712
|
return s;
|
|
286313
286713
|
}
|
|
286314
|
-
async function
|
|
286714
|
+
async function resolveValueRecursive(val, stepIndex = -1, stepName = "") {
|
|
286715
|
+
if (typeof val === "string") {
|
|
286716
|
+
return await resolveStr(val, stepIndex, stepName);
|
|
286717
|
+
}
|
|
286718
|
+
if (Array.isArray(val)) {
|
|
286719
|
+
const res2 = [];
|
|
286720
|
+
for (const item of val) {
|
|
286721
|
+
res2.push(await resolveValueRecursive(item, stepIndex, stepName));
|
|
286722
|
+
}
|
|
286723
|
+
return res2;
|
|
286724
|
+
}
|
|
286725
|
+
if (val && typeof val === "object" && val.constructor === Object) {
|
|
286726
|
+
const res2 = {};
|
|
286727
|
+
for (const [k, v] of Object.entries(val)) {
|
|
286728
|
+
res2[k] = await resolveValueRecursive(v, stepIndex, stepName);
|
|
286729
|
+
}
|
|
286730
|
+
return res2;
|
|
286731
|
+
}
|
|
286732
|
+
return val;
|
|
286733
|
+
}
|
|
286734
|
+
async function resolveMemValue(scope, key, stepIndex, stepName) {
|
|
286735
|
+
if (scope !== "human") {
|
|
286736
|
+
const mem2 = new Mem({ scope });
|
|
286737
|
+
const val = await mem2.get(key);
|
|
286738
|
+
return val != null ? String(val) : "";
|
|
286739
|
+
}
|
|
286740
|
+
if (runHumanCache.has(key)) {
|
|
286741
|
+
send("human_resolved", { index: stepIndex, key, source: "run" });
|
|
286742
|
+
return runHumanCache.get(key);
|
|
286743
|
+
}
|
|
286744
|
+
const envVal = process.env[humanEnvName(key)];
|
|
286745
|
+
if (envVal !== void 0 && envVal !== "") {
|
|
286746
|
+
send("human_resolved", { index: stepIndex, key, source: "env" });
|
|
286747
|
+
return envVal;
|
|
286748
|
+
}
|
|
286749
|
+
const mem = new Mem({ scope: "human" });
|
|
286750
|
+
const answer = await waitForHumanInput(runId, key, stepIndex, stepName, send);
|
|
286751
|
+
if (answer === null) {
|
|
286752
|
+
throw new HumanInputAbort(key, aborted ? "was cancelled" : "timed out after 10 minutes");
|
|
286753
|
+
}
|
|
286754
|
+
await mem.set(key, answer);
|
|
286755
|
+
send("human_resolved", { index: stepIndex, key, source: "prompt" });
|
|
286756
|
+
return answer;
|
|
286757
|
+
}
|
|
286758
|
+
function waitForHumanInput(forRunId, key, stepIndex, stepName, sendFn) {
|
|
286759
|
+
return new Promise((resolve2) => {
|
|
286760
|
+
const timer = setTimeout(() => {
|
|
286761
|
+
if (humanInputWaiters.get(forRunId)?.key === key) {
|
|
286762
|
+
humanInputWaiters.delete(forRunId);
|
|
286763
|
+
resolve2(null);
|
|
286764
|
+
}
|
|
286765
|
+
}, HUMAN_INPUT_TIMEOUT_MS);
|
|
286766
|
+
humanInputWaiters.set(forRunId, {
|
|
286767
|
+
key,
|
|
286768
|
+
stepIndex,
|
|
286769
|
+
resolve: (value) => {
|
|
286770
|
+
clearTimeout(timer);
|
|
286771
|
+
resolve2(value);
|
|
286772
|
+
}
|
|
286773
|
+
});
|
|
286774
|
+
sendFn("human_input_required", { runId: forRunId, key, stepIndex, stepName });
|
|
286775
|
+
});
|
|
286776
|
+
}
|
|
286777
|
+
async function executeStep2(step, stepIndex) {
|
|
286315
286778
|
const startTime = Date.now();
|
|
286779
|
+
const stepName = step.name || `${step.method} ${step.path}`;
|
|
286316
286780
|
const headers = { "Content-Type": "application/json" };
|
|
286317
286781
|
const bodyExtra = {};
|
|
286318
286782
|
for (const [target, memKeyOrTpl] of Object.entries(step.inject ?? {})) {
|
|
286319
|
-
const strVal = memKeyOrTpl.includes("{{") ? await resolveStr(memKeyOrTpl) : await (async () => {
|
|
286783
|
+
const strVal = memKeyOrTpl.includes("{{") ? await resolveStr(memKeyOrTpl, stepIndex, stepName) : await (async () => {
|
|
286320
286784
|
const [scope, key] = memKeyOrTpl.includes(":") ? memKeyOrTpl.split(":", 2) : ["workflow", memKeyOrTpl];
|
|
286321
|
-
|
|
286322
|
-
return val != null ? String(val) : "";
|
|
286785
|
+
return resolveMemValue(scope, key, stepIndex, stepName);
|
|
286323
286786
|
})();
|
|
286324
286787
|
if (!strVal) continue;
|
|
286325
286788
|
if (target.startsWith("body:")) bodyExtra[target.slice(5)] = strVal;
|
|
286326
286789
|
else headers[target.startsWith("header:") ? target.slice(7) : target] = strVal;
|
|
286327
286790
|
}
|
|
286328
|
-
const resolvedBody = {};
|
|
286329
|
-
for (const [k, v] of Object.entries(step.body ?? {})) {
|
|
286330
|
-
resolvedBody[k] = typeof v === "string" ? await resolveStr(v) : v;
|
|
286331
|
-
}
|
|
286791
|
+
const resolvedBody = await resolveValueRecursive(step.body ?? {}, stepIndex, stepName);
|
|
286332
286792
|
const requestBody = { ...bodyExtra, ...resolvedBody };
|
|
286333
286793
|
for (const [memKey, bodyField] of Object.entries(step.captureInput ?? {})) {
|
|
286334
286794
|
const val = requestBody[bodyField];
|
|
@@ -286341,7 +286801,7 @@ Output valid JSON only.
|
|
|
286341
286801
|
/:([a-zA-Z_][a-zA-Z0-9_]*)/g,
|
|
286342
286802
|
(_, p) => requestBody[p] !== void 0 ? String(requestBody[p]) : `:${p}`
|
|
286343
286803
|
);
|
|
286344
|
-
resolvedPath = await resolveStr(resolvedPath);
|
|
286804
|
+
resolvedPath = await resolveStr(resolvedPath, stepIndex, stepName);
|
|
286345
286805
|
const finalUrl = `${baseUrl.replace(/\/$/, "")}${resolvedPath}`;
|
|
286346
286806
|
const expectedStatus = step.expectStatus ?? 200;
|
|
286347
286807
|
try {
|
|
@@ -286384,22 +286844,20 @@ Output valid JSON only.
|
|
|
286384
286844
|
}
|
|
286385
286845
|
let passed = 0;
|
|
286386
286846
|
let failed = 0;
|
|
286387
|
-
async function resolveCondRef(ref) {
|
|
286847
|
+
async function resolveCondRef(ref, stepIndex, stepName) {
|
|
286388
286848
|
const matches = [...ref.matchAll(/\{\{([^}]+)\}\}/g)];
|
|
286389
286849
|
let result = ref;
|
|
286390
286850
|
for (const [placeholder, expr] of matches.map((m) => [m[0], m[1].trim()])) {
|
|
286391
286851
|
if (expr.includes(":") && !expr.startsWith("faker.")) {
|
|
286392
286852
|
const [scope, key] = expr.split(":", 2);
|
|
286393
|
-
|
|
286394
|
-
const val = await mem.get(key);
|
|
286395
|
-
result = result.replace(placeholder, val != null ? String(val) : "");
|
|
286853
|
+
result = result.replace(placeholder, await resolveMemValue(scope, key, stepIndex, stepName));
|
|
286396
286854
|
}
|
|
286397
286855
|
}
|
|
286398
286856
|
return result;
|
|
286399
286857
|
}
|
|
286400
|
-
async function evalCondRule(rule) {
|
|
286401
|
-
const left = await resolveCondRef(rule.left);
|
|
286402
|
-
const right = rule.right !== void 0 ? await resolveCondRef(rule.right) : void 0;
|
|
286858
|
+
async function evalCondRule(rule, stepIndex, stepName) {
|
|
286859
|
+
const left = await resolveCondRef(rule.left, stepIndex, stepName);
|
|
286860
|
+
const right = rule.right !== void 0 ? await resolveCondRef(rule.right, stepIndex, stepName) : void 0;
|
|
286403
286861
|
switch (rule.operator) {
|
|
286404
286862
|
case "equals":
|
|
286405
286863
|
return left === right;
|
|
@@ -286433,12 +286891,12 @@ Output valid JSON only.
|
|
|
286433
286891
|
return false;
|
|
286434
286892
|
}
|
|
286435
286893
|
}
|
|
286436
|
-
async function evalCondition(condition) {
|
|
286894
|
+
async function evalCondition(condition, stepIndex, stepName) {
|
|
286437
286895
|
const rules = condition.rules?.all ?? condition.rules?.any ?? [];
|
|
286438
286896
|
const isAnd = !!condition.rules?.all;
|
|
286439
286897
|
if (rules.length === 0) return { passed: true, reason: "No rules" };
|
|
286440
286898
|
const results = [];
|
|
286441
|
-
for (const rule of rules) results.push({ rule, ok: await evalCondRule(rule) });
|
|
286899
|
+
for (const rule of rules) results.push({ rule, ok: await evalCondRule(rule, stepIndex, stepName) });
|
|
286442
286900
|
const passed2 = isAnd ? results.every((r) => r.ok) : results.some((r) => r.ok);
|
|
286443
286901
|
const failing = results.filter((r) => !r.ok);
|
|
286444
286902
|
const reason = passed2 ? `All conditions met (${isAnd ? "AND" : "OR"})` : `Failed: ${failing.map((r) => `${r.rule.left} ${r.rule.operator}${r.rule.right !== void 0 ? " " + r.rule.right : ""}`).join(", ")}`;
|
|
@@ -286448,7 +286906,17 @@ Output valid JSON only.
|
|
|
286448
286906
|
if (aborted) break;
|
|
286449
286907
|
const step = workflow.steps[i];
|
|
286450
286908
|
send("step_start", { index: i, step: { name: step.name, method: step.method, path: step.path } });
|
|
286451
|
-
|
|
286909
|
+
let result;
|
|
286910
|
+
try {
|
|
286911
|
+
result = await executeStep2(step, i);
|
|
286912
|
+
} catch (err) {
|
|
286913
|
+
if (err instanceof HumanInputAbort || err?.name === "HumanInputAbort") {
|
|
286914
|
+
send("aborted", { index: i, reason: err.message });
|
|
286915
|
+
res.end();
|
|
286916
|
+
return;
|
|
286917
|
+
}
|
|
286918
|
+
throw err;
|
|
286919
|
+
}
|
|
286452
286920
|
if (result.passed) passed++;
|
|
286453
286921
|
else failed++;
|
|
286454
286922
|
send("step_result", {
|
|
@@ -286463,7 +286931,7 @@ Output valid JSON only.
|
|
|
286463
286931
|
error: result.error
|
|
286464
286932
|
});
|
|
286465
286933
|
if (step.condition && !aborted) {
|
|
286466
|
-
const condResult = await evalCondition(step.condition);
|
|
286934
|
+
const condResult = await evalCondition(step.condition, i, step.name || `${step.method} ${step.path}`);
|
|
286467
286935
|
send("condition_result", {
|
|
286468
286936
|
index: i,
|
|
286469
286937
|
passed: condResult.passed,
|
|
@@ -286608,29 +287076,34 @@ Output valid JSON only.
|
|
|
286608
287076
|
}
|
|
286609
287077
|
});
|
|
286610
287078
|
try {
|
|
286611
|
-
let dashboardDistPath =
|
|
286612
|
-
|
|
286613
|
-
|
|
287079
|
+
let dashboardDistPath = null;
|
|
287080
|
+
try {
|
|
287081
|
+
const dashboardPackagePath = require.resolve("@jetic/dashboard/package.json");
|
|
287082
|
+
const workspaceDist = import_path.default.join(import_path.default.dirname(dashboardPackagePath), "dist");
|
|
287083
|
+
if (import_fs.default.existsSync(workspaceDist)) dashboardDistPath = workspaceDist;
|
|
287084
|
+
} catch {
|
|
286614
287085
|
}
|
|
286615
|
-
if (!
|
|
286616
|
-
|
|
286617
|
-
|
|
286618
|
-
dashboardDistPath = import_path.default.join(import_path.default.dirname(dashboardPackagePath), "dist");
|
|
286619
|
-
} catch {
|
|
286620
|
-
}
|
|
287086
|
+
if (!dashboardDistPath) {
|
|
287087
|
+
const bundled = import_path.default.join(__dirname, "dashboard");
|
|
287088
|
+
if (import_fs.default.existsSync(bundled)) dashboardDistPath = bundled;
|
|
286621
287089
|
}
|
|
286622
|
-
if (!
|
|
286623
|
-
|
|
287090
|
+
if (!dashboardDistPath) {
|
|
287091
|
+
const sibling = import_path.default.join(__dirname, "..", "dashboard");
|
|
287092
|
+
if (import_fs.default.existsSync(sibling)) dashboardDistPath = sibling;
|
|
287093
|
+
}
|
|
287094
|
+
if (!dashboardDistPath) {
|
|
287095
|
+
console.warn("Dashboard build not found. Build it with: pnpm --filter @jetic/dashboard run build");
|
|
287096
|
+
} else {
|
|
287097
|
+
app.use(import_express.default.static(dashboardDistPath));
|
|
287098
|
+
app.get("*", (_req, res) => {
|
|
287099
|
+
const indexPath = import_path.default.join(dashboardDistPath, "index.html");
|
|
287100
|
+
if (import_fs.default.existsSync(indexPath)) {
|
|
287101
|
+
res.sendFile(indexPath);
|
|
287102
|
+
} else {
|
|
287103
|
+
res.status(404).send("Dashboard not built yet.");
|
|
287104
|
+
}
|
|
287105
|
+
});
|
|
286624
287106
|
}
|
|
286625
|
-
app.use(import_express.default.static(dashboardDistPath));
|
|
286626
|
-
app.get("*", (_req, res) => {
|
|
286627
|
-
const indexPath = import_path.default.join(dashboardDistPath, "index.html");
|
|
286628
|
-
if (import_fs.default.existsSync(indexPath)) {
|
|
286629
|
-
res.sendFile(indexPath);
|
|
286630
|
-
} else {
|
|
286631
|
-
res.status(404).send("Dashboard not built yet.");
|
|
286632
|
-
}
|
|
286633
|
-
});
|
|
286634
287107
|
} catch (e) {
|
|
286635
287108
|
console.error("Could not find dashboard static files. Ensure it is built.", e);
|
|
286636
287109
|
}
|
|
@@ -286801,7 +287274,7 @@ var mcpCommand = new import_commander10.Command("mcp").description("Launch Model
|
|
|
286801
287274
|
|
|
286802
287275
|
// src/index.ts
|
|
286803
287276
|
var program = new import_commander11.Command();
|
|
286804
|
-
program.name("jetic").description("AI-Native API Behavior Testing").version("0.1.
|
|
287277
|
+
program.name("jetic").description("AI-Native API Behavior Testing").version("0.1.9");
|
|
286805
287278
|
program.addCommand(initCommand);
|
|
286806
287279
|
program.addCommand(scanCommand);
|
|
286807
287280
|
program.addCommand(inspectCommand);
|