@fluentcommerce/fluent-mcp-extn 0.2.1 → 0.3.1
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 +112 -11
- package/dist/entity-registry.js +1 -0
- package/dist/fluent-client.js +30 -0
- package/dist/settings-tools.js +285 -4
- package/dist/tools.js +48 -7
- package/dist/workflow-tools.js +286 -0
- package/docs/E2E_TESTING.md +12 -12
- package/docs/RUNBOOK.md +5 -2
- package/docs/TOOL_REFERENCE.md +116 -3
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -6,8 +6,8 @@ import { buildEventPayload } from "./event-payload.js";
|
|
|
6
6
|
import { ToolError, toToolFailure } from "./errors.js";
|
|
7
7
|
// New tool modules
|
|
8
8
|
import { ENTITY_TOOL_DEFINITIONS, handleEntityCreate, handleEntityUpdate, handleEntityGet, } from "./entity-tools.js";
|
|
9
|
-
import { WORKFLOW_TOOL_DEFINITIONS, handleWorkflowUpload, handleWorkflowDiff, handleWorkflowSimulate, } from "./workflow-tools.js";
|
|
10
|
-
import { SETTING_TOOL_DEFINITIONS, handleSettingUpsert, handleSettingBulkUpsert, } from "./settings-tools.js";
|
|
9
|
+
import { WORKFLOW_TOOL_DEFINITIONS, handleWorkflowGet, handleWorkflowList, handleWorkflowUpload, handleWorkflowDiff, handleWorkflowSimulate, } from "./workflow-tools.js";
|
|
10
|
+
import { SETTING_TOOL_DEFINITIONS, handleSettingGet, handleSettingUpsert, handleSettingBulkUpsert, } from "./settings-tools.js";
|
|
11
11
|
import { ENVIRONMENT_TOOL_DEFINITIONS, handleEnvironmentDiscover, handleEnvironmentValidate, } from "./environment-tools.js";
|
|
12
12
|
import { TEST_TOOL_DEFINITIONS, handleTestAssert, } from "./test-tools.js";
|
|
13
13
|
import { shapeResponse, summarizeConnection, analyzeEvents, } from "./response-shaper.js";
|
|
@@ -128,6 +128,8 @@ const TransitionTriggerInputSchema = z.object({
|
|
|
128
128
|
module: z.string().min(1).optional(),
|
|
129
129
|
flexType: z.string().min(1).optional(),
|
|
130
130
|
flexVersion: z.union([z.string().min(1), z.number().int()]).optional(),
|
|
131
|
+
entityId: z.string().min(1).optional(),
|
|
132
|
+
entityRef: z.string().min(1).optional(),
|
|
131
133
|
});
|
|
132
134
|
const TransitionActionsInputSchema = z.object({
|
|
133
135
|
triggers: z.array(TransitionTriggerInputSchema).min(1),
|
|
@@ -599,8 +601,16 @@ const TOOL_DEFINITIONS = [
|
|
|
599
601
|
"INTEGRATION: The eventName from each userAction maps directly to event.send's \"name\" parameter.",
|
|
600
602
|
"The attributes[] tell you what to include in event.send's \"attributes\" parameter.",
|
|
601
603
|
"",
|
|
604
|
+
"CRITICAL: The Transition API requires `flexType` (e.g. ORDER::HD) to return results.",
|
|
605
|
+
"Without it the API silently returns empty `{response:[]}`. For some entity types",
|
|
606
|
+
"(e.g. CREDIT_MEMO) `flexVersion` is also required. When `type` and `subtype` are",
|
|
607
|
+
"provided but `flexType` is not, the tool auto-derives it as `TYPE::SUBTYPE`.",
|
|
608
|
+
"",
|
|
609
|
+
"BEST PRACTICE: Always provide `flexType` and `flexVersion` for reliable results.",
|
|
610
|
+
"Get these from the entity's `workflowRef` and `workflowVersion` fields via GraphQL.",
|
|
611
|
+
"",
|
|
602
612
|
"EXAMPLE: Find available actions for an ORDER in CREATED status:",
|
|
603
|
-
"{ triggers: [{ type: \"ORDER\", subtype: \"HD\", status: \"CREATED\", retailerId: \"5\", flexType: \"ORDER::HD\" }] }",
|
|
613
|
+
"{ triggers: [{ type: \"ORDER\", subtype: \"HD\", status: \"CREATED\", retailerId: \"5\", flexType: \"ORDER::HD\", flexVersion: 4 }] }",
|
|
604
614
|
].join("\n"),
|
|
605
615
|
inputSchema: {
|
|
606
616
|
type: "object",
|
|
@@ -623,17 +633,19 @@ const TOOL_DEFINITIONS = [
|
|
|
623
633
|
},
|
|
624
634
|
module: {
|
|
625
635
|
type: "string",
|
|
626
|
-
description: "
|
|
636
|
+
description: "REQUIRED for results. App module name (e.g. adminconsole, oms, store, servicepoint). Without this, API silently returns empty userActions. Case-sensitive.",
|
|
627
637
|
},
|
|
628
638
|
flexType: {
|
|
629
639
|
type: "string",
|
|
630
|
-
description: "Workflow type (e.g. ORDER::HD, FULFILMENT::HD_WH).",
|
|
640
|
+
description: "Workflow type (e.g. ORDER::HD, FULFILMENT::HD_WH). REQUIRED for API to return results. Auto-derived from type::subtype when omitted.",
|
|
631
641
|
},
|
|
632
642
|
flexVersion: {
|
|
633
643
|
oneOf: [{ type: "integer" }, { type: "string" }],
|
|
634
|
-
description: "Workflow version.
|
|
644
|
+
description: "Workflow version. Strongly recommended — required for some entity types (e.g. CREDIT_MEMO). Get from entity's workflowVersion field.",
|
|
635
645
|
},
|
|
636
646
|
name: { type: "string", description: "Event name filter" },
|
|
647
|
+
entityId: { type: "string", description: "Entity ID (strongly recommended — API may return empty without it)" },
|
|
648
|
+
entityRef: { type: "string", description: "Entity ref (strongly recommended — API may return empty without it)" },
|
|
637
649
|
},
|
|
638
650
|
required: ["retailerId"],
|
|
639
651
|
additionalProperties: false,
|
|
@@ -1258,7 +1270,14 @@ export function toEventQueryParams(input) {
|
|
|
1258
1270
|
return params;
|
|
1259
1271
|
}
|
|
1260
1272
|
/**
|
|
1261
|
-
* Fill missing trigger
|
|
1273
|
+
* Fill missing trigger fields from runtime config and validate readiness.
|
|
1274
|
+
*
|
|
1275
|
+
* The Transition API requires `flexType` (e.g. "ORDER::HD") to return results.
|
|
1276
|
+
* Without it the API silently returns `{"response":[]}`. For some entity
|
|
1277
|
+
* types (e.g. CREDIT_MEMO) `flexVersion` is also required.
|
|
1278
|
+
*
|
|
1279
|
+
* This function auto-derives `flexType` from `type`+`subtype` when both are
|
|
1280
|
+
* present and `flexType` is not explicitly set.
|
|
1262
1281
|
*/
|
|
1263
1282
|
export function resolveTransitionRequest(input, fallbackRetailerId) {
|
|
1264
1283
|
const triggers = input.triggers.map((trigger, index) => {
|
|
@@ -1266,9 +1285,16 @@ export function resolveTransitionRequest(input, fallbackRetailerId) {
|
|
|
1266
1285
|
if (!retailerId) {
|
|
1267
1286
|
throw new ToolError("VALIDATION_ERROR", `triggers[${index}].retailerId is required. Set FLUENT_RETAILER_ID or pass retailerId per trigger.`);
|
|
1268
1287
|
}
|
|
1288
|
+
// Auto-derive flexType from type+subtype when not explicitly provided.
|
|
1289
|
+
// The Transition API requires flexType to return user actions.
|
|
1290
|
+
let flexType = trigger.flexType;
|
|
1291
|
+
if (!flexType && trigger.type && trigger.subtype) {
|
|
1292
|
+
flexType = `${trigger.type}::${trigger.subtype}`;
|
|
1293
|
+
}
|
|
1269
1294
|
return {
|
|
1270
1295
|
...trigger,
|
|
1271
1296
|
retailerId,
|
|
1297
|
+
...(flexType ? { flexType } : {}),
|
|
1272
1298
|
};
|
|
1273
1299
|
});
|
|
1274
1300
|
return { triggers };
|
|
@@ -3212,6 +3238,16 @@ export function registerToolHandlers(server, ctx) {
|
|
|
3212
3238
|
const result = await handleEntityGet(args, ctx);
|
|
3213
3239
|
return json(result, false, ctx.responseBudget);
|
|
3214
3240
|
}
|
|
3241
|
+
// ------- workflow.get ---------------------------------------------------
|
|
3242
|
+
if (toolName === "workflow.get") {
|
|
3243
|
+
const result = await handleWorkflowGet(args, ctx);
|
|
3244
|
+
return json(result, false, ctx.responseBudget);
|
|
3245
|
+
}
|
|
3246
|
+
// ------- workflow.list --------------------------------------------------
|
|
3247
|
+
if (toolName === "workflow.list") {
|
|
3248
|
+
const result = await handleWorkflowList(args, ctx);
|
|
3249
|
+
return json(result, false, ctx.responseBudget);
|
|
3250
|
+
}
|
|
3215
3251
|
// ------- workflow.upload -----------------------------------------------
|
|
3216
3252
|
if (toolName === "workflow.upload") {
|
|
3217
3253
|
const result = await handleWorkflowUpload(args, ctx);
|
|
@@ -3227,6 +3263,11 @@ export function registerToolHandlers(server, ctx) {
|
|
|
3227
3263
|
const result = await handleWorkflowSimulate(args, ctx);
|
|
3228
3264
|
return json(result, false, ctx.responseBudget);
|
|
3229
3265
|
}
|
|
3266
|
+
// ------- setting.get ---------------------------------------------------
|
|
3267
|
+
if (toolName === "setting.get") {
|
|
3268
|
+
const result = await handleSettingGet(args, ctx);
|
|
3269
|
+
return json(result, false, ctx.responseBudget);
|
|
3270
|
+
}
|
|
3230
3271
|
// ------- setting.upsert ------------------------------------------------
|
|
3231
3272
|
if (toolName === "setting.upsert") {
|
|
3232
3273
|
const result = await handleSettingUpsert(args, ctx);
|
package/dist/workflow-tools.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* event outcomes without live execution.
|
|
8
8
|
*/
|
|
9
9
|
import { z } from "zod";
|
|
10
|
+
import * as fs from "node:fs";
|
|
11
|
+
import * as path from "node:path";
|
|
10
12
|
import { ToolError } from "./errors.js";
|
|
11
13
|
// ---------------------------------------------------------------------------
|
|
12
14
|
// Input schemas
|
|
@@ -40,6 +42,42 @@ export const WorkflowDiffInputSchema = z.object({
|
|
|
40
42
|
.default("summary")
|
|
41
43
|
.describe("Output format: summary (default), detailed, or mermaid"),
|
|
42
44
|
});
|
|
45
|
+
export const WorkflowGetInputSchema = z.object({
|
|
46
|
+
entityType: z
|
|
47
|
+
.string()
|
|
48
|
+
.describe("Workflow entity type (e.g., ORDER, FULFILMENT, INVENTORY_CATALOGUE, RETURN_ORDER, BILLING_ACCOUNT, VIRTUAL_CATALOGUE, PRODUCT_CATALOGUE, CONTROL_GROUP, PAYMENT, WAVE, CONSIGNMENT, ARTICLE)."),
|
|
49
|
+
entitySubtype: z
|
|
50
|
+
.string()
|
|
51
|
+
.describe("Workflow entity subtype (e.g., HD, DEFAULT, MASTER, BASE, CC, CUSTOMER, REFUND, PAYMENT)."),
|
|
52
|
+
retailerId: z
|
|
53
|
+
.string()
|
|
54
|
+
.optional()
|
|
55
|
+
.describe("Target retailer ID. Falls back to FLUENT_RETAILER_ID."),
|
|
56
|
+
version: z
|
|
57
|
+
.string()
|
|
58
|
+
.optional()
|
|
59
|
+
.describe("Specific workflow version to fetch. Omit for latest."),
|
|
60
|
+
outputFile: z
|
|
61
|
+
.string()
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("Optional file path to save the full workflow JSON. " +
|
|
64
|
+
"When provided, writes workflow to file and returns only summary " +
|
|
65
|
+
"(name, version, status/ruleset counts) — keeps large workflow JSON out of the LLM context. " +
|
|
66
|
+
"Parent directories are created automatically."),
|
|
67
|
+
});
|
|
68
|
+
export const WorkflowListInputSchema = z.object({
|
|
69
|
+
retailerId: z
|
|
70
|
+
.string()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Target retailer ID. Falls back to FLUENT_RETAILER_ID."),
|
|
73
|
+
outputDir: z
|
|
74
|
+
.string()
|
|
75
|
+
.optional()
|
|
76
|
+
.describe("Optional directory path to download ALL workflows (latest version each) as individual JSON files. " +
|
|
77
|
+
"Each file is named <ENTITY_TYPE>-<ENTITY_SUBTYPE>.json (e.g., ORDER-HD.json). " +
|
|
78
|
+
"Returns only summary metadata — full workflow JSON is NOT included in the response. " +
|
|
79
|
+
"Reads from live server (NOT workflowlog cache). Parent directories are created automatically."),
|
|
80
|
+
});
|
|
43
81
|
export const WorkflowSimulateInputSchema = z.object({
|
|
44
82
|
workflow: z
|
|
45
83
|
.union([z.string(), z.record(z.string(), z.unknown())])
|
|
@@ -64,6 +102,91 @@ export const WorkflowSimulateInputSchema = z.object({
|
|
|
64
102
|
// Tool definitions (JSON Schema for MCP)
|
|
65
103
|
// ---------------------------------------------------------------------------
|
|
66
104
|
export const WORKFLOW_TOOL_DEFINITIONS = [
|
|
105
|
+
{
|
|
106
|
+
name: "workflow.get",
|
|
107
|
+
description: [
|
|
108
|
+
"Fetch a workflow definition by entity type and subtype from the Fluent REST API.",
|
|
109
|
+
"",
|
|
110
|
+
"Calls GET /api/v4.1/workflow/{retailerId}/{entityType}::{entitySubtype}/",
|
|
111
|
+
"Returns the full workflow JSON including statuses, rulesets, rules, and triggers.",
|
|
112
|
+
"",
|
|
113
|
+
"NOTE: This fetches a specific workflow by name, which works even when the list",
|
|
114
|
+
"endpoint returns 401 (insufficient permissions for listing).",
|
|
115
|
+
"",
|
|
116
|
+
"KNOWN ENTITY TYPES:",
|
|
117
|
+
"ORDER, FULFILMENT, INVENTORY_CATALOGUE, RETURN_ORDER, BILLING_ACCOUNT,",
|
|
118
|
+
"VIRTUAL_CATALOGUE, PRODUCT_CATALOGUE, CONTROL_GROUP, PAYMENT, WAVE,",
|
|
119
|
+
"CONSIGNMENT, ARTICLE, LOCATION, NETWORK, CUSTOMER, CREDIT_MEMO",
|
|
120
|
+
"",
|
|
121
|
+
"COMMON SUBTYPES:",
|
|
122
|
+
"HD, CC, DEFAULT, MASTER, BASE, CUSTOMER, REFUND, PAYMENT, HD_WH, HD_MP",
|
|
123
|
+
"",
|
|
124
|
+
"RESPONSE includes: name, version, entityType, entitySubtype, statuses, rulesets,",
|
|
125
|
+
"and a summary with status/ruleset/rule counts.",
|
|
126
|
+
"",
|
|
127
|
+
"Use outputFile to save large workflow JSON to disk instead of returning inline.",
|
|
128
|
+
"Returns only summary (name, version, counts) when outputFile is set.",
|
|
129
|
+
].join("\n"),
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: {
|
|
133
|
+
entityType: {
|
|
134
|
+
type: "string",
|
|
135
|
+
description: "Workflow entity type (e.g., ORDER, FULFILMENT, INVENTORY_CATALOGUE)",
|
|
136
|
+
},
|
|
137
|
+
entitySubtype: {
|
|
138
|
+
type: "string",
|
|
139
|
+
description: "Workflow entity subtype (e.g., HD, DEFAULT, MASTER)",
|
|
140
|
+
},
|
|
141
|
+
retailerId: {
|
|
142
|
+
type: "string",
|
|
143
|
+
description: "Target retailer ID.",
|
|
144
|
+
},
|
|
145
|
+
version: {
|
|
146
|
+
type: "string",
|
|
147
|
+
description: "Specific workflow version. Omit for latest.",
|
|
148
|
+
},
|
|
149
|
+
outputFile: {
|
|
150
|
+
type: "string",
|
|
151
|
+
description: "File path to save workflow JSON. Returns summary only (not full JSON) when set.",
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
required: ["entityType", "entitySubtype"],
|
|
155
|
+
additionalProperties: false,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: "workflow.list",
|
|
160
|
+
description: [
|
|
161
|
+
"List all workflows for a retailer from the Fluent REST API.",
|
|
162
|
+
"",
|
|
163
|
+
"Calls GET /api/v4.1/workflow?retailerId={retailerId}",
|
|
164
|
+
"Returns a list of workflow summaries (name, version, entityType, entitySubtype, status).",
|
|
165
|
+
"",
|
|
166
|
+
"When outputDir is provided, downloads ALL workflows (latest version each) as individual",
|
|
167
|
+
"JSON files to the directory. Each file is named <TYPE>-<SUBTYPE>.json (e.g., ORDER-HD.json).",
|
|
168
|
+
"Returns only summary metadata — full JSON stays on disk, not in the response.",
|
|
169
|
+
"Reads from LIVE server, not the workflowlog cache.",
|
|
170
|
+
"",
|
|
171
|
+
"NOTE: This endpoint may return 401 on some accounts where the user lacks listing",
|
|
172
|
+
"permissions. In that case, use workflow.get to fetch a specific workflow by name.",
|
|
173
|
+
].join("\n"),
|
|
174
|
+
inputSchema: {
|
|
175
|
+
type: "object",
|
|
176
|
+
properties: {
|
|
177
|
+
retailerId: {
|
|
178
|
+
type: "string",
|
|
179
|
+
description: "Target retailer ID.",
|
|
180
|
+
},
|
|
181
|
+
outputDir: {
|
|
182
|
+
type: "string",
|
|
183
|
+
description: "Directory to save all workflows as individual JSON files. Returns summary only when set.",
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
required: [],
|
|
187
|
+
additionalProperties: false,
|
|
188
|
+
},
|
|
189
|
+
},
|
|
67
190
|
{
|
|
68
191
|
name: "workflow.upload",
|
|
69
192
|
description: [
|
|
@@ -750,3 +873,166 @@ export async function handleWorkflowSimulate(args, _ctx) {
|
|
|
750
873
|
limitations,
|
|
751
874
|
};
|
|
752
875
|
}
|
|
876
|
+
/**
|
|
877
|
+
* Handle workflow.get tool call.
|
|
878
|
+
* Fetches a specific workflow by entityType::entitySubtype via REST API.
|
|
879
|
+
*/
|
|
880
|
+
export async function handleWorkflowGet(args, ctx) {
|
|
881
|
+
const parsed = WorkflowGetInputSchema.parse(args);
|
|
882
|
+
const retailerId = parsed.retailerId ?? ctx.config.retailerId;
|
|
883
|
+
if (!retailerId) {
|
|
884
|
+
throw new ToolError("VALIDATION_ERROR", "retailerId is required. Set FLUENT_RETAILER_ID or pass retailerId.");
|
|
885
|
+
}
|
|
886
|
+
const client = requireWorkflowClient(ctx);
|
|
887
|
+
const workflowName = `${parsed.entityType}::${parsed.entitySubtype}`;
|
|
888
|
+
const response = await client.getWorkflow(retailerId, parsed.entityType, parsed.entitySubtype, parsed.version);
|
|
889
|
+
// Extract summary info from the workflow
|
|
890
|
+
const wf = response;
|
|
891
|
+
if (!wf || typeof wf !== "object") {
|
|
892
|
+
throw new ToolError("VALIDATION_ERROR", `No workflow found for ${workflowName} on retailer ${retailerId}.`);
|
|
893
|
+
}
|
|
894
|
+
const statuses = (wf.statuses ?? wf.subStatuses);
|
|
895
|
+
const rulesets = wf.rulesets;
|
|
896
|
+
const totalRules = (rulesets ?? []).reduce((sum, rs) => {
|
|
897
|
+
const rules = rs.rules;
|
|
898
|
+
return sum + (rules?.length ?? 0);
|
|
899
|
+
}, 0);
|
|
900
|
+
const summary = {
|
|
901
|
+
statuses: statuses?.length ?? 0,
|
|
902
|
+
rulesets: rulesets?.length ?? 0,
|
|
903
|
+
totalRules,
|
|
904
|
+
};
|
|
905
|
+
// If outputFile is provided, write workflow to file and return summary only
|
|
906
|
+
if (parsed.outputFile) {
|
|
907
|
+
const fileContent = JSON.stringify(wf, null, 2);
|
|
908
|
+
const dir = path.dirname(parsed.outputFile);
|
|
909
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
910
|
+
fs.writeFileSync(parsed.outputFile, fileContent, "utf-8");
|
|
911
|
+
return {
|
|
912
|
+
ok: true,
|
|
913
|
+
workflowName: wf.name ?? workflowName,
|
|
914
|
+
version: wf.version,
|
|
915
|
+
entityType: wf.entityType ?? parsed.entityType,
|
|
916
|
+
entitySubtype: wf.entitySubtype ?? parsed.entitySubtype,
|
|
917
|
+
retailerId,
|
|
918
|
+
summary,
|
|
919
|
+
savedTo: parsed.outputFile,
|
|
920
|
+
sizeBytes: Buffer.byteLength(fileContent, "utf-8"),
|
|
921
|
+
message: `Workflow saved to ${parsed.outputFile} (${Buffer.byteLength(fileContent, "utf-8")} bytes). Full JSON NOT included in response.`,
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
return {
|
|
925
|
+
ok: true,
|
|
926
|
+
workflowName: wf.name ?? workflowName,
|
|
927
|
+
version: wf.version,
|
|
928
|
+
entityType: wf.entityType ?? parsed.entityType,
|
|
929
|
+
entitySubtype: wf.entitySubtype ?? parsed.entitySubtype,
|
|
930
|
+
retailerId,
|
|
931
|
+
summary,
|
|
932
|
+
workflow: wf,
|
|
933
|
+
};
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Handle workflow.list tool call.
|
|
937
|
+
* Lists all workflows for a retailer via REST API.
|
|
938
|
+
*/
|
|
939
|
+
export async function handleWorkflowList(args, ctx) {
|
|
940
|
+
const parsed = WorkflowListInputSchema.parse(args);
|
|
941
|
+
const retailerId = parsed.retailerId ?? ctx.config.retailerId;
|
|
942
|
+
if (!retailerId) {
|
|
943
|
+
throw new ToolError("VALIDATION_ERROR", "retailerId is required. Set FLUENT_RETAILER_ID or pass retailerId.");
|
|
944
|
+
}
|
|
945
|
+
const client = requireWorkflowClient(ctx);
|
|
946
|
+
const response = await client.listWorkflows(retailerId);
|
|
947
|
+
// Response can be: bare array, paginated { results: [...] }, or unwrapped from { data: ... }
|
|
948
|
+
let workflows;
|
|
949
|
+
if (Array.isArray(response)) {
|
|
950
|
+
workflows = response;
|
|
951
|
+
}
|
|
952
|
+
else if (response && typeof response === "object" && "results" in response) {
|
|
953
|
+
workflows = response.results;
|
|
954
|
+
}
|
|
955
|
+
else {
|
|
956
|
+
workflows = [];
|
|
957
|
+
}
|
|
958
|
+
// Deduplicate: group by name, keep only latest version
|
|
959
|
+
const latestByName = new Map();
|
|
960
|
+
for (const wf of workflows) {
|
|
961
|
+
const w = wf;
|
|
962
|
+
const name = w.name;
|
|
963
|
+
if (!name)
|
|
964
|
+
continue;
|
|
965
|
+
const existing = latestByName.get(name);
|
|
966
|
+
if (!existing || Number(w.version) > Number(existing.version)) {
|
|
967
|
+
latestByName.set(name, w);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
const latest = [...latestByName.values()].map((w) => ({
|
|
971
|
+
name: w.name,
|
|
972
|
+
version: w.version,
|
|
973
|
+
entityType: w.entityType,
|
|
974
|
+
entitySubtype: w.entitySubtype,
|
|
975
|
+
status: w.status,
|
|
976
|
+
createdOn: w.createdOn,
|
|
977
|
+
}));
|
|
978
|
+
// If outputDir is provided, download each workflow's full JSON to individual files
|
|
979
|
+
if (parsed.outputDir && latest.length > 0) {
|
|
980
|
+
fs.mkdirSync(parsed.outputDir, { recursive: true });
|
|
981
|
+
const savedFiles = [];
|
|
982
|
+
const errors = [];
|
|
983
|
+
for (const wf of latest) {
|
|
984
|
+
const entityType = wf.entityType;
|
|
985
|
+
const entitySubtype = wf.entitySubtype;
|
|
986
|
+
if (!entityType || !entitySubtype)
|
|
987
|
+
continue;
|
|
988
|
+
try {
|
|
989
|
+
const fullWf = await client.getWorkflow(retailerId, entityType, entitySubtype);
|
|
990
|
+
if (fullWf && typeof fullWf === "object") {
|
|
991
|
+
const fileContent = JSON.stringify(fullWf, null, 2);
|
|
992
|
+
const fileName = `${entityType}-${entitySubtype}.json`;
|
|
993
|
+
const filePath = path.join(parsed.outputDir, fileName);
|
|
994
|
+
fs.writeFileSync(filePath, fileContent, "utf-8");
|
|
995
|
+
savedFiles.push({
|
|
996
|
+
name: wf.name,
|
|
997
|
+
file: filePath,
|
|
998
|
+
sizeBytes: Buffer.byteLength(fileContent, "utf-8"),
|
|
999
|
+
version: wf.version,
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
catch (err) {
|
|
1004
|
+
errors.push({
|
|
1005
|
+
name: `${entityType}::${entitySubtype}`,
|
|
1006
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1007
|
+
});
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
const totalBytes = savedFiles.reduce((s, f) => s + f.sizeBytes, 0);
|
|
1011
|
+
return {
|
|
1012
|
+
ok: true,
|
|
1013
|
+
retailerId,
|
|
1014
|
+
totalVersions: workflows.length,
|
|
1015
|
+
uniqueWorkflows: latest.length,
|
|
1016
|
+
savedTo: parsed.outputDir,
|
|
1017
|
+
filesWritten: savedFiles.length,
|
|
1018
|
+
totalSizeBytes: totalBytes,
|
|
1019
|
+
files: savedFiles,
|
|
1020
|
+
...(errors.length > 0 ? { errors } : {}),
|
|
1021
|
+
message: `${savedFiles.length} workflow(s) saved to ${parsed.outputDir}/ (${(totalBytes / 1024).toFixed(1)} KB total). ` +
|
|
1022
|
+
`Full JSON NOT included in response.` +
|
|
1023
|
+
(errors.length > 0
|
|
1024
|
+
? ` ${errors.length} workflow(s) failed to download.`
|
|
1025
|
+
: ""),
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
return {
|
|
1029
|
+
ok: true,
|
|
1030
|
+
retailerId,
|
|
1031
|
+
totalVersions: workflows.length,
|
|
1032
|
+
uniqueWorkflows: latest.length,
|
|
1033
|
+
workflows: latest,
|
|
1034
|
+
note: workflows.length === 0
|
|
1035
|
+
? "No workflows found. The list endpoint may return 401 on some accounts — try workflow.get with a specific entityType and entitySubtype instead."
|
|
1036
|
+
: undefined,
|
|
1037
|
+
};
|
|
1038
|
+
}
|
package/docs/E2E_TESTING.md
CHANGED
|
@@ -235,8 +235,8 @@ Discovery-first single order lifecycle test. Creates an order with
|
|
|
235
235
|
**How to run:**
|
|
236
236
|
|
|
237
237
|
```bash
|
|
238
|
-
npx tsx scripts/e2e-full-order.ts --profile
|
|
239
|
-
FLUENT_PROFILE=
|
|
238
|
+
npx tsx scripts/e2e-full-order.ts --profile YOUR_PROFILE --retailer 2
|
|
239
|
+
FLUENT_PROFILE=YOUR_PROFILE FLUENT_RETAILER_ID=2 npx tsx scripts/e2e-full-order.ts
|
|
240
240
|
```
|
|
241
241
|
|
|
242
242
|
**Environment:** requires `FLUENT_PROFILE` + `FLUENT_RETAILER_ID`, or `--profile`
|
|
@@ -269,7 +269,7 @@ MULTI order type workflow and includes workflow transition discovery.
|
|
|
269
269
|
npx tsx scripts/e2e-full-multi.ts
|
|
270
270
|
```
|
|
271
271
|
|
|
272
|
-
**Environment:** defaults to `FLUENT_BASE_URL=https
|
|
272
|
+
**Environment:** defaults to `FLUENT_BASE_URL=https://<account>.test.api.fluentretail.com`
|
|
273
273
|
and `FLUENT_RETAILER_ID=2`. Override with env vars. Requires OAuth or profile
|
|
274
274
|
credentials.
|
|
275
275
|
|
|
@@ -296,7 +296,7 @@ Comprehensive smoke test for all 11 agentic tools against a live environment.
|
|
|
296
296
|
**How to run:**
|
|
297
297
|
|
|
298
298
|
```bash
|
|
299
|
-
npx tsx scripts/e2e-agentic.ts --profile
|
|
299
|
+
npx tsx scripts/e2e-agentic.ts --profile YOUR_PROFILE
|
|
300
300
|
```
|
|
301
301
|
|
|
302
302
|
**Environment:** requires `FLUENT_PROFILE` or `--profile` flag. Defaults to
|
|
@@ -322,7 +322,7 @@ responses.
|
|
|
322
322
|
**How to run:**
|
|
323
323
|
|
|
324
324
|
```bash
|
|
325
|
-
npx tsx scripts/e2e-response-shaping.ts --profile
|
|
325
|
+
npx tsx scripts/e2e-response-shaping.ts --profile YOUR_PROFILE
|
|
326
326
|
```
|
|
327
327
|
|
|
328
328
|
**Environment:** requires `FLUENT_PROFILE` or `--profile`. Uses default budget
|
|
@@ -347,7 +347,7 @@ per-event drilldowns via `event.get`.
|
|
|
347
347
|
**How to run:**
|
|
348
348
|
|
|
349
349
|
```bash
|
|
350
|
-
npx tsx scripts/run-flow-inspect.ts <ROOT_ENTITY_REF> --profile
|
|
350
|
+
npx tsx scripts/run-flow-inspect.ts <ROOT_ENTITY_REF> --profile YOUR_PROFILE
|
|
351
351
|
npx tsx scripts/run-flow-inspect.ts HD-001 --root-type ORDER --profile HMDEV
|
|
352
352
|
npx tsx scripts/run-flow-inspect.ts HD-001 --root-id 12345 --light --no-drilldown
|
|
353
353
|
npx tsx scripts/run-flow-inspect.ts HD-001 --drilldown-classes webhook,mutation --drilldown-limit 20
|
|
@@ -357,7 +357,7 @@ npx tsx scripts/run-flow-inspect.ts HD-001 --drilldown-classes webhook,mutation
|
|
|
357
357
|
|
|
358
358
|
| Flag | Default | Description |
|
|
359
359
|
|------|---------|-------------|
|
|
360
|
-
| `--profile <name>` |
|
|
360
|
+
| `--profile <name>` | - | Fluent CLI profile |
|
|
361
361
|
| `--root-type <type>` | any | Root entity type (ORDER, FULFILMENT, etc.) |
|
|
362
362
|
| `--root-id <id>` | - | Root entity ID for disambiguation |
|
|
363
363
|
| `--out <path>` | `accounts/<profile>/analysis/EVENT_FLOW_INSPECT_<ref>.json` | Raw output path |
|
|
@@ -394,7 +394,7 @@ mutations, sendEvent actions, scheduled actions, and ruleset durations.
|
|
|
394
394
|
**How to run:**
|
|
395
395
|
|
|
396
396
|
```bash
|
|
397
|
-
npx tsx scripts/order-event-inspect.ts <ORDER_REF> --profile
|
|
397
|
+
npx tsx scripts/order-event-inspect.ts <ORDER_REF> --profile YOUR_PROFILE
|
|
398
398
|
npx tsx scripts/order-event-inspect.ts HD-001 --profile HMDEV --out /tmp/inspect.json
|
|
399
399
|
```
|
|
400
400
|
|
|
@@ -411,16 +411,16 @@ and entity ref.
|
|
|
411
411
|
|
|
412
412
|
```bash
|
|
413
413
|
# Fetch specific events by ID
|
|
414
|
-
npx tsx scripts/check-event-status.ts --event-id <uuid> --profile
|
|
414
|
+
npx tsx scripts/check-event-status.ts --event-id <uuid> --profile YOUR_PROFILE
|
|
415
415
|
|
|
416
416
|
# List events by name
|
|
417
|
-
npx tsx scripts/check-event-status.ts --event-name ConfirmValidation --profile
|
|
417
|
+
npx tsx scripts/check-event-status.ts --event-name ConfirmValidation --profile YOUR_PROFILE
|
|
418
418
|
|
|
419
419
|
# List events by entity ref
|
|
420
|
-
npx tsx scripts/check-event-status.ts --entity-ref HD-001 --profile
|
|
420
|
+
npx tsx scripts/check-event-status.ts --entity-ref HD-001 --profile YOUR_PROFILE
|
|
421
421
|
|
|
422
422
|
# Combine filters
|
|
423
|
-
npx tsx scripts/check-event-status.ts --event-name ConfirmValidation --entity-ref HD-001 --count 20 --profile
|
|
423
|
+
npx tsx scripts/check-event-status.ts --event-name ConfirmValidation --entity-ref HD-001 --count 20 --profile YOUR_PROFILE
|
|
424
424
|
```
|
|
425
425
|
|
|
426
426
|
**Options:**
|
package/docs/RUNBOOK.md
CHANGED
|
@@ -92,8 +92,11 @@ Run tools in this order:
|
|
|
92
92
|
7. `event.get` using ID returned by send
|
|
93
93
|
8. `graphql.query` simple read
|
|
94
94
|
9. `entity.get` — verify entity lookup works (e.g., look up a known order by ref)
|
|
95
|
-
10. `
|
|
96
|
-
11. `
|
|
95
|
+
10. `setting.get` with `name: "fc.mystique.manifest.%"` — verify settings read access and wildcard support
|
|
96
|
+
11. `workflow.list` — verify REST workflow API access (list all workflows for a retailer)
|
|
97
|
+
12. `workflow.get` with `entityType: "ORDER", entitySubtype: "HD"` — verify specific workflow fetch
|
|
98
|
+
13. `workflow.transitions` (optional) — verify User Action API access for a known trigger
|
|
99
|
+
14. `graphql.queryAll` — verify auto-pagination and run-level timeout
|
|
97
100
|
12. `graphql.introspect` with `listMutations: true` — verify schema access
|
|
98
101
|
13. `environment.discover` with `include: ["retailer", "locations"]` — verify environment snapshot
|
|
99
102
|
14. `environment.validate` with `checks: ["auth", "retailer", "locations"]` — verify pre-flight checks
|