@withone/cli 1.47.8 → 1.47.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -29,8 +29,9 @@ import {
|
|
|
29
29
|
saveFlow,
|
|
30
30
|
searchCachePath,
|
|
31
31
|
validateActionInput,
|
|
32
|
+
walkSteps,
|
|
32
33
|
writeCache
|
|
33
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-VW7J2RQW.js";
|
|
34
35
|
import {
|
|
35
36
|
memSqlCommand
|
|
36
37
|
} from "./chunk-QV3Y5N5G.js";
|
|
@@ -2739,7 +2740,13 @@ async function actionsExecuteCommand(platform, actionId, connectionKey, options)
|
|
|
2739
2740
|
} else {
|
|
2740
2741
|
console.log();
|
|
2741
2742
|
console.log(pc6.bold("Response:"));
|
|
2742
|
-
|
|
2743
|
+
const rd = result.responseData;
|
|
2744
|
+
if (rd && typeof rd === "object" && typeof rd.text === "string" && "contentType" in rd) {
|
|
2745
|
+
if (rd.contentType) console.log(pc6.dim(`(${rd.contentType})`));
|
|
2746
|
+
console.log(rd.text);
|
|
2747
|
+
} else {
|
|
2748
|
+
console.log(JSON.stringify(result.responseData, null, 2));
|
|
2749
|
+
}
|
|
2743
2750
|
}
|
|
2744
2751
|
} catch (error2) {
|
|
2745
2752
|
spinner5.stop("Execution failed");
|
|
@@ -3597,7 +3604,7 @@ function validateOutputSchemas(flow2) {
|
|
|
3597
3604
|
walkValue(v, `${pathPrefix}.${k}`);
|
|
3598
3605
|
}
|
|
3599
3606
|
}
|
|
3600
|
-
function
|
|
3607
|
+
function walkSteps2(steps, pathPrefix) {
|
|
3601
3608
|
steps.forEach((step, i) => {
|
|
3602
3609
|
const stepPath = `${pathPrefix}[${i}]`;
|
|
3603
3610
|
if (step.if) checkText(step.if, `${stepPath}.if`);
|
|
@@ -3612,13 +3619,13 @@ function validateOutputSchemas(flow2) {
|
|
|
3612
3619
|
for (const { configKey, fieldName } of nestedKeys) {
|
|
3613
3620
|
const c = step[configKey];
|
|
3614
3621
|
if (c && Array.isArray(c[fieldName])) {
|
|
3615
|
-
|
|
3622
|
+
walkSteps2(c[fieldName], `${stepPath}.${configKey}.${fieldName}`);
|
|
3616
3623
|
}
|
|
3617
3624
|
}
|
|
3618
3625
|
}
|
|
3619
3626
|
});
|
|
3620
3627
|
}
|
|
3621
|
-
|
|
3628
|
+
walkSteps2(flow2.steps, "steps");
|
|
3622
3629
|
return errors;
|
|
3623
3630
|
}
|
|
3624
3631
|
function validateOutputSchemaShape(schema, location) {
|
|
@@ -3652,6 +3659,7 @@ function validateFlow(flow2, rootDir) {
|
|
|
3652
3659
|
...validateStepIds(f),
|
|
3653
3660
|
...validateSelectorReferences(f, rootDir),
|
|
3654
3661
|
...validateOutputSchemas(f),
|
|
3662
|
+
...validateFileReadSchemas(f),
|
|
3655
3663
|
...rootDir ? validateCodeModules(f, rootDir) : []
|
|
3656
3664
|
];
|
|
3657
3665
|
}
|
|
@@ -3692,6 +3700,108 @@ function validateCodeModules(flow2, rootDir) {
|
|
|
3692
3700
|
walk(flow2.steps, "steps");
|
|
3693
3701
|
return errors;
|
|
3694
3702
|
}
|
|
3703
|
+
var VALID_FILE_READ_SCHEMA_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "object", "array", "null", "unknown"]);
|
|
3704
|
+
function isPlainObject(v) {
|
|
3705
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
3706
|
+
}
|
|
3707
|
+
function validateFileReadFieldRule(raw, fieldPath, errors) {
|
|
3708
|
+
if (typeof raw === "string") {
|
|
3709
|
+
if (!VALID_FILE_READ_SCHEMA_TYPES.has(raw)) {
|
|
3710
|
+
errors.push({ path: fieldPath, message: `unknown type "${raw}". Allowed: ${[...VALID_FILE_READ_SCHEMA_TYPES].join(", ")}.` });
|
|
3711
|
+
}
|
|
3712
|
+
return;
|
|
3713
|
+
}
|
|
3714
|
+
if (!isPlainObject(raw)) {
|
|
3715
|
+
errors.push({ path: fieldPath, message: 'field rule must be a type string or an object (e.g. { "type": "string", "required": true })' });
|
|
3716
|
+
return;
|
|
3717
|
+
}
|
|
3718
|
+
const rule = raw;
|
|
3719
|
+
if (rule.type !== void 0 && (typeof rule.type !== "string" || !VALID_FILE_READ_SCHEMA_TYPES.has(rule.type))) {
|
|
3720
|
+
errors.push({ path: `${fieldPath}.type`, message: `unknown type "${String(rule.type)}". Allowed: ${[...VALID_FILE_READ_SCHEMA_TYPES].join(", ")}.` });
|
|
3721
|
+
}
|
|
3722
|
+
if (rule.required !== void 0 && typeof rule.required !== "boolean") {
|
|
3723
|
+
errors.push({ path: `${fieldPath}.required`, message: '"required" must be a boolean' });
|
|
3724
|
+
}
|
|
3725
|
+
if (rule.enum !== void 0) {
|
|
3726
|
+
if (!Array.isArray(rule.enum) || rule.enum.length === 0) {
|
|
3727
|
+
errors.push({ path: `${fieldPath}.enum`, message: '"enum" must be a non-empty array of allowed values' });
|
|
3728
|
+
} else if (rule.enum.some((e) => e !== null && typeof e === "object")) {
|
|
3729
|
+
errors.push({ path: `${fieldPath}.enum`, message: '"enum" entries must be primitives (string/number/boolean) or null' });
|
|
3730
|
+
}
|
|
3731
|
+
}
|
|
3732
|
+
for (const k of ["minItems", "maxItems", "length"]) {
|
|
3733
|
+
const n = rule[k];
|
|
3734
|
+
if (n !== void 0 && (typeof n !== "number" || !Number.isInteger(n) || n < 0)) {
|
|
3735
|
+
errors.push({ path: `${fieldPath}.${k}`, message: `"${k}" must be a non-negative integer` });
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
if (typeof rule.minItems === "number" && typeof rule.maxItems === "number" && rule.minItems > rule.maxItems) {
|
|
3739
|
+
errors.push({ path: `${fieldPath}.minItems`, message: '"minItems" must be <= "maxItems"' });
|
|
3740
|
+
}
|
|
3741
|
+
if (rule.length !== void 0 && (rule.minItems !== void 0 || rule.maxItems !== void 0)) {
|
|
3742
|
+
errors.push({ path: `${fieldPath}.length`, message: '"length" cannot be combined with "minItems"/"maxItems"' });
|
|
3743
|
+
}
|
|
3744
|
+
const hasArrayConstraint = rule.items !== void 0 || rule.minItems !== void 0 || rule.maxItems !== void 0 || rule.length !== void 0;
|
|
3745
|
+
if (hasArrayConstraint && rule.type !== "array") {
|
|
3746
|
+
errors.push({ path: `${fieldPath}.type`, message: '"items"/"minItems"/"maxItems"/"length" require "type": "array"' });
|
|
3747
|
+
}
|
|
3748
|
+
if (rule.items !== void 0) {
|
|
3749
|
+
if (typeof rule.items === "string") {
|
|
3750
|
+
if (!VALID_FILE_READ_SCHEMA_TYPES.has(rule.items)) {
|
|
3751
|
+
errors.push({ path: `${fieldPath}.items`, message: `unknown item type "${rule.items}". Allowed: ${[...VALID_FILE_READ_SCHEMA_TYPES].join(", ")}.` });
|
|
3752
|
+
}
|
|
3753
|
+
} else if (isPlainObject(rule.items)) {
|
|
3754
|
+
validateFileReadFieldRule(rule.items, `${fieldPath}.items`, errors);
|
|
3755
|
+
} else {
|
|
3756
|
+
errors.push({ path: `${fieldPath}.items`, message: '"items" must be a type string or a field rule object' });
|
|
3757
|
+
}
|
|
3758
|
+
}
|
|
3759
|
+
if (rule.properties !== void 0) {
|
|
3760
|
+
if (rule.type !== "object") {
|
|
3761
|
+
errors.push({ path: `${fieldPath}.properties`, message: '"properties" requires "type": "object"' });
|
|
3762
|
+
}
|
|
3763
|
+
errors.push(...validateFileReadSchemaShape(rule.properties, `${fieldPath}.properties`));
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
function validateFileReadSchemaShape(schema, location) {
|
|
3767
|
+
const errors = [];
|
|
3768
|
+
if (!isPlainObject(schema)) {
|
|
3769
|
+
errors.push({ path: location, message: '"schema" must be an object mapping field names to type/rules' });
|
|
3770
|
+
return errors;
|
|
3771
|
+
}
|
|
3772
|
+
for (const [field, raw] of Object.entries(schema)) {
|
|
3773
|
+
validateFileReadFieldRule(raw, `${location}.${field}`, errors);
|
|
3774
|
+
}
|
|
3775
|
+
return errors;
|
|
3776
|
+
}
|
|
3777
|
+
function validateFileReadSchemas(flow2) {
|
|
3778
|
+
const errors = [];
|
|
3779
|
+
const nestedKeys = getNestedStepsKeys();
|
|
3780
|
+
function walk(steps, pathPrefix) {
|
|
3781
|
+
for (let i = 0; i < steps.length; i++) {
|
|
3782
|
+
const step = steps[i];
|
|
3783
|
+
const stepPath = `${pathPrefix}[${i}]`;
|
|
3784
|
+
if (step.type === "file-read" && step.fileRead && step.fileRead.schema !== void 0) {
|
|
3785
|
+
const fr = step.fileRead;
|
|
3786
|
+
if (fr.parseJson !== true) {
|
|
3787
|
+
errors.push({
|
|
3788
|
+
path: `${stepPath}.fileRead.schema`,
|
|
3789
|
+
message: "fileRead.schema is only checked when parseJson:true \u2014 set parseJson:true or remove schema."
|
|
3790
|
+
});
|
|
3791
|
+
}
|
|
3792
|
+
errors.push(...validateFileReadSchemaShape(fr.schema, `${stepPath}.fileRead.schema`));
|
|
3793
|
+
}
|
|
3794
|
+
for (const { configKey, fieldName } of nestedKeys) {
|
|
3795
|
+
const config2 = step[configKey];
|
|
3796
|
+
if (config2 && Array.isArray(config2[fieldName])) {
|
|
3797
|
+
walk(config2[fieldName], `${stepPath}.${configKey}.${fieldName}`);
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
walk(flow2.steps, "steps");
|
|
3803
|
+
return errors;
|
|
3804
|
+
}
|
|
3695
3805
|
|
|
3696
3806
|
// src/commands/flow.ts
|
|
3697
3807
|
import fs5 from "fs";
|
|
@@ -3716,6 +3826,49 @@ async function writeFlowResultFile(filePath, meta, steps) {
|
|
|
3716
3826
|
await done;
|
|
3717
3827
|
return abs;
|
|
3718
3828
|
}
|
|
3829
|
+
function previewValue(value, max = 120) {
|
|
3830
|
+
if (value === void 0) return "<undefined>";
|
|
3831
|
+
let str;
|
|
3832
|
+
try {
|
|
3833
|
+
str = typeof value === "string" ? JSON.stringify(value) : JSON.stringify(value) ?? String(value);
|
|
3834
|
+
} catch {
|
|
3835
|
+
str = String(value);
|
|
3836
|
+
}
|
|
3837
|
+
if (str === void 0) str = String(value);
|
|
3838
|
+
return str.length > max ? `${str.slice(0, max - 1)}\u2026` : str;
|
|
3839
|
+
}
|
|
3840
|
+
function renderDryRef(ref) {
|
|
3841
|
+
if (ref.status === "resolved") {
|
|
3842
|
+
return `${pc7.green("\u2713")} ${ref.selector} ${pc7.dim("\u2192")} ${previewValue(ref.value)}`;
|
|
3843
|
+
}
|
|
3844
|
+
if (ref.status === "deferred") {
|
|
3845
|
+
return `${pc7.dim("\u25CB")} ${ref.selector} ${pc7.dim("\u2192 pending (produced by a later step)")}`;
|
|
3846
|
+
}
|
|
3847
|
+
return `${pc7.yellow("!")} ${ref.selector} ${pc7.yellow("\u2192 unresolved \u2014 check the input/env name")}`;
|
|
3848
|
+
}
|
|
3849
|
+
function renderDryResolution(steps) {
|
|
3850
|
+
const isExpr = (t) => t === "transform" || t === "condition" || t === "while";
|
|
3851
|
+
for (const s of steps) {
|
|
3852
|
+
const label = s.name ? `${s.stepId} ${pc7.dim(`"${s.name}"`)}` : s.stepId;
|
|
3853
|
+
console.log(` ${pc7.cyan("\u25B8")} ${label} ${pc7.dim(`(${s.type})`)}`);
|
|
3854
|
+
if (s.error !== void 0) {
|
|
3855
|
+
console.log(` ${pc7.red("error")} ${s.error}`);
|
|
3856
|
+
} else if (isExpr(s.type) && s.deferred) {
|
|
3857
|
+
const deps = s.references.map((r) => r.selector).join(", ");
|
|
3858
|
+
console.log(` ${pc7.dim("\u25CB pending \u2014 depends on")} ${deps} ${pc7.dim("(produced by a later step)")}`);
|
|
3859
|
+
} else if (isExpr(s.type)) {
|
|
3860
|
+
console.log(` ${pc7.dim("=")} ${previewValue(s.resolved)}`);
|
|
3861
|
+
}
|
|
3862
|
+
if (!(isExpr(s.type) && s.deferred)) {
|
|
3863
|
+
for (const ref of s.references) {
|
|
3864
|
+
console.log(` ${renderDryRef(ref)}`);
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
if (!isExpr(s.type) && s.references.length === 0 && s.error === void 0) {
|
|
3868
|
+
console.log(` ${pc7.dim("(no interpolations)")}`);
|
|
3869
|
+
}
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3719
3872
|
function getConfig2() {
|
|
3720
3873
|
const apiKey = getApiKey();
|
|
3721
3874
|
if (!apiKey) {
|
|
@@ -3870,6 +4023,20 @@ ${preflightErrors.map((e) => ` ${e.path}: ${e.message}`).join("\n")}`);
|
|
|
3870
4023
|
}
|
|
3871
4024
|
error(msg);
|
|
3872
4025
|
}
|
|
4026
|
+
if (options.stopAfter) {
|
|
4027
|
+
const ids = /* @__PURE__ */ new Set();
|
|
4028
|
+
walkSteps(flow2.steps, (s) => {
|
|
4029
|
+
ids.add(s.id);
|
|
4030
|
+
});
|
|
4031
|
+
if (!ids.has(options.stopAfter)) {
|
|
4032
|
+
const msg = `--stop-after target "${options.stopAfter}" is not a step in workflow "${flow2.key}". Known step ids: ${[...ids].join(", ")}`;
|
|
4033
|
+
if (isAgentMode()) {
|
|
4034
|
+
json({ error: msg, unknownStopAfter: options.stopAfter, stepIds: [...ids] });
|
|
4035
|
+
process.exit(1);
|
|
4036
|
+
}
|
|
4037
|
+
error(msg);
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
3873
4040
|
const inputs = parseInputs(options.input || []);
|
|
3874
4041
|
const resolvedInputs = await autoResolveConnectionInputs(flow2, inputs, api);
|
|
3875
4042
|
const runner = new FlowRunner(flow2, resolvedInputs);
|
|
@@ -3883,7 +4050,17 @@ ${pc7.yellow("Pausing after current step completes...")} (run ID: ${runId})`);
|
|
|
3883
4050
|
}
|
|
3884
4051
|
};
|
|
3885
4052
|
process.on("SIGINT", sigintHandler);
|
|
4053
|
+
let dryRunSteps;
|
|
4054
|
+
let dryResolveTarget;
|
|
4055
|
+
let stoppedAfter;
|
|
3886
4056
|
const onEvent = (event) => {
|
|
4057
|
+
if (event.event === "flow:dry-run") {
|
|
4058
|
+
dryRunSteps = event.steps;
|
|
4059
|
+
} else if (event.event === "step:dry-resolve") {
|
|
4060
|
+
dryResolveTarget = event;
|
|
4061
|
+
} else if (event.event === "flow:stopped") {
|
|
4062
|
+
stoppedAfter = event.stoppedAfter;
|
|
4063
|
+
}
|
|
3887
4064
|
if (isAgentMode()) {
|
|
3888
4065
|
json(event);
|
|
3889
4066
|
} else if (options.verbose) {
|
|
@@ -3909,23 +4086,46 @@ ${pc7.yellow("Pausing after current step completes...")} (run ID: ${runId})`);
|
|
|
3909
4086
|
verbose: options.verbose,
|
|
3910
4087
|
allowBash: options.allowBash,
|
|
3911
4088
|
skipValidation: options.skipValidation,
|
|
4089
|
+
stopAfter: options.stopAfter,
|
|
3912
4090
|
rootDir,
|
|
3913
4091
|
onEvent
|
|
3914
4092
|
});
|
|
3915
4093
|
process.off("SIGINT", sigintHandler);
|
|
3916
4094
|
if (!options.verbose && !isAgentMode()) {
|
|
3917
|
-
execSpinner.stop("Workflow completed");
|
|
4095
|
+
execSpinner.stop(stoppedAfter ? "Workflow stopped" : dryRunSteps ? "Dry run complete" : "Workflow completed");
|
|
3918
4096
|
}
|
|
3919
4097
|
const resultFile = options.outputFile ? await writeFlowResultFile(options.outputFile, { runId, logFile: logPath, status: "success" }, context.steps) : void 0;
|
|
4098
|
+
const finalStatus = stoppedAfter ? "stopped" : "success";
|
|
3920
4099
|
if (isAgentMode()) {
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
4100
|
+
const envelope = {
|
|
4101
|
+
event: "workflow:result",
|
|
4102
|
+
runId,
|
|
4103
|
+
logFile: logPath,
|
|
4104
|
+
statePath: runner.getStatePath(),
|
|
4105
|
+
status: finalStatus
|
|
4106
|
+
};
|
|
4107
|
+
if (resultFile) envelope.outputFile = resultFile;
|
|
4108
|
+
else envelope.steps = context.steps;
|
|
4109
|
+
if (options.dryRun) envelope.dryRun = true;
|
|
4110
|
+
if (stoppedAfter) envelope.stoppedAfter = stoppedAfter;
|
|
4111
|
+
json(envelope);
|
|
3924
4112
|
return;
|
|
3925
4113
|
}
|
|
3926
4114
|
if (resultFile) {
|
|
3927
4115
|
note(`Full result written to ${resultFile}`, "Output");
|
|
3928
4116
|
}
|
|
4117
|
+
if (dryRunSteps && !stoppedAfter) {
|
|
4118
|
+
console.log();
|
|
4119
|
+
renderDryResolution(dryRunSteps);
|
|
4120
|
+
console.log();
|
|
4121
|
+
const missing = dryRunSteps.reduce((n, s) => n + s.references.filter((r) => r.status === "missing").length, 0);
|
|
4122
|
+
note(
|
|
4123
|
+
`Dry run \u2014 no steps executed. Resolved ${dryRunSteps.length} step(s)` + (missing > 0 ? `; ${pc7.yellow(`${missing} unresolved input/env reference(s)`)}` : "") + `.
|
|
4124
|
+
${pc7.dim("$.steps.* refs resolve at runtime \u2014 re-run with --stop-after=<stepId> to resolve them against real output.")}`,
|
|
4125
|
+
"Dry Run"
|
|
4126
|
+
);
|
|
4127
|
+
return;
|
|
4128
|
+
}
|
|
3929
4129
|
const stepEntries = Object.entries(context.steps);
|
|
3930
4130
|
const succeeded = stepEntries.filter(([, r]) => r.status === "success").length;
|
|
3931
4131
|
const failed = stepEntries.filter(([, r]) => r.status === "failed").length;
|
|
@@ -3934,7 +4134,17 @@ ${pc7.yellow("Pausing after current step completes...")} (run ID: ${runId})`);
|
|
|
3934
4134
|
console.log(` ${pc7.green("\u2713")} ${succeeded} succeeded ${failed > 0 ? pc7.red(`\u2717 ${failed} failed`) : ""} ${skipped > 0 ? pc7.dim(`\u25CB ${skipped} skipped`) : ""}`);
|
|
3935
4135
|
console.log(` ${pc7.dim(`Run ID: ${runId}`)}`);
|
|
3936
4136
|
console.log(` ${pc7.dim(`Log: ${logPath}`)}`);
|
|
3937
|
-
if (
|
|
4137
|
+
if (dryResolveTarget) {
|
|
4138
|
+
console.log();
|
|
4139
|
+
console.log(` ${pc7.dim(`Resolved (not executed) "${dryResolveTarget.stepId}":`)}`);
|
|
4140
|
+
renderDryResolution([dryResolveTarget]);
|
|
4141
|
+
}
|
|
4142
|
+
if (stoppedAfter) {
|
|
4143
|
+
note(
|
|
4144
|
+
`Stopped after step "${stoppedAfter}". Inspect step outputs with: ${pc7.cyan(`one flow inspect ${runId}`)}`,
|
|
4145
|
+
"Stopped"
|
|
4146
|
+
);
|
|
4147
|
+
} else if (options.dryRun) {
|
|
3938
4148
|
note("Dry run \u2014 no steps were executed", "Dry Run");
|
|
3939
4149
|
}
|
|
3940
4150
|
} catch (error2) {
|
|
@@ -4126,6 +4336,64 @@ async function flowRunsCommand(flowKey) {
|
|
|
4126
4336
|
);
|
|
4127
4337
|
console.log();
|
|
4128
4338
|
}
|
|
4339
|
+
async function flowInspectCommand(runId, options = {}) {
|
|
4340
|
+
intro(pc7.bgCyan(pc7.black(" One Workflow ")));
|
|
4341
|
+
const state = FlowRunner.loadRunState(runId);
|
|
4342
|
+
if (!state) {
|
|
4343
|
+
const msg = `No run found for id "${runId}". List runs with: one flow runs`;
|
|
4344
|
+
if (isAgentMode()) {
|
|
4345
|
+
json({ error: msg, runId });
|
|
4346
|
+
process.exit(1);
|
|
4347
|
+
}
|
|
4348
|
+
error(msg);
|
|
4349
|
+
return;
|
|
4350
|
+
}
|
|
4351
|
+
const statePath = FlowRunner.statePathFor(state.flowKey, state.runId);
|
|
4352
|
+
const stepEntries = Object.entries(state.context.steps || {});
|
|
4353
|
+
if (isAgentMode()) {
|
|
4354
|
+
json({
|
|
4355
|
+
runId: state.runId,
|
|
4356
|
+
flowKey: state.flowKey,
|
|
4357
|
+
status: state.status,
|
|
4358
|
+
startedAt: state.startedAt,
|
|
4359
|
+
completedAt: state.completedAt,
|
|
4360
|
+
pausedAt: state.pausedAt,
|
|
4361
|
+
currentStepId: state.currentStepId,
|
|
4362
|
+
inputs: state.inputs,
|
|
4363
|
+
steps: state.context.steps,
|
|
4364
|
+
statePath
|
|
4365
|
+
});
|
|
4366
|
+
return;
|
|
4367
|
+
}
|
|
4368
|
+
console.log();
|
|
4369
|
+
console.log(` ${pc7.bold(state.flowKey)} ${pc7.dim(`run ${state.runId}`)} ${colorStatus(state.status)}`);
|
|
4370
|
+
console.log(` ${pc7.dim(`Started: ${state.startedAt}${state.completedAt ? ` \xB7 Ended: ${state.completedAt}` : ""}`)}`);
|
|
4371
|
+
if (state.currentStepId) console.log(` ${pc7.dim(`Current step: ${state.currentStepId}`)}`);
|
|
4372
|
+
console.log();
|
|
4373
|
+
if (stepEntries.length === 0) {
|
|
4374
|
+
note("No step outputs recorded yet for this run.", "Steps");
|
|
4375
|
+
} else {
|
|
4376
|
+
for (const [id, result] of stepEntries) {
|
|
4377
|
+
const icon = result.status === "success" ? pc7.green("\u2713") : result.status === "skipped" ? pc7.dim("\u25CB") : result.status === "timeout" ? pc7.yellow("\u29D6") : pc7.red("\u2717");
|
|
4378
|
+
const dur = result.durationMs !== void 0 ? pc7.dim(` ${result.durationMs}ms`) : "";
|
|
4379
|
+
const retries = result.retries ? pc7.dim(` (${result.retries} retr${result.retries === 1 ? "y" : "ies"})`) : "";
|
|
4380
|
+
console.log(` ${icon} ${id} ${pc7.dim(`[${result.status}]`)}${dur}${retries}`);
|
|
4381
|
+
if (result.error) {
|
|
4382
|
+
console.log(` ${pc7.red("error")} ${result.error}${result.errorCode ? pc7.dim(` (${result.errorCode})`) : ""}`);
|
|
4383
|
+
}
|
|
4384
|
+
if (result.output !== void 0) {
|
|
4385
|
+
const json2 = JSON.stringify(result.output, null, options.full ? 2 : 0) ?? String(result.output);
|
|
4386
|
+
const shown = options.full || json2.length <= 240 ? json2 : `${json2.slice(0, 239)}\u2026 ${pc7.dim("(--full for all)")}`;
|
|
4387
|
+
const indented = options.full ? shown.split("\n").map((l) => ` ${l}`).join("\n") : ` ${pc7.dim("output")} ${shown}`;
|
|
4388
|
+
console.log(indented);
|
|
4389
|
+
}
|
|
4390
|
+
}
|
|
4391
|
+
}
|
|
4392
|
+
console.log();
|
|
4393
|
+
console.log(` ${pc7.dim(`State: ${statePath}`)}`);
|
|
4394
|
+
console.log(` ${pc7.dim(`Log: ${path5.join(".one/flows/.logs", `${state.flowKey}-${state.runId}.log`)}`)}`);
|
|
4395
|
+
console.log();
|
|
4396
|
+
}
|
|
4129
4397
|
function colorStatus(status) {
|
|
4130
4398
|
switch (status) {
|
|
4131
4399
|
case "completed":
|
|
@@ -9437,7 +9705,7 @@ one --agent actions execute <platform> <actionId> <key> -d '{}' # Execute it
|
|
|
9437
9705
|
- \`--dry-run\` \u2014 Preview request without executing
|
|
9438
9706
|
- \`--mock\` \u2014 Return example response without making an API call (useful for building UI against a response shape)
|
|
9439
9707
|
- \`--skip-validation\` \u2014 Skip input validation against the action schema
|
|
9440
|
-
- \`--output <path>\` \u2014 Save response to a file (for binary downloads like PDFs, images, documents)
|
|
9708
|
+
- \`--output <path>\` \u2014 Save response to a file (for binary downloads like PDFs, images, documents). Text responses (text/plain, HTML, CSV, XML) render inline automatically \u2014 \`--output\` is only needed for genuinely binary payloads.
|
|
9441
9709
|
- \`--no-cache\` \u2014 Bypass the cached action details and re-fetch them; the fresh details still refresh the cache (execution itself is never cached)
|
|
9442
9710
|
|
|
9443
9711
|
The CLI validates required parameters against the action schema before executing. If you're missing a required path variable, query param, or body field, you'll get a clear error listing what's missing and which flag to use. Pass \`--skip-validation\` to bypass.
|
|
@@ -9452,6 +9720,9 @@ Chain actions across platforms as JSON workflow files with conditions, loops, pa
|
|
|
9452
9720
|
one --agent flow create <key> --definition '<json>' # Create a workflow
|
|
9453
9721
|
one --agent flow validate <key> # Validate it
|
|
9454
9722
|
one --agent flow execute <key> -i param=value # Execute it
|
|
9723
|
+
one --agent flow execute <key> --dry-run -i ... # Resolve interpolations, run nothing
|
|
9724
|
+
one --agent flow execute <key> --stop-after <stepId> # Run up to a step, then stop
|
|
9725
|
+
one --agent flow inspect <runId> # Per-step outputs of a past run
|
|
9455
9726
|
one --agent flow list # List all workflows
|
|
9456
9727
|
\`\`\`
|
|
9457
9728
|
|
|
@@ -9465,6 +9736,7 @@ one --agent flow list # List all workflows
|
|
|
9465
9736
|
- Use \`--skip-validation\` to bypass input validation on action steps
|
|
9466
9737
|
- Use \`--output-file <path>\` to stream the full result to a file instead of stdout \u2014 for large results that would otherwise be truncated or hit the JSON string-size limit; stdout (and \`--agent\` output) then carries an \`outputFile\` pointer instead of inline \`steps\`
|
|
9467
9738
|
- Step-level \`if\`/\`unless\` (and \`while\`/\`condition\` steps) are null-safe: a condition referencing a skipped or not-yet-run step's output (e.g. \`$.steps.maybeSkipped.output.x\`) evaluates to \`false\` instead of crashing the flow
|
|
9739
|
+
- Debugging: \`--dry-run\` resolves each step's interpolations and shows what they evaluate to without executing (\`$.steps.*\` refs are reported as deferred); \`--stop-after <stepId>\` runs up to a step then stops; \`--dry-run --stop-after <stepId>\` runs earlier steps for real then dry-resolves the target against their output; \`flow inspect <runId>\` shows a past run's per-step outputs (post-mortem, no re-run; \`--full\` for untruncated)
|
|
9468
9740
|
|
|
9469
9741
|
### 3. Relay \u2014 Webhook event forwarding between platforms
|
|
9470
9742
|
Receive webhooks from platforms (Stripe, GitHub, Airtable, Attio, Google Calendar) and forward event data to any connected platform using passthrough actions with Handlebars templates. No middleware, no code.
|
|
@@ -9565,7 +9837,7 @@ one --agent actions execute <platform> <actionId> <connectionKey> [options]
|
|
|
9565
9837
|
- \`--dry-run\` \u2014 Preview without executing
|
|
9566
9838
|
- \`--mock\` \u2014 Return example response without making an API call
|
|
9567
9839
|
- \`--skip-validation\` \u2014 Skip input validation against the action schema
|
|
9568
|
-
- \`--output <path>\` \u2014 Save response to a file (for binary downloads like PDFs, images, documents)
|
|
9840
|
+
- \`--output <path>\` \u2014 Save response to a file (for binary downloads like PDFs, images, documents). Text responses (text/plain, HTML, CSV, XML) render inline automatically \u2014 \`--output\` is only needed for genuinely binary payloads.
|
|
9569
9841
|
- \`--no-cache\` \u2014 Bypass the cached action details and re-fetch them; the fresh details still refresh the cache (execution itself is never cached)
|
|
9570
9842
|
|
|
9571
9843
|
Execute reuses the action details cached by \`actions knowledge\` (method, path, schema), so in the standard search \u2192 knowledge \u2192 execute flow it makes a single API call \u2014 the action being executed. The live response is never cached. In \`--agent\` mode the response includes \`"_preflight": {"cache": "hit"|"miss"}\` showing whether the lookup was served from disk.
|
|
@@ -11361,7 +11633,7 @@ var flow = program.command("flow").alias("f").description("Create, execute, and
|
|
|
11361
11633
|
flow.command("create [key]").description("Create a new workflow from JSON definition").option("--definition <json>", "Workflow definition as JSON string").option("-o, --output <path>", "Custom output path (default .one/flows/<key>/flow.json)").action(async (key, options) => {
|
|
11362
11634
|
await flowCreateCommand(key, options);
|
|
11363
11635
|
});
|
|
11364
|
-
flow.command("execute <keyOrPath>").alias("x").description("Execute a workflow by key or file path").option("-i, --input <name=value>", "Input parameter (repeatable)", collect, []).option("--dry-run", "
|
|
11636
|
+
flow.command("execute <keyOrPath>").alias("x").description("Execute a workflow by key or file path").option("-i, --input <name=value>", "Input parameter (repeatable)", collect, []).option("--dry-run", "Resolve each step's interpolations and show what they evaluate to, without executing any step ($.steps.* refs resolve at runtime)").option("--mock", "With --dry-run: execute transforms/code with realistic mock API responses").option("--stop-after <stepId>", "Execute steps up to and including <stepId>, then stop (later steps are not run). With --dry-run, runs earlier steps for real and dry-resolves <stepId> against their output without executing it. See: one flow inspect").option("--skip-validation", "Skip input validation against action schemas").option("--allow-bash", "Allow bash step execution (disabled by default for security)").option("-v, --verbose", "Show full request/response for each step").option("--output-file <path>", "Write the full result to a file (streamed) instead of stdout \u2014 avoids truncation/string-limit errors for large results; stdout/agent output then carries an outputFile pointer").action(async (keyOrPath, options) => {
|
|
11365
11637
|
await flowExecuteCommand(keyOrPath, options);
|
|
11366
11638
|
});
|
|
11367
11639
|
flow.command("list").alias("ls").description("List all workflows in .one/flows/").action(async () => {
|
|
@@ -11376,6 +11648,9 @@ flow.command("resume <runId>").description("Resume a paused or failed workflow r
|
|
|
11376
11648
|
flow.command("runs [flowKey]").description("List workflow runs (optionally filtered by flow key)").action(async (flowKey) => {
|
|
11377
11649
|
await flowRunsCommand(flowKey);
|
|
11378
11650
|
});
|
|
11651
|
+
flow.command("inspect <runId>").description("Inspect a past run's per-step outputs (post-mortem, no re-run) from its saved state file").option("--full", "Show full step outputs (default truncates large values)").action(async (runId, options) => {
|
|
11652
|
+
await flowInspectCommand(runId, options);
|
|
11653
|
+
});
|
|
11379
11654
|
flow.command("scaffold [template]").description("Generate a workflow scaffold (templates: basic, conditional, loop, ai)").action(async (template) => {
|
|
11380
11655
|
await flowScaffoldCommand(template);
|
|
11381
11656
|
});
|
package/package.json
CHANGED
package/skills/one/SKILL.md
CHANGED
|
@@ -89,7 +89,7 @@ Options:
|
|
|
89
89
|
- `--dry-run` — Preview the request without executing
|
|
90
90
|
- `--mock` — Return example response without making an API call (useful for building UI)
|
|
91
91
|
- `--skip-validation` — Skip input validation against the action schema
|
|
92
|
-
- `--output <path>` — Save response to a file (for binary downloads like PDFs, images, documents)
|
|
92
|
+
- `--output <path>` — Save response to a file (for binary downloads like PDFs, images, documents). Text responses (text/plain, HTML, CSV, XML) render inline automatically; `--output` is only needed for genuinely binary payloads.
|
|
93
93
|
- `--no-cache` — Bypass the cached action details and re-fetch them; the fresh details still refresh the cache (execution itself is never cached)
|
|
94
94
|
|
|
95
95
|
The CLI validates required parameters before executing. Missing params return a structured error with the flag name, parameter name, and description. Pass `--skip-validation` to bypass.
|
|
@@ -261,7 +261,7 @@ Without declared paths, the default walker concatenates every string in the reco
|
|
|
261
261
|
One also supports more advanced patterns. Read the relevant reference file before using these:
|
|
262
262
|
|
|
263
263
|
- **Webhook Relay** — Receive webhooks from a platform and forward to another (e.g., Stripe event -> Slack message). Read `references/relay.md` in this skill's directory for the full workflow.
|
|
264
|
-
- **Multi-step Workflows** — Chain actions across platforms as JSON workflow files (like n8n/Zapier but file-based). Read `references/flows.md` in this skill's directory for the schema and examples.
|
|
264
|
+
- **Multi-step Workflows** — Chain actions across platforms as JSON workflow files (like n8n/Zapier but file-based). Read `references/flows.md` in this skill's directory for the schema and examples. To debug: `flow execute <key> --dry-run` (resolve interpolations without running), `--stop-after <stepId>` (run up to a step then stop), and `flow inspect <runId>` (a past run's per-step outputs).
|
|
265
265
|
|
|
266
266
|
## Adding New Connections
|
|
267
267
|
|
|
@@ -8,7 +8,7 @@ Nothing about a flow's runtime requirements is guessable from its name. Before `
|
|
|
8
8
|
|
|
9
9
|
1. **Recommended:** `one --agent flow list` — the JSON output includes `requiresBash`, `usesCodeModules`, `inputs` (with `autoResolvable`), `stepTypes`, and the flow's `description`. Fastest path to knowing what you need.
|
|
10
10
|
2. Read the flow's `description` field from the JSON. Authors are required (see "Author conventions" below) to state `--allow-bash` requirements and non-auto-resolving inputs there.
|
|
11
|
-
3. `one --agent flow execute <key> --dry-run` to
|
|
11
|
+
3. `one --agent flow execute <key> --dry-run` to resolve every step's interpolations and see what they evaluate to — without side effects (see "Debugging a flow" below).
|
|
12
12
|
|
|
13
13
|
If you skip this, you will hit errors like *"Workflow X contains bash steps. Re-run with --allow-bash."* — the CLI pre-flights and fails fast, but the error is entirely avoidable by reading first.
|
|
14
14
|
|
|
@@ -400,6 +400,28 @@ After a parallel step, access each substep's output by its `id`: `$.steps.fetchE
|
|
|
400
400
|
{ "id": "write", "type": "file-write", "fileWrite": { "path": "./output/results.json", "content": "$.steps.transform.output" } }
|
|
401
401
|
```
|
|
402
402
|
|
|
403
|
+
**Validate a read config (`fileRead.schema`).** With `parseJson: true` you can add an optional `schema` that is **enforced at runtime** — a mismatch throws (`errorCode: "SCHEMA_VALIDATION"`) and is handled by the step's `onError`, so a bad config file fails fast with a clear message instead of producing garbage downstream. (This is distinct from `step.outputSchema`, which is documentation/wiring only and never checked at runtime.)
|
|
404
|
+
|
|
405
|
+
```json
|
|
406
|
+
{
|
|
407
|
+
"id": "read", "type": "file-read",
|
|
408
|
+
"fileRead": {
|
|
409
|
+
"path": "./data/config.json", "parseJson": true,
|
|
410
|
+
"schema": {
|
|
411
|
+
"name": { "type": "string", "required": true },
|
|
412
|
+
"mode": { "type": "string", "required": true, "enum": ["dev", "staging", "prod"] },
|
|
413
|
+
"retries": { "type": "number" },
|
|
414
|
+
"tags": { "type": "array", "items": "string", "minItems": 1, "maxItems": 10 },
|
|
415
|
+
"coords": { "type": "array", "length": 2, "items": "number" },
|
|
416
|
+
"owner": { "type": "object", "required": true,
|
|
417
|
+
"properties": { "id": "string", "email": { "type": "string", "required": true } } }
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Field rules (a richer field-rule format inspired by `outputSchema`): a value is a bare type string (`"string"`, optional) or `{ type, required, enum, items, minItems, maxItems, length, properties }`. Types: `string`/`number`/`boolean`/`object`/`array`/`null`/`unknown` (`unknown` passes anything). **Array constraints (`items`/`minItems`/`maxItems`/`length`) require `type: "array"`, and `properties` requires `type: "object"`** — `flow validate` rejects them otherwise (so they can't silently no-op). All violations are reported in one error; paths are dotted/indexed (`owner.email`, `tags[3]`); a non-object root reports `expected an object at the root but got <type>`. `flow validate` checks the schema's shape (and that `parseJson:true` is set). With `onError: { strategy: "continue" }` the step lands as `{ status: "failed", errorCode: "SCHEMA_VALIDATION" }` so downstream steps can branch on it; `retry`/`fallback` and `retryOn:["SCHEMA_VALIDATION"]` work too.
|
|
424
|
+
|
|
403
425
|
### `while` — Condition-driven loop (do-while)
|
|
404
426
|
|
|
405
427
|
```json
|
|
@@ -655,12 +677,24 @@ one --agent flow validate <key>
|
|
|
655
677
|
one --agent flow execute <key> -i key=value
|
|
656
678
|
one --agent flow execute <key> --dry-run -i key=value
|
|
657
679
|
one --agent flow execute <key> --dry-run --mock -i key=value
|
|
680
|
+
one --agent flow execute <key> --stop-after <stepId> -i key=value
|
|
681
|
+
one --agent flow execute <key> --dry-run --stop-after <stepId> -i key=value
|
|
658
682
|
one --agent flow execute <key> --skip-validation -i key=value
|
|
659
683
|
one --agent flow execute <key> --allow-bash -i key=value
|
|
660
684
|
one --agent flow runs [flowKey]
|
|
685
|
+
one --agent flow inspect <runId> # per-step outputs of a past run (add --full for untruncated)
|
|
661
686
|
one --agent flow resume <runId>
|
|
662
687
|
```
|
|
663
688
|
|
|
689
|
+
## Debugging a flow
|
|
690
|
+
|
|
691
|
+
Three tools that turn "re-run the whole 40-step flow to debug step 30" into near-zero-cost inspection:
|
|
692
|
+
|
|
693
|
+
- **`--dry-run`** — resolves every step's interpolations and shows what they evaluate to, *without executing any step*. `$.input.*` / `$.env.*` resolve immediately; `$.steps.*` references are reported as `deferred` (they're produced at runtime). Transform/condition/while steps show their *evaluated* result; if a transform can't evaluate yet because it reads a step that hasn't run, it's marked `deferred` (depends on a later step) rather than shown as an error — genuine problems (syntax errors, missing inputs, a missing field on a step that *did* run) still surface as errors. Catches wiring bugs (typo'd input names, wrong field paths) with zero API spend. In `--agent` mode this is the `flow:dry-run` event whose `steps[]` carry a `references` table (`{selector, value, status}` where status is `resolved` | `deferred` | `missing`), an evaluated `resolved` value, and a `deferred` / `error` flag for expression steps.
|
|
694
|
+
- **`--stop-after <stepId>`** — executes steps up to and including `<stepId>`, then stops; later steps don't run. Isolates where a long flow breaks. Emits a `flow:stopped` event; the run's partial state is saved.
|
|
695
|
+
- **`--dry-run --stop-after <stepId>`** — runs the steps *before* `<stepId>` for real, then dry-resolves `<stepId>` against that **real** accumulated context (so its `$.steps.*` references resolve to actual upstream output) and stops without executing it. The honest way to preview "what will step N actually receive". Emits a `step:dry-resolve` event for the target.
|
|
696
|
+
- **`flow inspect <runId>`** — prints a past run's per-step status, durations, errors, and outputs from its persisted state file (post-mortem, no re-run). Use after `--stop-after`, or on any failed run, to see exactly what each step produced. `--full` disables output truncation.
|
|
697
|
+
|
|
664
698
|
## Important Notes
|
|
665
699
|
|
|
666
700
|
- **Prefer passthrough actions over custom actions.** Custom actions add server-side fan-out that causes timeouts at scale. The flow runner handles pagination, retries, and rate limiting locally. Search with `-t knowledge` to find passthrough endpoints (e.g. GET `/gmail/v1/users/{userId}/threads` instead of POST `/gmail/get-threads`)
|