@withone/cli 1.32.1 → 1.34.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
CHANGED
|
@@ -98,7 +98,7 @@ one flow validate welcome-customer
|
|
|
98
98
|
one flow execute welcome-customer -i email=jane@example.com
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
Workflows live under `.one/flows/<key>/flow.json` with an optional `lib/` subfolder for `.mjs` code modules — create new flows in this folder layout. (The legacy single-file layout `.one/flows/<key>.flow.json` is deprecated but still loads for backward compatibility.) Flows support conditions, loops, while loops, parallel steps, transforms, sub-flows, pagination, bash steps, and external `.mjs` code modules. Run `one guide flows` for the full reference.
|
|
101
|
+
Workflows live under `.one/flows/<key>/flow.json` with an optional `lib/` subfolder for `.mjs` code modules — create new flows in this folder layout. Flows can be organized into subdirectory groups: `.one/flows/<group>/<key>/flow.json`. Reference them as `group/key` or just the bare key if unique. (The legacy single-file layout `.one/flows/<key>.flow.json` is deprecated but still loads for backward compatibility.) Flows support conditions, loops, while loops, parallel steps, transforms, sub-flows, pagination, bash steps, and external `.mjs` code modules. Run `one guide flows` for the full reference.
|
|
102
102
|
|
|
103
103
|
## How it works
|
|
104
104
|
|
|
@@ -357,12 +357,15 @@ In agent mode (`--agent`), the JSON response includes the guide content and an `
|
|
|
357
357
|
|
|
358
358
|
### `one flow create [key]`
|
|
359
359
|
|
|
360
|
-
Create a workflow from a JSON definition. New workflows are always saved to the folder layout at `.one/flows/<key>/flow.json` (with a `lib/` subfolder scaffolded for code modules). The legacy `.one/flows/<key>.flow.json` single-file layout is deprecated; existing legacy files continue to load and run unchanged for backward compatibility.
|
|
360
|
+
Create a workflow from a JSON definition. New workflows are always saved to the folder layout at `.one/flows/<key>/flow.json` (with a `lib/` subfolder scaffolded for code modules). Use `group/key` to place flows in a subdirectory group (e.g. `.one/flows/research/company-research/flow.json`). The legacy `.one/flows/<key>.flow.json` single-file layout is deprecated; existing legacy files continue to load and run unchanged for backward compatibility.
|
|
361
361
|
|
|
362
362
|
```bash
|
|
363
363
|
# From a --definition flag
|
|
364
364
|
one flow create welcome-customer --definition '{"key":"welcome-customer","name":"Welcome","version":"1","inputs":{},"steps":[]}'
|
|
365
365
|
|
|
366
|
+
# Create in a subdirectory group
|
|
367
|
+
one flow create research/company-research --definition @flow.json
|
|
368
|
+
|
|
366
369
|
# From stdin
|
|
367
370
|
cat flow.json | one flow create
|
|
368
371
|
|
|
@@ -1042,7 +1042,7 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
|
|
|
1042
1042
|
if (flowStack.includes(resolvedKey)) {
|
|
1043
1043
|
throw new Error(`Circular flow detected: ${[...flowStack, resolvedKey].join(" \u2192 ")}`);
|
|
1044
1044
|
}
|
|
1045
|
-
const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-
|
|
1045
|
+
const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-TNI7YWVY.js");
|
|
1046
1046
|
const { flow: subFlow, rootDir: subRootDir } = loadFlowWithMeta2(resolvedKey);
|
|
1047
1047
|
const subContext = await executeFlow(
|
|
1048
1048
|
subFlow,
|
|
@@ -1847,7 +1847,18 @@ Workflows live in \`.one/flows/\` (relative to your current working directory \u
|
|
|
1847
1847
|
- **Folder layout (REQUIRED for new flows)** \u2014 \`.one/flows/<key>/flow.json\`, with an optional \`lib/\` subfolder for JavaScript modules. This is like a skill: the folder groups the JSON spec with any JavaScript modules it needs, so the whole flow is shareable. **Always create new flows in this layout.**
|
|
1848
1848
|
- **Single-file layout (DEPRECATED)** \u2014 \`.one/flows/<key>.flow.json\`. Still loads and runs for backward compatibility, but is deprecated. Do not create new flows in this layout. When editing an existing single-file flow, migrate it to the folder layout: move \`<key>.flow.json\` to \`<key>/flow.json\` and extract any non-trivial \`code.source\` blocks into \`<key>/lib/*.mjs\` modules.
|
|
1849
1849
|
|
|
1850
|
-
|
|
1850
|
+
**Subdirectory groups** \u2014 Flows can be organized into subdirectories: \`.one/flows/<group>/<key>/flow.json\`. For example:
|
|
1851
|
+
\`\`\`
|
|
1852
|
+
.one/flows/
|
|
1853
|
+
research/
|
|
1854
|
+
company-research/flow.json
|
|
1855
|
+
competitor-research/flow.json
|
|
1856
|
+
deal-ops/
|
|
1857
|
+
deal-log/flow.json
|
|
1858
|
+
\`\`\`
|
|
1859
|
+
Reference grouped flows with \`group/key\` (e.g. \`one flow execute research/company-research\`) or just the bare key if it's unique (e.g. \`one flow execute company-research\`). Create grouped flows with \`one flow create research/company-research --definition ...\`. \`flow list\` shows the group prefix.
|
|
1860
|
+
|
|
1861
|
+
When resolving a flow by key, the CLI checks the folder layout first, then the deprecated legacy file, then scans group subdirectories. The \`loadFlow\` helper in agent integrations behaves the same.
|
|
1851
1862
|
|
|
1852
1863
|
## Before you execute a flow you did NOT author \u2014 READ THIS
|
|
1853
1864
|
|
|
@@ -1872,19 +1883,21 @@ A good description is one paragraph. If a flow's description doesn't tell you ho
|
|
|
1872
1883
|
## Commands
|
|
1873
1884
|
|
|
1874
1885
|
\`\`\`bash
|
|
1875
|
-
one --agent flow create <key> --definition '<json>'
|
|
1876
|
-
one --agent flow create <key> --definition @flow.json
|
|
1877
|
-
one --agent flow
|
|
1878
|
-
one --agent flow
|
|
1879
|
-
one --agent flow
|
|
1880
|
-
one --agent flow execute <key>
|
|
1881
|
-
one --agent flow execute <key>
|
|
1882
|
-
one --agent flow
|
|
1883
|
-
one --agent flow
|
|
1884
|
-
one --agent flow
|
|
1886
|
+
one --agent flow create <key> --definition '<json>' # Create (or --definition @file.json)
|
|
1887
|
+
one --agent flow create <key> --definition @flow.json # Create from file
|
|
1888
|
+
one --agent flow create <group/key> --definition '<json>' # Create in a subdirectory group
|
|
1889
|
+
one --agent flow list # List (shows group prefixes)
|
|
1890
|
+
one --agent flow validate <key> # Validate
|
|
1891
|
+
one --agent flow execute <key> -i name=value # Execute (bare key)
|
|
1892
|
+
one --agent flow execute <group/key> -i name=value # Execute (namespaced key)
|
|
1893
|
+
one --agent flow execute <key> --dry-run --mock # Test with mock data
|
|
1894
|
+
one --agent flow execute <key> --allow-bash # Enable bash steps
|
|
1895
|
+
one --agent flow runs [flowKey] # List past runs
|
|
1896
|
+
one --agent flow resume <runId> # Resume failed run
|
|
1897
|
+
one --agent flow scaffold [template] # Generate a starter template
|
|
1885
1898
|
\`\`\`
|
|
1886
1899
|
|
|
1887
|
-
You can also write the JSON file directly to \`.one/flows/<key>/flow.json\` \u2014 often easier than passing large JSON via --definition. (The legacy \`.one/flows/<key>.flow.json\` single-file location is deprecated; don't use it for new flows.)
|
|
1900
|
+
You can also write the JSON file directly to \`.one/flows/<key>/flow.json\` (or \`.one/flows/<group>/<key>/flow.json\` for grouped flows) \u2014 often easier than passing large JSON via --definition. (The legacy \`.one/flows/<key>.flow.json\` single-file location is deprecated; don't use it for new flows.)
|
|
1888
1901
|
|
|
1889
1902
|
## Code modules (flow \`lib/\` folder)
|
|
1890
1903
|
|
|
@@ -2501,13 +2514,31 @@ var FlowRunner = class _FlowRunner {
|
|
|
2501
2514
|
}
|
|
2502
2515
|
};
|
|
2503
2516
|
function resolveFlowPath(keyOrPath) {
|
|
2504
|
-
if (keyOrPath.
|
|
2517
|
+
if (keyOrPath.endsWith(".json")) {
|
|
2518
|
+
return path2.resolve(keyOrPath);
|
|
2519
|
+
}
|
|
2520
|
+
if (keyOrPath.includes("\\")) {
|
|
2505
2521
|
return path2.resolve(keyOrPath);
|
|
2506
2522
|
}
|
|
2523
|
+
if (keyOrPath.includes("/")) {
|
|
2524
|
+
const nestedFolder = path2.resolve(FLOWS_DIR, keyOrPath, "flow.json");
|
|
2525
|
+
if (fs2.existsSync(nestedFolder)) return nestedFolder;
|
|
2526
|
+
const literal = path2.resolve(keyOrPath);
|
|
2527
|
+
if (fs2.existsSync(literal)) return literal;
|
|
2528
|
+
return nestedFolder;
|
|
2529
|
+
}
|
|
2507
2530
|
const folderPath = path2.resolve(FLOWS_DIR, keyOrPath, "flow.json");
|
|
2508
|
-
const legacyPath = path2.resolve(FLOWS_DIR, `${keyOrPath}.flow.json`);
|
|
2509
2531
|
if (fs2.existsSync(folderPath)) return folderPath;
|
|
2532
|
+
const legacyPath = path2.resolve(FLOWS_DIR, `${keyOrPath}.flow.json`);
|
|
2510
2533
|
if (fs2.existsSync(legacyPath)) return legacyPath;
|
|
2534
|
+
const flowsDir = path2.resolve(FLOWS_DIR);
|
|
2535
|
+
if (fs2.existsSync(flowsDir)) {
|
|
2536
|
+
for (const entry of fs2.readdirSync(flowsDir, { withFileTypes: true })) {
|
|
2537
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
|
|
2538
|
+
const nested = path2.join(flowsDir, entry.name, keyOrPath, "flow.json");
|
|
2539
|
+
if (fs2.existsSync(nested)) return nested;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2511
2542
|
return folderPath;
|
|
2512
2543
|
}
|
|
2513
2544
|
function getFlowRootDir(flowFilePath) {
|
|
@@ -2572,12 +2603,13 @@ function listFlows() {
|
|
|
2572
2603
|
if (!fs2.existsSync(flowsDir)) return [];
|
|
2573
2604
|
const flows = [];
|
|
2574
2605
|
const seenKeys = /* @__PURE__ */ new Set();
|
|
2575
|
-
const readFlowFile = (filePath) => {
|
|
2606
|
+
const readFlowFile = (filePath, group) => {
|
|
2576
2607
|
try {
|
|
2577
2608
|
const content = fs2.readFileSync(filePath, "utf-8");
|
|
2578
2609
|
const flow = JSON.parse(content);
|
|
2579
|
-
|
|
2580
|
-
seenKeys.
|
|
2610
|
+
const nsKey = group ? `${group}/${flow.key}` : flow.key;
|
|
2611
|
+
if (seenKeys.has(nsKey)) return;
|
|
2612
|
+
seenKeys.add(nsKey);
|
|
2581
2613
|
flows.push({
|
|
2582
2614
|
key: flow.key,
|
|
2583
2615
|
name: flow.name,
|
|
@@ -2586,6 +2618,7 @@ function listFlows() {
|
|
|
2586
2618
|
stepCount: flow.steps.length,
|
|
2587
2619
|
path: filePath,
|
|
2588
2620
|
layout: path2.basename(filePath) === "flow.json" ? "folder" : "legacy",
|
|
2621
|
+
group,
|
|
2589
2622
|
stepTypes: collectStepTypes(flow),
|
|
2590
2623
|
requiresBash: flowRequiresBash(flow),
|
|
2591
2624
|
usesCodeModules: flowUsesCodeModules(flow),
|
|
@@ -2594,26 +2627,44 @@ function listFlows() {
|
|
|
2594
2627
|
} catch {
|
|
2595
2628
|
}
|
|
2596
2629
|
};
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2630
|
+
const scanDir = (dir, group) => {
|
|
2631
|
+
for (const entry of fs2.readdirSync(dir, { withFileTypes: true })) {
|
|
2632
|
+
if (entry.name.startsWith(".")) continue;
|
|
2633
|
+
const full = path2.join(dir, entry.name);
|
|
2634
|
+
if (entry.isDirectory()) {
|
|
2635
|
+
const flowJson = path2.join(full, "flow.json");
|
|
2636
|
+
if (fs2.existsSync(flowJson)) {
|
|
2637
|
+
readFlowFile(flowJson, group);
|
|
2638
|
+
} else {
|
|
2639
|
+
if (!group) {
|
|
2640
|
+
scanDir(full, entry.name);
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
} else if (entry.isFile() && entry.name.endsWith(".flow.json")) {
|
|
2644
|
+
readFlowFile(full, group);
|
|
2645
|
+
}
|
|
2605
2646
|
}
|
|
2606
|
-
}
|
|
2647
|
+
};
|
|
2648
|
+
scanDir(flowsDir);
|
|
2607
2649
|
return flows;
|
|
2608
2650
|
}
|
|
2609
|
-
function saveFlow(flow, outputPath) {
|
|
2651
|
+
function saveFlow(flow, outputPath, group) {
|
|
2610
2652
|
let flowPath;
|
|
2611
2653
|
if (outputPath) {
|
|
2612
2654
|
flowPath = path2.resolve(outputPath);
|
|
2613
2655
|
} else {
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
if (
|
|
2656
|
+
let bareKey = flow.key;
|
|
2657
|
+
let resolvedGroup = group;
|
|
2658
|
+
if (flow.key.includes("/")) {
|
|
2659
|
+
const parts = flow.key.split("/");
|
|
2660
|
+
bareKey = parts.pop();
|
|
2661
|
+
resolvedGroup = resolvedGroup || parts.join("/");
|
|
2662
|
+
flow.key = bareKey;
|
|
2663
|
+
}
|
|
2664
|
+
const basePath = resolvedGroup ? path2.resolve(FLOWS_DIR, resolvedGroup, bareKey) : path2.resolve(FLOWS_DIR, bareKey);
|
|
2665
|
+
const legacyPath = path2.resolve(FLOWS_DIR, `${bareKey}.flow.json`);
|
|
2666
|
+
const folderPath = path2.join(basePath, "flow.json");
|
|
2667
|
+
if (!resolvedGroup && fs2.existsSync(legacyPath) && !fs2.existsSync(folderPath)) {
|
|
2617
2668
|
flowPath = legacyPath;
|
|
2618
2669
|
} else {
|
|
2619
2670
|
flowPath = folderPath;
|
package/dist/index.js
CHANGED
|
@@ -19,10 +19,11 @@ import {
|
|
|
19
19
|
resolveFlowPath,
|
|
20
20
|
saveFlow,
|
|
21
21
|
validateActionInput
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-HZP7NT4K.js";
|
|
23
23
|
|
|
24
24
|
// src/index.ts
|
|
25
25
|
import { createRequire as createRequire2 } from "module";
|
|
26
|
+
import path16 from "path";
|
|
26
27
|
import { Command } from "commander";
|
|
27
28
|
|
|
28
29
|
// src/commands/init.ts
|
|
@@ -73,6 +74,19 @@ function resolveConfig() {
|
|
|
73
74
|
return { config: config2, scope: "project", path: projectPath, projectRoot, projectSlug };
|
|
74
75
|
}
|
|
75
76
|
}
|
|
77
|
+
const root = path.parse(process.cwd()).root;
|
|
78
|
+
let dir = path.resolve(process.cwd());
|
|
79
|
+
while (dir !== root) {
|
|
80
|
+
dir = path.dirname(dir);
|
|
81
|
+
const slug = getProjectSlug(dir);
|
|
82
|
+
const configPath = path.join(PROJECTS_DIR, slug, "config.json");
|
|
83
|
+
if (fs.existsSync(configPath)) {
|
|
84
|
+
const config2 = readConfigFile(configPath);
|
|
85
|
+
if (config2) {
|
|
86
|
+
return { config: config2, scope: "project", path: configPath, projectRoot: dir, projectSlug: slug };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
76
90
|
if (fs.existsSync(CONFIG_FILE)) {
|
|
77
91
|
const config2 = readConfigFile(CONFIG_FILE);
|
|
78
92
|
if (config2) {
|
|
@@ -2882,32 +2896,32 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
2882
2896
|
const validTypes = FLOW_SCHEMA.stepTypes.map((st) => st.type);
|
|
2883
2897
|
for (let i = 0; i < steps.length; i++) {
|
|
2884
2898
|
const step = steps[i];
|
|
2885
|
-
const
|
|
2899
|
+
const path17 = `${pathPrefix}[${i}]`;
|
|
2886
2900
|
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
2887
|
-
errors.push({ path:
|
|
2901
|
+
errors.push({ path: path17, message: "Step must be an object" });
|
|
2888
2902
|
continue;
|
|
2889
2903
|
}
|
|
2890
2904
|
const s = step;
|
|
2891
2905
|
if (!s.id || typeof s.id !== "string") {
|
|
2892
|
-
errors.push({ path: `${
|
|
2906
|
+
errors.push({ path: `${path17}.id`, message: 'Step must have a string "id"' });
|
|
2893
2907
|
}
|
|
2894
2908
|
if (!s.name || typeof s.name !== "string") {
|
|
2895
|
-
errors.push({ path: `${
|
|
2909
|
+
errors.push({ path: `${path17}.name`, message: 'Step must have a string "name"' });
|
|
2896
2910
|
}
|
|
2897
2911
|
if (!s.type || !validTypes.includes(s.type)) {
|
|
2898
|
-
errors.push({ path: `${
|
|
2912
|
+
errors.push({ path: `${path17}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
|
|
2899
2913
|
continue;
|
|
2900
2914
|
}
|
|
2901
2915
|
if (s.requires !== void 0) {
|
|
2902
2916
|
if (!Array.isArray(s.requires)) {
|
|
2903
|
-
errors.push({ path: `${
|
|
2917
|
+
errors.push({ path: `${path17}.requires`, message: '"requires" must be an array of selector strings (e.g. ["$.steps.foo.output.bar"])' });
|
|
2904
2918
|
} else {
|
|
2905
2919
|
for (let r = 0; r < s.requires.length; r++) {
|
|
2906
2920
|
const sel = s.requires[r];
|
|
2907
2921
|
if (typeof sel !== "string") {
|
|
2908
|
-
errors.push({ path: `${
|
|
2922
|
+
errors.push({ path: `${path17}.requires[${r}]`, message: '"requires" entry must be a selector string' });
|
|
2909
2923
|
} else if (!sel.startsWith("$.")) {
|
|
2910
|
-
errors.push({ path: `${
|
|
2924
|
+
errors.push({ path: `${path17}.requires[${r}]`, message: `"requires" entry "${sel}" must be a selector starting with "$." (e.g. "$.steps.foo.output.bar")` });
|
|
2911
2925
|
}
|
|
2912
2926
|
}
|
|
2913
2927
|
}
|
|
@@ -2915,7 +2929,7 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
2915
2929
|
if (s.onError && typeof s.onError === "object") {
|
|
2916
2930
|
const oe = s.onError;
|
|
2917
2931
|
if (!FLOW_SCHEMA.errorStrategies.includes(oe.strategy)) {
|
|
2918
|
-
errors.push({ path: `${
|
|
2932
|
+
errors.push({ path: `${path17}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
|
|
2919
2933
|
}
|
|
2920
2934
|
}
|
|
2921
2935
|
const descriptor = getStepTypeDescriptor(s.type);
|
|
@@ -2925,14 +2939,14 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
2925
2939
|
if (!configObj || typeof configObj !== "object") {
|
|
2926
2940
|
const hint = detectFlatConfigHint(s, descriptor);
|
|
2927
2941
|
errors.push({
|
|
2928
|
-
path: `${
|
|
2942
|
+
path: `${path17}.${configKey}`,
|
|
2929
2943
|
message: `${capitalize(descriptor.type)} step must have a "${configKey}" config object${hint}`
|
|
2930
2944
|
});
|
|
2931
2945
|
continue;
|
|
2932
2946
|
}
|
|
2933
2947
|
const config2 = configObj;
|
|
2934
2948
|
for (const [fieldName, fd] of Object.entries(descriptor.fields)) {
|
|
2935
|
-
const fieldPath = `${
|
|
2949
|
+
const fieldPath = `${path17}.${configKey}.${fieldName}`;
|
|
2936
2950
|
const value = config2[fieldName];
|
|
2937
2951
|
if (fd.required && (value === void 0 || value === null || value === "")) {
|
|
2938
2952
|
errors.push({ path: fieldPath, message: `${capitalize(descriptor.type)} must have ${fd.type === "string" ? "a string" : fd.type === "array" ? "a" : "a"} "${fieldName}"` });
|
|
@@ -2968,24 +2982,24 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
2968
2982
|
const hasSource = typeof config2.source === "string" && config2.source.length > 0;
|
|
2969
2983
|
const hasModule = typeof config2.module === "string" && config2.module.length > 0;
|
|
2970
2984
|
if (!hasSource && !hasModule) {
|
|
2971
|
-
errors.push({ path: `${
|
|
2985
|
+
errors.push({ path: `${path17}.${configKey}`, message: 'Code step must define either "source" (inline JS) or "module" (path to .mjs file)' });
|
|
2972
2986
|
} else if (hasSource && hasModule) {
|
|
2973
|
-
errors.push({ path: `${
|
|
2987
|
+
errors.push({ path: `${path17}.${configKey}`, message: 'Code step cannot define both "source" and "module" \u2014 pick one' });
|
|
2974
2988
|
}
|
|
2975
2989
|
if (hasModule) {
|
|
2976
2990
|
const m = config2.module;
|
|
2977
2991
|
if (m.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(m)) {
|
|
2978
|
-
errors.push({ path: `${
|
|
2992
|
+
errors.push({ path: `${path17}.${configKey}.module`, message: "Code module path must be relative to the flow folder (no absolute paths)" });
|
|
2979
2993
|
} else if (m.split(/[\\/]/).includes("..")) {
|
|
2980
|
-
errors.push({ path: `${
|
|
2994
|
+
errors.push({ path: `${path17}.${configKey}.module`, message: 'Code module path must not escape the flow folder ("..")' });
|
|
2981
2995
|
} else if (!m.endsWith(".mjs")) {
|
|
2982
|
-
errors.push({ path: `${
|
|
2996
|
+
errors.push({ path: `${path17}.${configKey}.module`, message: "Code module must be a .mjs file" });
|
|
2983
2997
|
}
|
|
2984
2998
|
}
|
|
2985
2999
|
if (hasSource) {
|
|
2986
3000
|
const syntaxError = checkCodeSourceSyntax(config2.source);
|
|
2987
3001
|
if (syntaxError) {
|
|
2988
|
-
errors.push({ path: `${
|
|
3002
|
+
errors.push({ path: `${path17}.${configKey}.source`, message: `Syntax error in code step: ${syntaxError}` });
|
|
2989
3003
|
}
|
|
2990
3004
|
}
|
|
2991
3005
|
}
|
|
@@ -3020,16 +3034,16 @@ function validateStepIds(flow2) {
|
|
|
3020
3034
|
function collectIds(steps, pathPrefix) {
|
|
3021
3035
|
for (let i = 0; i < steps.length; i++) {
|
|
3022
3036
|
const step = steps[i];
|
|
3023
|
-
const
|
|
3037
|
+
const path17 = `${pathPrefix}[${i}]`;
|
|
3024
3038
|
if (seen.has(step.id)) {
|
|
3025
|
-
errors.push({ path: `${
|
|
3039
|
+
errors.push({ path: `${path17}.id`, message: `Duplicate step ID: "${step.id}"` });
|
|
3026
3040
|
} else {
|
|
3027
3041
|
seen.add(step.id);
|
|
3028
3042
|
}
|
|
3029
3043
|
for (const { configKey, fieldName } of nestedKeys) {
|
|
3030
3044
|
const config2 = step[configKey];
|
|
3031
3045
|
if (config2 && Array.isArray(config2[fieldName])) {
|
|
3032
|
-
collectIds(config2[fieldName], `${
|
|
3046
|
+
collectIds(config2[fieldName], `${path17}.${configKey}.${fieldName}`);
|
|
3033
3047
|
}
|
|
3034
3048
|
}
|
|
3035
3049
|
}
|
|
@@ -3077,7 +3091,7 @@ function validateSelectorReferences(flow2) {
|
|
|
3077
3091
|
}
|
|
3078
3092
|
return selectors;
|
|
3079
3093
|
}
|
|
3080
|
-
function checkSelectors(selectors,
|
|
3094
|
+
function checkSelectors(selectors, path17, precedingStepIds) {
|
|
3081
3095
|
for (const selector of selectors) {
|
|
3082
3096
|
const parts = selector.split(".");
|
|
3083
3097
|
if (parts.length < 3) continue;
|
|
@@ -3085,15 +3099,15 @@ function validateSelectorReferences(flow2) {
|
|
|
3085
3099
|
if (root === "input") {
|
|
3086
3100
|
const inputName = parts[2];
|
|
3087
3101
|
if (!inputNames.has(inputName)) {
|
|
3088
|
-
errors.push({ path:
|
|
3102
|
+
errors.push({ path: path17, message: `Selector "${selector}" references undefined input "${inputName}"` });
|
|
3089
3103
|
}
|
|
3090
3104
|
} else if (root === "steps") {
|
|
3091
3105
|
const stepId = parts[2].replace(/[\[\]]/g, "").split(/[\[\]]/)[0];
|
|
3092
3106
|
if (!allStepIds.has(stepId)) {
|
|
3093
|
-
errors.push({ path:
|
|
3107
|
+
errors.push({ path: path17, message: `Selector "${selector}" references undefined step "${stepId}"` });
|
|
3094
3108
|
} else if (precedingStepIds && !precedingStepIds.has(stepId)) {
|
|
3095
3109
|
errors.push({
|
|
3096
|
-
path:
|
|
3110
|
+
path: path17,
|
|
3097
3111
|
message: `Selector "${selector}" references step "${stepId}" which is declared after the current step. Steps execute in declaration order, so this will always resolve to undefined at runtime \u2014 move the dependency earlier in the steps array.`
|
|
3098
3112
|
});
|
|
3099
3113
|
}
|
|
@@ -3101,20 +3115,20 @@ function validateSelectorReferences(flow2) {
|
|
|
3101
3115
|
}
|
|
3102
3116
|
}
|
|
3103
3117
|
const EXPRESSION_FIELDS = /* @__PURE__ */ new Set(["condition.expression", "while.condition"]);
|
|
3104
|
-
function checkOperatorsInSelectorField(value,
|
|
3118
|
+
function checkOperatorsInSelectorField(value, path17) {
|
|
3105
3119
|
if (typeof value === "string" && value.startsWith("$.")) {
|
|
3106
3120
|
if (value.includes("||")) {
|
|
3107
|
-
errors.push({ path:
|
|
3121
|
+
errors.push({ path: path17, message: `Selector "${value}" contains unsupported operator "||". Selectors in data fields use dot-path resolution, not JS evaluation. Use the "default" field on the input definition instead, or use a "code" step for complex expressions.` });
|
|
3108
3122
|
} else if (value.includes("&&")) {
|
|
3109
|
-
errors.push({ path:
|
|
3123
|
+
errors.push({ path: path17, message: `Selector "${value}" contains unsupported operator "&&". Selectors in data fields use dot-path resolution, not JS evaluation. Use a "condition" step or "code" step for complex expressions.` });
|
|
3110
3124
|
}
|
|
3111
3125
|
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
3112
3126
|
for (const [k, v] of Object.entries(value)) {
|
|
3113
|
-
checkOperatorsInSelectorField(v, `${
|
|
3127
|
+
checkOperatorsInSelectorField(v, `${path17}.${k}`);
|
|
3114
3128
|
}
|
|
3115
3129
|
} else if (Array.isArray(value)) {
|
|
3116
3130
|
for (let i = 0; i < value.length; i++) {
|
|
3117
|
-
checkOperatorsInSelectorField(value[i], `${
|
|
3131
|
+
checkOperatorsInSelectorField(value[i], `${path17}[${i}]`);
|
|
3118
3132
|
}
|
|
3119
3133
|
}
|
|
3120
3134
|
}
|
|
@@ -3182,10 +3196,10 @@ var VALID_OUTPUT_SCHEMA_TYPES = /* @__PURE__ */ new Set(["string", "number", "bo
|
|
|
3182
3196
|
function isOutputSchemaObject(v) {
|
|
3183
3197
|
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
3184
3198
|
}
|
|
3185
|
-
function walkOutputSchema(schema,
|
|
3199
|
+
function walkOutputSchema(schema, path17) {
|
|
3186
3200
|
let current = schema;
|
|
3187
|
-
for (let i = 0; i <
|
|
3188
|
-
const seg =
|
|
3201
|
+
for (let i = 0; i < path17.length; i++) {
|
|
3202
|
+
const seg = path17[i];
|
|
3189
3203
|
if (typeof current === "string") {
|
|
3190
3204
|
return current === "unknown" || current === "object" || current === "array" ? "opaque" : "opaque";
|
|
3191
3205
|
}
|
|
@@ -3449,8 +3463,15 @@ async function flowCreateCommand(key, options) {
|
|
|
3449
3463
|
} else {
|
|
3450
3464
|
error("Interactive workflow creation not yet supported. Use --definition <json> or pipe JSON via stdin.");
|
|
3451
3465
|
}
|
|
3466
|
+
let group;
|
|
3452
3467
|
if (key) {
|
|
3453
|
-
|
|
3468
|
+
if (key.includes("/")) {
|
|
3469
|
+
const parts = key.split("/");
|
|
3470
|
+
flow2.key = parts.pop();
|
|
3471
|
+
group = parts.join("/");
|
|
3472
|
+
} else {
|
|
3473
|
+
flow2.key = key;
|
|
3474
|
+
}
|
|
3454
3475
|
}
|
|
3455
3476
|
const errors = validateFlow(flow2);
|
|
3456
3477
|
if (errors.length > 0) {
|
|
@@ -3461,7 +3482,7 @@ async function flowCreateCommand(key, options) {
|
|
|
3461
3482
|
error(`Validation failed:
|
|
3462
3483
|
${errors.map((e) => ` ${e.path}: ${e.message}`).join("\n")}`);
|
|
3463
3484
|
}
|
|
3464
|
-
const flowPath = saveFlow(flow2, options.output);
|
|
3485
|
+
const flowPath = saveFlow(flow2, options.output, group);
|
|
3465
3486
|
if (isAgentMode()) {
|
|
3466
3487
|
json({ created: true, key: flow2.key, path: flowPath });
|
|
3467
3488
|
return;
|
|
@@ -3625,7 +3646,7 @@ async function flowListCommand() {
|
|
|
3625
3646
|
{ key: "flags", label: "Requires" }
|
|
3626
3647
|
],
|
|
3627
3648
|
flows.map((f) => ({
|
|
3628
|
-
key: f.key,
|
|
3649
|
+
key: f.group ? `${f.group}/${f.key}` : f.key,
|
|
3629
3650
|
name: f.name,
|
|
3630
3651
|
layout: f.layout,
|
|
3631
3652
|
inputCount: String(f.inputCount),
|
|
@@ -4954,8 +4975,8 @@ function sleep(ms) {
|
|
|
4954
4975
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4955
4976
|
}
|
|
4956
4977
|
function interpolate(template, record) {
|
|
4957
|
-
return template.replace(/\{?\{(\w+(?:\.\w+)*)\}\}?/g, (_,
|
|
4958
|
-
const parts =
|
|
4978
|
+
return template.replace(/\{?\{(\w+(?:\.\w+)*)\}\}?/g, (_, path17) => {
|
|
4979
|
+
const parts = path17.split(".");
|
|
4959
4980
|
let value = record;
|
|
4960
4981
|
for (const part of parts) {
|
|
4961
4982
|
if (typeof value !== "object" || value === null) return "";
|
|
@@ -4983,8 +5004,8 @@ function deepMerge(target, source) {
|
|
|
4983
5004
|
}
|
|
4984
5005
|
return result;
|
|
4985
5006
|
}
|
|
4986
|
-
function getByDotPath2(obj,
|
|
4987
|
-
const parts =
|
|
5007
|
+
function getByDotPath2(obj, path17) {
|
|
5008
|
+
const parts = path17.split(".");
|
|
4988
5009
|
let current = obj;
|
|
4989
5010
|
for (const part of parts) {
|
|
4990
5011
|
if (current === null || current === void 0 || typeof current !== "object") return void 0;
|
|
@@ -4993,8 +5014,8 @@ function getByDotPath2(obj, path16) {
|
|
|
4993
5014
|
return current;
|
|
4994
5015
|
}
|
|
4995
5016
|
function stripExcludedFields(obj, paths) {
|
|
4996
|
-
for (const
|
|
4997
|
-
stripOnePath(obj,
|
|
5017
|
+
for (const path17 of paths) {
|
|
5018
|
+
stripOnePath(obj, path17.replace(/\[\]/g, ".*").split("."));
|
|
4998
5019
|
}
|
|
4999
5020
|
}
|
|
5000
5021
|
function stripOnePath(obj, parts) {
|
|
@@ -5300,8 +5321,8 @@ function sleep2(ms) {
|
|
|
5300
5321
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
5301
5322
|
}
|
|
5302
5323
|
function stripFields(record, paths) {
|
|
5303
|
-
for (const
|
|
5304
|
-
stripOnePath2(record,
|
|
5324
|
+
for (const path17 of paths) {
|
|
5325
|
+
stripOnePath2(record, path17.split("."));
|
|
5305
5326
|
}
|
|
5306
5327
|
}
|
|
5307
5328
|
function stripOnePath2(obj, parts) {
|
|
@@ -7706,7 +7727,7 @@ one --agent flow list # List all workflows
|
|
|
7706
7727
|
\`\`\`
|
|
7707
7728
|
|
|
7708
7729
|
**Key concepts:**
|
|
7709
|
-
- Workflows live at \`.one/flows/<key>/flow.json\` (folder layout \u2014 REQUIRED for new flows). The legacy \`.one/flows/<key>.flow.json\` single-file layout is DEPRECATED but still loads for backward compatibility
|
|
7730
|
+
- Workflows live at \`.one/flows/<key>/flow.json\` (folder layout \u2014 REQUIRED for new flows). Flows can be organized into subdirectory groups: \`.one/flows/<group>/<key>/flow.json\`. Reference them as \`group/key\` or just the bare key. The legacy \`.one/flows/<key>.flow.json\` single-file layout is DEPRECATED but still loads for backward compatibility
|
|
7710
7731
|
- Code steps can reference an external \`.mjs\` module under the flow's \`lib/\` folder (stdin JSON in, stdout JSON out) \u2014 keeps JS out of JSON strings and makes flows shareable
|
|
7711
7732
|
- 12 step types: action, transform, code, condition, loop, parallel, file-read, file-write, while, flow, paginate, bash
|
|
7712
7733
|
- Data wiring via selectors: \`$.input.param\`, \`$.steps.stepId.response\`, \`$.loop.item\`
|
|
@@ -8683,8 +8704,8 @@ program.name("one").option("--agent", "Machine-readable JSON output (no colors,
|
|
|
8683
8704
|
|
|
8684
8705
|
Workflows (multi-step):
|
|
8685
8706
|
one flow list List saved workflows
|
|
8686
|
-
one flow create [key] Create a workflow from JSON
|
|
8687
|
-
one flow execute <key> Execute a workflow
|
|
8707
|
+
one flow create [key] Create a workflow from JSON (key can be group/key)
|
|
8708
|
+
one flow execute <key> Execute a workflow (key can be group/key)
|
|
8688
8709
|
one flow validate <key> Validate a flow
|
|
8689
8710
|
|
|
8690
8711
|
Data Sync (run "one sync install" first, then "one guide sync" for full reference):
|
|
@@ -8828,6 +8849,83 @@ configSkills.command("status").description("Show installed skill version and whe
|
|
|
8828
8849
|
console.log(` current: ${status.currentVersion}`);
|
|
8829
8850
|
console.log(` path: ${status.canonicalPath}`);
|
|
8830
8851
|
});
|
|
8852
|
+
config.command("reset").description("Remove the project config for the current directory (falls back to global)").option("--global", "Remove the global config instead").action(async (opts) => {
|
|
8853
|
+
const resolved = resolveConfig();
|
|
8854
|
+
if (opts.global) {
|
|
8855
|
+
const globalPath = getGlobalConfigPath();
|
|
8856
|
+
if (!globalConfigExists()) {
|
|
8857
|
+
if (isAgentMode()) {
|
|
8858
|
+
json({ deleted: false, reason: "No global config found" });
|
|
8859
|
+
} else {
|
|
8860
|
+
console.log("No global config found.");
|
|
8861
|
+
}
|
|
8862
|
+
return;
|
|
8863
|
+
}
|
|
8864
|
+
if (!isAgentMode()) {
|
|
8865
|
+
const p8 = await import("@clack/prompts");
|
|
8866
|
+
const confirmed = await p8.confirm({
|
|
8867
|
+
message: "Delete the global config? This removes your API key for all projects without a project config.",
|
|
8868
|
+
initialValue: false
|
|
8869
|
+
});
|
|
8870
|
+
if (p8.isCancel(confirmed) || !confirmed) {
|
|
8871
|
+
console.log("Cancelled.");
|
|
8872
|
+
return;
|
|
8873
|
+
}
|
|
8874
|
+
}
|
|
8875
|
+
const fs17 = await import("fs");
|
|
8876
|
+
fs17.unlinkSync(globalPath);
|
|
8877
|
+
if (isAgentMode()) {
|
|
8878
|
+
json({ deleted: true, scope: "global" });
|
|
8879
|
+
} else {
|
|
8880
|
+
console.log("Global config removed.");
|
|
8881
|
+
}
|
|
8882
|
+
return;
|
|
8883
|
+
}
|
|
8884
|
+
if (resolved.scope !== "project") {
|
|
8885
|
+
if (isAgentMode()) {
|
|
8886
|
+
json({ deleted: false, reason: "No project config found for this directory" });
|
|
8887
|
+
} else {
|
|
8888
|
+
console.log("No project config found for this directory.");
|
|
8889
|
+
console.log(`Currently using: ${resolved.scope ?? "none"}`);
|
|
8890
|
+
}
|
|
8891
|
+
return;
|
|
8892
|
+
}
|
|
8893
|
+
const fs16 = await import("fs");
|
|
8894
|
+
const configContent = fs16.readFileSync(resolved.path, "utf-8");
|
|
8895
|
+
fs16.unlinkSync(resolved.path);
|
|
8896
|
+
const next = resolveConfig();
|
|
8897
|
+
fs16.mkdirSync(path16.dirname(resolved.path), { recursive: true });
|
|
8898
|
+
fs16.writeFileSync(resolved.path, configContent);
|
|
8899
|
+
let fallbackLabel;
|
|
8900
|
+
if (next.scope === "project") {
|
|
8901
|
+
fallbackLabel = `parent project config (${path16.basename(next.projectRoot)})`;
|
|
8902
|
+
} else if (next.scope === "global") {
|
|
8903
|
+
fallbackLabel = "global config";
|
|
8904
|
+
} else {
|
|
8905
|
+
fallbackLabel = "no config";
|
|
8906
|
+
}
|
|
8907
|
+
if (!isAgentMode()) {
|
|
8908
|
+
const p8 = await import("@clack/prompts");
|
|
8909
|
+
const confirmed = await p8.confirm({
|
|
8910
|
+
message: `Delete project config for ${path16.basename(resolved.projectRoot)}? Will fall back to ${fallbackLabel}.`,
|
|
8911
|
+
initialValue: false
|
|
8912
|
+
});
|
|
8913
|
+
if (p8.isCancel(confirmed) || !confirmed) {
|
|
8914
|
+
console.log("Cancelled.");
|
|
8915
|
+
return;
|
|
8916
|
+
}
|
|
8917
|
+
}
|
|
8918
|
+
fs16.unlinkSync(resolved.path);
|
|
8919
|
+
try {
|
|
8920
|
+
fs16.rmdirSync(path16.dirname(resolved.path));
|
|
8921
|
+
} catch {
|
|
8922
|
+
}
|
|
8923
|
+
if (isAgentMode()) {
|
|
8924
|
+
json({ deleted: true, scope: "project", projectRoot: resolved.projectRoot, fallback: next.scope });
|
|
8925
|
+
} else {
|
|
8926
|
+
console.log(`Project config removed. Now using ${fallbackLabel}.`);
|
|
8927
|
+
}
|
|
8928
|
+
});
|
|
8831
8929
|
var connection = program.command("connection").description("Manage connections");
|
|
8832
8930
|
connection.command("add [platform]").alias("a").description("Add a new connection").action(async (platform) => {
|
|
8833
8931
|
await connectionAddCommand(platform);
|