@withone/cli 1.14.1 → 1.16.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/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
loadFlow,
|
|
12
12
|
resolveFlowPath,
|
|
13
13
|
saveFlow
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-SX2Y4HDW.js";
|
|
15
15
|
|
|
16
16
|
// src/index.ts
|
|
17
17
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -1316,7 +1316,9 @@ async function connectionListCommand(options) {
|
|
|
1316
1316
|
connections: limited.map((conn) => ({
|
|
1317
1317
|
platform: conn.platform,
|
|
1318
1318
|
state: conn.state,
|
|
1319
|
-
key: conn.key
|
|
1319
|
+
key: conn.key,
|
|
1320
|
+
...conn.name && { name: conn.name },
|
|
1321
|
+
...conn.tags?.length && { tags: conn.tags }
|
|
1320
1322
|
})),
|
|
1321
1323
|
...limited.length < filtered.length && {
|
|
1322
1324
|
hint: `Showing ${limited.length} of ${filtered.length} connections. Use --search <query> to filter by platform or --limit <n> to see more.`
|
|
@@ -1712,23 +1714,542 @@ function colorMethod(method) {
|
|
|
1712
1714
|
// src/commands/flow.ts
|
|
1713
1715
|
import pc7 from "picocolors";
|
|
1714
1716
|
|
|
1717
|
+
// src/lib/flow-schema.ts
|
|
1718
|
+
var FLOW_SCHEMA = {
|
|
1719
|
+
errorStrategies: ["fail", "continue", "retry", "fallback"],
|
|
1720
|
+
validInputTypes: ["string", "number", "boolean", "object", "array"],
|
|
1721
|
+
flowFields: {
|
|
1722
|
+
key: { type: "string", required: true, description: "Unique kebab-case identifier", pattern: /^[a-z0-9][a-z0-9-]*[a-z0-9]$/ },
|
|
1723
|
+
name: { type: "string", required: true, description: "Human-readable flow name" },
|
|
1724
|
+
description: { type: "string", required: false, description: "What this flow does" },
|
|
1725
|
+
version: { type: "string", required: false, description: "Semver or arbitrary version string" },
|
|
1726
|
+
inputs: { type: "object", required: true, description: "Input declarations (Record<string, InputDeclaration>)" },
|
|
1727
|
+
steps: { type: "array", required: true, description: "Ordered array of steps", stepsArray: true }
|
|
1728
|
+
},
|
|
1729
|
+
inputFields: {
|
|
1730
|
+
type: { type: "string", required: true, description: "Data type: string, number, boolean, object, array", enum: ["string", "number", "boolean", "object", "array"] },
|
|
1731
|
+
required: { type: "boolean", required: false, description: "Whether this input must be provided" },
|
|
1732
|
+
default: { type: "unknown", required: false, description: "Default value if not provided" },
|
|
1733
|
+
description: { type: "string", required: false, description: "Human-readable description" },
|
|
1734
|
+
connection: { type: "object", required: false, description: 'Connection metadata: { platform: "gmail" } \u2014 enables auto-resolution' }
|
|
1735
|
+
},
|
|
1736
|
+
stepCommonFields: {
|
|
1737
|
+
id: { type: "string", required: true, description: "Unique step identifier (used in selectors)" },
|
|
1738
|
+
name: { type: "string", required: true, description: "Human-readable step label" },
|
|
1739
|
+
type: { type: "string", required: true, description: "Step type (determines which config object is required)" },
|
|
1740
|
+
if: { type: "string", required: false, description: "JS expression \u2014 skip step if falsy" },
|
|
1741
|
+
unless: { type: "string", required: false, description: "JS expression \u2014 skip step if truthy" }
|
|
1742
|
+
},
|
|
1743
|
+
stepTypes: [
|
|
1744
|
+
{
|
|
1745
|
+
type: "action",
|
|
1746
|
+
configKey: "action",
|
|
1747
|
+
description: "Execute a platform API action",
|
|
1748
|
+
fields: {
|
|
1749
|
+
platform: { type: "string", required: true, description: "Platform name (kebab-case)" },
|
|
1750
|
+
actionId: { type: "string", required: true, description: "Action ID from `actions search`" },
|
|
1751
|
+
connectionKey: { type: "string", required: true, description: "Connection key (use $.input selector)" },
|
|
1752
|
+
data: { type: "object", required: false, description: "Request body (POST/PUT/PATCH)" },
|
|
1753
|
+
pathVars: { type: "object", required: false, description: "URL path variables" },
|
|
1754
|
+
queryParams: { type: "object", required: false, description: "Query parameters" },
|
|
1755
|
+
headers: { type: "object", required: false, description: "Additional headers" }
|
|
1756
|
+
},
|
|
1757
|
+
example: {
|
|
1758
|
+
id: "findCustomer",
|
|
1759
|
+
name: "Search Stripe customers",
|
|
1760
|
+
type: "action",
|
|
1761
|
+
action: {
|
|
1762
|
+
platform: "stripe",
|
|
1763
|
+
actionId: "conn_mod_def::xxx::yyy",
|
|
1764
|
+
connectionKey: "$.input.stripeConnectionKey",
|
|
1765
|
+
data: { query: "email:'{{$.input.customerEmail}}'" }
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
},
|
|
1769
|
+
{
|
|
1770
|
+
type: "transform",
|
|
1771
|
+
configKey: "transform",
|
|
1772
|
+
description: "Single JS expression with implicit return",
|
|
1773
|
+
fields: {
|
|
1774
|
+
expression: { type: "string", required: true, description: "JS expression evaluated with flow context as $" }
|
|
1775
|
+
},
|
|
1776
|
+
example: {
|
|
1777
|
+
id: "extractNames",
|
|
1778
|
+
name: "Extract customer names",
|
|
1779
|
+
type: "transform",
|
|
1780
|
+
transform: { expression: "$.steps.findCustomer.response.data.map(c => c.name)" }
|
|
1781
|
+
}
|
|
1782
|
+
},
|
|
1783
|
+
{
|
|
1784
|
+
type: "code",
|
|
1785
|
+
configKey: "code",
|
|
1786
|
+
description: "Multi-line async JS with explicit return",
|
|
1787
|
+
fields: {
|
|
1788
|
+
source: { type: "string", required: true, description: "JS function body (flow context as $, supports await)" }
|
|
1789
|
+
},
|
|
1790
|
+
example: {
|
|
1791
|
+
id: "processData",
|
|
1792
|
+
name: "Process and enrich data",
|
|
1793
|
+
type: "code",
|
|
1794
|
+
code: { source: "const items = $.steps.fetch.response.data;\nreturn items.filter(i => i.active);" }
|
|
1795
|
+
}
|
|
1796
|
+
},
|
|
1797
|
+
{
|
|
1798
|
+
type: "condition",
|
|
1799
|
+
configKey: "condition",
|
|
1800
|
+
description: "If/then/else branching",
|
|
1801
|
+
fields: {
|
|
1802
|
+
expression: { type: "string", required: true, description: "JS expression \u2014 truthy runs then, falsy runs else" },
|
|
1803
|
+
then: { type: "array", required: true, description: "Steps to run when true", stepsArray: true },
|
|
1804
|
+
else: { type: "array", required: false, description: "Steps to run when false", stepsArray: true }
|
|
1805
|
+
},
|
|
1806
|
+
example: {
|
|
1807
|
+
id: "checkFound",
|
|
1808
|
+
name: "Check if customer exists",
|
|
1809
|
+
type: "condition",
|
|
1810
|
+
condition: {
|
|
1811
|
+
expression: "$.steps.search.response.data.length > 0",
|
|
1812
|
+
then: [{ id: "notify", name: "Send notification", type: "action", action: { platform: "slack", actionId: "...", connectionKey: "$.input.slackKey", data: { text: "Found!" } } }],
|
|
1813
|
+
else: [{ id: "logMiss", name: "Log not found", type: "transform", transform: { expression: "'Not found'" } }]
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
},
|
|
1817
|
+
{
|
|
1818
|
+
type: "loop",
|
|
1819
|
+
configKey: "loop",
|
|
1820
|
+
description: "Iterate over an array with optional concurrency",
|
|
1821
|
+
fields: {
|
|
1822
|
+
over: { type: "string", required: true, description: "Selector resolving to an array" },
|
|
1823
|
+
as: { type: "string", required: true, description: "Variable name for current item ($.loop.<as>)" },
|
|
1824
|
+
indexAs: { type: "string", required: false, description: "Variable name for index" },
|
|
1825
|
+
steps: { type: "array", required: true, description: "Steps to run per iteration", stepsArray: true },
|
|
1826
|
+
maxIterations: { type: "number", required: false, description: "Safety cap (default: no limit)" },
|
|
1827
|
+
maxConcurrency: { type: "number", required: false, description: "Parallel batch size (default: 1 = sequential)" }
|
|
1828
|
+
},
|
|
1829
|
+
example: {
|
|
1830
|
+
id: "processOrders",
|
|
1831
|
+
name: "Process each order",
|
|
1832
|
+
type: "loop",
|
|
1833
|
+
loop: {
|
|
1834
|
+
over: "$.steps.listOrders.response.data",
|
|
1835
|
+
as: "order",
|
|
1836
|
+
steps: [{ id: "createInvoice", name: "Create invoice", type: "action", action: { platform: "stripe", actionId: "...", connectionKey: "$.input.stripeKey", data: { amount: "$.loop.order.total" } } }]
|
|
1837
|
+
}
|
|
1838
|
+
}
|
|
1839
|
+
},
|
|
1840
|
+
{
|
|
1841
|
+
type: "parallel",
|
|
1842
|
+
configKey: "parallel",
|
|
1843
|
+
description: "Run steps concurrently",
|
|
1844
|
+
fields: {
|
|
1845
|
+
steps: { type: "array", required: true, description: "Steps to run in parallel", stepsArray: true },
|
|
1846
|
+
maxConcurrency: { type: "number", required: false, description: "Max concurrent steps (default: 5)" }
|
|
1847
|
+
},
|
|
1848
|
+
example: {
|
|
1849
|
+
id: "lookups",
|
|
1850
|
+
name: "Parallel data lookups",
|
|
1851
|
+
type: "parallel",
|
|
1852
|
+
parallel: {
|
|
1853
|
+
steps: [
|
|
1854
|
+
{ id: "getStripe", name: "Get Stripe data", type: "action", action: { platform: "stripe", actionId: "...", connectionKey: "$.input.stripeKey" } },
|
|
1855
|
+
{ id: "getSlack", name: "Get Slack data", type: "action", action: { platform: "slack", actionId: "...", connectionKey: "$.input.slackKey" } }
|
|
1856
|
+
]
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
},
|
|
1860
|
+
{
|
|
1861
|
+
type: "file-read",
|
|
1862
|
+
configKey: "fileRead",
|
|
1863
|
+
description: "Read a file (optional JSON parse)",
|
|
1864
|
+
fields: {
|
|
1865
|
+
path: { type: "string", required: true, description: "File path to read" },
|
|
1866
|
+
parseJson: { type: "boolean", required: false, description: "Parse contents as JSON (default: false)" }
|
|
1867
|
+
},
|
|
1868
|
+
example: {
|
|
1869
|
+
id: "readConfig",
|
|
1870
|
+
name: "Read config file",
|
|
1871
|
+
type: "file-read",
|
|
1872
|
+
fileRead: { path: "./data/config.json", parseJson: true }
|
|
1873
|
+
}
|
|
1874
|
+
},
|
|
1875
|
+
{
|
|
1876
|
+
type: "file-write",
|
|
1877
|
+
configKey: "fileWrite",
|
|
1878
|
+
description: "Write or append to a file",
|
|
1879
|
+
fields: {
|
|
1880
|
+
path: { type: "string", required: true, description: "File path to write" },
|
|
1881
|
+
content: { type: "unknown", required: true, description: "Content to write (supports selectors)" },
|
|
1882
|
+
append: { type: "boolean", required: false, description: "Append instead of overwrite (default: false)" }
|
|
1883
|
+
},
|
|
1884
|
+
example: {
|
|
1885
|
+
id: "writeResults",
|
|
1886
|
+
name: "Save results",
|
|
1887
|
+
type: "file-write",
|
|
1888
|
+
fileWrite: { path: "./output/results.json", content: "$.steps.transform.output" }
|
|
1889
|
+
}
|
|
1890
|
+
},
|
|
1891
|
+
{
|
|
1892
|
+
type: "while",
|
|
1893
|
+
configKey: "while",
|
|
1894
|
+
description: "Do-while loop with condition check",
|
|
1895
|
+
fields: {
|
|
1896
|
+
condition: { type: "string", required: true, description: "JS expression checked before each iteration (after first)" },
|
|
1897
|
+
steps: { type: "array", required: true, description: "Steps to run each iteration", stepsArray: true },
|
|
1898
|
+
maxIterations: { type: "number", required: false, description: "Safety cap (default: 100)" }
|
|
1899
|
+
},
|
|
1900
|
+
example: {
|
|
1901
|
+
id: "paginate",
|
|
1902
|
+
name: "Paginate through pages",
|
|
1903
|
+
type: "while",
|
|
1904
|
+
while: {
|
|
1905
|
+
condition: "$.steps.paginate.output.lastResult.nextPageToken != null",
|
|
1906
|
+
maxIterations: 50,
|
|
1907
|
+
steps: [{ id: "fetchPage", name: "Fetch next page", type: "action", action: { platform: "gmail", actionId: "...", connectionKey: "$.input.gmailKey" } }]
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
},
|
|
1911
|
+
{
|
|
1912
|
+
type: "flow",
|
|
1913
|
+
configKey: "flow",
|
|
1914
|
+
description: "Execute a sub-flow (supports composition)",
|
|
1915
|
+
fields: {
|
|
1916
|
+
key: { type: "string", required: true, description: "Flow key or path of the sub-flow" },
|
|
1917
|
+
inputs: { type: "object", required: false, description: "Inputs to pass to the sub-flow (supports selectors)" }
|
|
1918
|
+
},
|
|
1919
|
+
example: {
|
|
1920
|
+
id: "enrich",
|
|
1921
|
+
name: "Run enrichment sub-flow",
|
|
1922
|
+
type: "flow",
|
|
1923
|
+
flow: { key: "enrich-customer", inputs: { email: "$.steps.getCustomer.response.email" } }
|
|
1924
|
+
}
|
|
1925
|
+
},
|
|
1926
|
+
{
|
|
1927
|
+
type: "paginate",
|
|
1928
|
+
configKey: "paginate",
|
|
1929
|
+
description: "Auto-paginate API results into a single array",
|
|
1930
|
+
fields: {
|
|
1931
|
+
action: { type: "object", required: true, description: "Action config (same shape as action step: platform, actionId, connectionKey)" },
|
|
1932
|
+
pageTokenField: { type: "string", required: true, description: "Dot-path in response to next page token" },
|
|
1933
|
+
resultsField: { type: "string", required: true, description: "Dot-path in response to results array" },
|
|
1934
|
+
inputTokenParam: { type: "string", required: true, description: "Dot-path in action config where page token is injected" },
|
|
1935
|
+
maxPages: { type: "number", required: false, description: "Max pages to fetch (default: 10)" }
|
|
1936
|
+
},
|
|
1937
|
+
example: {
|
|
1938
|
+
id: "allMessages",
|
|
1939
|
+
name: "Fetch all Gmail messages",
|
|
1940
|
+
type: "paginate",
|
|
1941
|
+
paginate: {
|
|
1942
|
+
action: { platform: "gmail", actionId: "...", connectionKey: "$.input.gmailKey", queryParams: { maxResults: 100 } },
|
|
1943
|
+
pageTokenField: "nextPageToken",
|
|
1944
|
+
resultsField: "messages",
|
|
1945
|
+
inputTokenParam: "queryParams.pageToken",
|
|
1946
|
+
maxPages: 10
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
},
|
|
1950
|
+
{
|
|
1951
|
+
type: "bash",
|
|
1952
|
+
configKey: "bash",
|
|
1953
|
+
description: "Shell command (requires --allow-bash)",
|
|
1954
|
+
fields: {
|
|
1955
|
+
command: { type: "string", required: true, description: "Shell command to execute (supports selectors)" },
|
|
1956
|
+
timeout: { type: "number", required: false, description: "Timeout in ms (default: 30000)" },
|
|
1957
|
+
parseJson: { type: "boolean", required: false, description: "Parse stdout as JSON (default: false)" },
|
|
1958
|
+
cwd: { type: "string", required: false, description: "Working directory (supports selectors)" },
|
|
1959
|
+
env: { type: "object", required: false, description: "Additional environment variables" }
|
|
1960
|
+
},
|
|
1961
|
+
example: {
|
|
1962
|
+
id: "analyze",
|
|
1963
|
+
name: "Analyze with Claude",
|
|
1964
|
+
type: "bash",
|
|
1965
|
+
bash: {
|
|
1966
|
+
command: "cat /tmp/data.json | claude --print 'Analyze this data' --output-format json",
|
|
1967
|
+
timeout: 18e4,
|
|
1968
|
+
parseJson: true
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
}
|
|
1972
|
+
]
|
|
1973
|
+
};
|
|
1974
|
+
var _coveredTypes = Object.fromEntries(
|
|
1975
|
+
FLOW_SCHEMA.stepTypes.map((st) => [st.type, true])
|
|
1976
|
+
);
|
|
1977
|
+
var _stepTypeMap = new Map(
|
|
1978
|
+
FLOW_SCHEMA.stepTypes.map((st) => [st.type, st])
|
|
1979
|
+
);
|
|
1980
|
+
function getStepTypeDescriptor(type) {
|
|
1981
|
+
return _stepTypeMap.get(type);
|
|
1982
|
+
}
|
|
1983
|
+
function getValidStepTypes() {
|
|
1984
|
+
return FLOW_SCHEMA.stepTypes.map((st) => st.type);
|
|
1985
|
+
}
|
|
1986
|
+
function getNestedStepsKeys() {
|
|
1987
|
+
const result = [];
|
|
1988
|
+
for (const st of FLOW_SCHEMA.stepTypes) {
|
|
1989
|
+
for (const [fieldName, fd] of Object.entries(st.fields)) {
|
|
1990
|
+
if (fd.stepsArray) {
|
|
1991
|
+
result.push({ configKey: st.configKey, fieldName });
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
return result;
|
|
1996
|
+
}
|
|
1997
|
+
function generateFlowGuide() {
|
|
1998
|
+
const validTypes = getValidStepTypes();
|
|
1999
|
+
const sections = [];
|
|
2000
|
+
sections.push(`# One Flows \u2014 Reference
|
|
2001
|
+
|
|
2002
|
+
## Overview
|
|
2003
|
+
|
|
2004
|
+
Workflows are JSON files at \`.one/flows/<key>.flow.json\` that chain actions across platforms.
|
|
2005
|
+
|
|
2006
|
+
## Commands
|
|
2007
|
+
|
|
2008
|
+
\`\`\`bash
|
|
2009
|
+
one --agent flow create <key> --definition '<json>' # Create (or --definition @file.json)
|
|
2010
|
+
one --agent flow create <key> --definition @flow.json # Create from file
|
|
2011
|
+
one --agent flow list # List
|
|
2012
|
+
one --agent flow validate <key> # Validate
|
|
2013
|
+
one --agent flow execute <key> -i name=value # Execute
|
|
2014
|
+
one --agent flow execute <key> --dry-run --mock # Test with mock data
|
|
2015
|
+
one --agent flow execute <key> --allow-bash # Enable bash steps
|
|
2016
|
+
one --agent flow runs [flowKey] # List past runs
|
|
2017
|
+
one --agent flow resume <runId> # Resume failed run
|
|
2018
|
+
one --agent flow scaffold [template] # Generate a starter template
|
|
2019
|
+
\`\`\`
|
|
2020
|
+
|
|
2021
|
+
You can also write the JSON file directly to \`.one/flows/<key>.flow.json\` \u2014 this is often easier than passing large JSON via --definition.
|
|
2022
|
+
|
|
2023
|
+
## Building a Workflow
|
|
2024
|
+
|
|
2025
|
+
1. **Design first** \u2014 clarify the end goal, map the full value chain, identify where AI analysis is needed
|
|
2026
|
+
2. **Discover connections** \u2014 \`one --agent connection list\`
|
|
2027
|
+
3. **Get knowledge** for every action \u2014 \`one --agent actions knowledge <platform> <actionId>\`
|
|
2028
|
+
4. **Construct JSON** \u2014 declare inputs, wire steps with selectors
|
|
2029
|
+
5. **Validate** \u2014 \`one --agent flow validate <key>\`
|
|
2030
|
+
6. **Execute** \u2014 \`one --agent flow execute <key> -i param=value\``);
|
|
2031
|
+
sections.push(`## Flow JSON Schema
|
|
2032
|
+
|
|
2033
|
+
\`\`\`json
|
|
2034
|
+
{
|
|
2035
|
+
"key": "my-workflow",
|
|
2036
|
+
"name": "My Workflow",
|
|
2037
|
+
"description": "What this flow does",
|
|
2038
|
+
"version": "1",
|
|
2039
|
+
"inputs": {
|
|
2040
|
+
"connectionKey": {
|
|
2041
|
+
"type": "string",
|
|
2042
|
+
"required": true,
|
|
2043
|
+
"description": "Platform connection key",
|
|
2044
|
+
"connection": { "platform": "stripe" }
|
|
2045
|
+
},
|
|
2046
|
+
"param": {
|
|
2047
|
+
"type": "string",
|
|
2048
|
+
"required": true,
|
|
2049
|
+
"description": "A user parameter"
|
|
2050
|
+
}
|
|
2051
|
+
},
|
|
2052
|
+
"steps": [
|
|
2053
|
+
{
|
|
2054
|
+
"id": "stepId",
|
|
2055
|
+
"name": "Human-readable step name",
|
|
2056
|
+
"type": "action",
|
|
2057
|
+
"action": {
|
|
2058
|
+
"platform": "stripe",
|
|
2059
|
+
"actionId": "conn_mod_def::xxx::yyy",
|
|
2060
|
+
"connectionKey": "$.input.connectionKey",
|
|
2061
|
+
"data": { "query": "{{$.input.param}}" }
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
]
|
|
2065
|
+
}
|
|
2066
|
+
\`\`\`
|
|
2067
|
+
|
|
2068
|
+
### Top-level fields
|
|
2069
|
+
|
|
2070
|
+
| Field | Type | Required | Description |
|
|
2071
|
+
|-------|------|----------|-------------|`);
|
|
2072
|
+
for (const [name, fd] of Object.entries(FLOW_SCHEMA.flowFields)) {
|
|
2073
|
+
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2074
|
+
}
|
|
2075
|
+
sections.push(`
|
|
2076
|
+
### Input declarations
|
|
2077
|
+
|
|
2078
|
+
| Field | Type | Required | Description |
|
|
2079
|
+
|-------|------|----------|-------------|`);
|
|
2080
|
+
for (const [name, fd] of Object.entries(FLOW_SCHEMA.inputFields)) {
|
|
2081
|
+
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2082
|
+
}
|
|
2083
|
+
sections.push(`
|
|
2084
|
+
### Step fields (all steps)
|
|
2085
|
+
|
|
2086
|
+
Every step MUST have \`id\`, \`name\`, and \`type\`. The \`type\` determines which config object is required.
|
|
2087
|
+
|
|
2088
|
+
| Field | Type | Required | Description |
|
|
2089
|
+
|-------|------|----------|-------------|`);
|
|
2090
|
+
for (const [name, fd] of Object.entries(FLOW_SCHEMA.stepCommonFields)) {
|
|
2091
|
+
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2092
|
+
}
|
|
2093
|
+
sections.push(`| \`onError\` | object | no | Error handling: \`{ "strategy": "${FLOW_SCHEMA.errorStrategies.join(" | ")}", "retries": 3, "retryDelayMs": 1000 }\` |`);
|
|
2094
|
+
sections.push(`
|
|
2095
|
+
## Step Types
|
|
2096
|
+
|
|
2097
|
+
**IMPORTANT:** Each step type requires a config object nested under a specific key. The type name and config key differ for some types (noted below).
|
|
2098
|
+
|
|
2099
|
+
| Type | Config Key | Description |
|
|
2100
|
+
|------|-----------|-------------|`);
|
|
2101
|
+
for (const st of FLOW_SCHEMA.stepTypes) {
|
|
2102
|
+
const keyNote = st.type !== st.configKey ? ` \u26A0\uFE0F` : "";
|
|
2103
|
+
sections.push(`| \`${st.type}\` | \`${st.configKey}\`${keyNote} | ${st.description} |`);
|
|
2104
|
+
}
|
|
2105
|
+
sections.push(`
|
|
2106
|
+
## Step Type Reference`);
|
|
2107
|
+
for (const st of FLOW_SCHEMA.stepTypes) {
|
|
2108
|
+
sections.push(`
|
|
2109
|
+
### \`${st.type}\` \u2014 ${st.description}`);
|
|
2110
|
+
if (st.type !== st.configKey) {
|
|
2111
|
+
sections.push(`
|
|
2112
|
+
> **Note:** Type is \`"${st.type}"\` but config key is \`"${st.configKey}"\` (camelCase).`);
|
|
2113
|
+
}
|
|
2114
|
+
sections.push(`
|
|
2115
|
+
| Field | Type | Required | Description |
|
|
2116
|
+
|-------|------|----------|-------------|`);
|
|
2117
|
+
for (const [name, fd] of Object.entries(st.fields)) {
|
|
2118
|
+
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2119
|
+
}
|
|
2120
|
+
sections.push(`
|
|
2121
|
+
\`\`\`json
|
|
2122
|
+
${JSON.stringify(st.example, null, 2)}
|
|
2123
|
+
\`\`\``);
|
|
2124
|
+
}
|
|
2125
|
+
sections.push(`
|
|
2126
|
+
## Selectors
|
|
2127
|
+
|
|
2128
|
+
| Pattern | Resolves To |
|
|
2129
|
+
|---------|-------------|
|
|
2130
|
+
| \`$.input.paramName\` | Input value |
|
|
2131
|
+
| \`$.steps.stepId.response\` | Full API response |
|
|
2132
|
+
| \`$.steps.stepId.response.data[0].email\` | Nested field |
|
|
2133
|
+
| \`$.steps.stepId.response.data[*].id\` | Wildcard array map |
|
|
2134
|
+
| \`$.env.MY_VAR\` | Environment variable |
|
|
2135
|
+
| \`$.loop.item\` / \`$.loop.i\` | Loop iteration |
|
|
2136
|
+
| \`"Hello {{$.steps.getUser.response.name}}"\` | String interpolation |
|
|
2137
|
+
|
|
2138
|
+
### When to use bare selectors vs \`{{...}}\` interpolation
|
|
2139
|
+
|
|
2140
|
+
- **Bare selectors** (\`$.input.x\`): Use for fields the engine resolves directly \u2014 \`connectionKey\`, \`over\`, \`path\`, \`expression\`, \`condition\`, and any field where the entire value is a single selector. The resolved value keeps its original type (object, array, number).
|
|
2141
|
+
- **Interpolation** (\`{{$.input.x}}\`): Use inside string values where the selector is embedded in text \u2014 e.g., \`"Hello {{$.steps.getUser.response.name}}"\`. The resolved value is always stringified. Use this in \`data\`, \`pathVars\`, and \`queryParams\` when mixing selectors with literal text.
|
|
2142
|
+
- **Rule of thumb**: If the value is purely a selector, use bare. If it's a string containing a selector, use \`{{...}}\`.
|
|
2143
|
+
|
|
2144
|
+
### \`output\` vs \`response\` on step results
|
|
2145
|
+
|
|
2146
|
+
Every completed step produces both \`output\` and \`response\`:
|
|
2147
|
+
- **Action steps**: \`response\` is the raw API response. \`output\` is the same as \`response\`.
|
|
2148
|
+
- **Code/transform steps**: \`output\` is the return value. \`response\` is an alias for \`output\`.
|
|
2149
|
+
- **In practice**: Use \`$.steps.stepId.response\` for action steps (API data) and \`$.steps.stepId.output\` for code/transform steps (computed data). Both work interchangeably, but using the semantically correct one makes flows easier to read.
|
|
2150
|
+
|
|
2151
|
+
## Error Handling
|
|
2152
|
+
|
|
2153
|
+
\`\`\`json
|
|
2154
|
+
{"onError": {"strategy": "retry", "retries": 3, "retryDelayMs": 1000}}
|
|
2155
|
+
\`\`\`
|
|
2156
|
+
|
|
2157
|
+
Strategies: \`${FLOW_SCHEMA.errorStrategies.join("`, `")}\`
|
|
2158
|
+
|
|
2159
|
+
Conditional execution: \`"if": "$.steps.prev.response.data.length > 0"\`
|
|
2160
|
+
|
|
2161
|
+
## Input Connection Auto-Resolution
|
|
2162
|
+
|
|
2163
|
+
When an input has \`"connection": { "platform": "stripe" }\`, the flow engine can automatically resolve the connection key at execution time. If the user has exactly one connection for that platform, the engine fills in the key without requiring \`-i connectionKey=...\`. If multiple connections exist, the user must specify which one. This is metadata for tooling \u2014 it does not affect the flow JSON structure, but it makes execution more convenient.
|
|
2164
|
+
|
|
2165
|
+
## Complete Example: Fetch Data, Transform, Notify
|
|
2166
|
+
|
|
2167
|
+
\`\`\`json
|
|
2168
|
+
{
|
|
2169
|
+
"key": "contacts-to-slack",
|
|
2170
|
+
"name": "CRM Contacts Summary to Slack",
|
|
2171
|
+
"description": "Fetch recent contacts from CRM, build a summary, post to Slack",
|
|
2172
|
+
"version": "1",
|
|
2173
|
+
"inputs": {
|
|
2174
|
+
"crmConnectionKey": {
|
|
2175
|
+
"type": "string",
|
|
2176
|
+
"required": true,
|
|
2177
|
+
"description": "CRM platform connection key",
|
|
2178
|
+
"connection": { "platform": "attio" }
|
|
2179
|
+
},
|
|
2180
|
+
"slackConnectionKey": {
|
|
2181
|
+
"type": "string",
|
|
2182
|
+
"required": true,
|
|
2183
|
+
"description": "Slack connection key",
|
|
2184
|
+
"connection": { "platform": "slack" }
|
|
2185
|
+
},
|
|
2186
|
+
"slackChannel": {
|
|
2187
|
+
"type": "string",
|
|
2188
|
+
"required": true,
|
|
2189
|
+
"description": "Slack channel name or ID"
|
|
2190
|
+
}
|
|
2191
|
+
},
|
|
2192
|
+
"steps": [
|
|
2193
|
+
{
|
|
2194
|
+
"id": "fetchContacts",
|
|
2195
|
+
"name": "Fetch recent contacts",
|
|
2196
|
+
"type": "action",
|
|
2197
|
+
"action": {
|
|
2198
|
+
"platform": "attio",
|
|
2199
|
+
"actionId": "ATTIO_LIST_PEOPLE_ACTION_ID",
|
|
2200
|
+
"connectionKey": "$.input.crmConnectionKey",
|
|
2201
|
+
"queryParams": { "limit": "10" }
|
|
2202
|
+
}
|
|
2203
|
+
},
|
|
2204
|
+
{
|
|
2205
|
+
"id": "buildSummary",
|
|
2206
|
+
"name": "Build formatted summary",
|
|
2207
|
+
"type": "code",
|
|
2208
|
+
"code": {
|
|
2209
|
+
"source": "const contacts = $.steps.fetchContacts.response.data || [];\\nconst lines = contacts.map((c, i) => \`\${i+1}. \${c.name || 'Unknown'} \u2014 \${c.email || 'no email'}\`);\\nreturn { summary: \`Found \${contacts.length} contacts:\\n\${lines.join('\\n')}\` };"
|
|
2210
|
+
}
|
|
2211
|
+
},
|
|
2212
|
+
{
|
|
2213
|
+
"id": "notifySlack",
|
|
2214
|
+
"name": "Post summary to Slack",
|
|
2215
|
+
"type": "action",
|
|
2216
|
+
"action": {
|
|
2217
|
+
"platform": "slack",
|
|
2218
|
+
"actionId": "SLACK_SEND_MESSAGE_ACTION_ID",
|
|
2219
|
+
"connectionKey": "$.input.slackConnectionKey",
|
|
2220
|
+
"data": {
|
|
2221
|
+
"channel": "$.input.slackChannel",
|
|
2222
|
+
"text": "{{$.steps.buildSummary.output.summary}}"
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
]
|
|
2227
|
+
}
|
|
2228
|
+
\`\`\`
|
|
2229
|
+
|
|
2230
|
+
Note: Action IDs above are placeholders. Always use \`one --agent actions search <platform> "<query>"\` to find real IDs.
|
|
2231
|
+
|
|
2232
|
+
## AI-Augmented Pattern
|
|
2233
|
+
|
|
2234
|
+
For workflows that need analysis/summarization, use the file-write \u2192 bash \u2192 code pattern:
|
|
2235
|
+
|
|
2236
|
+
1. \`file-write\` \u2014 save data to temp file
|
|
2237
|
+
2. \`bash\` \u2014 \`claude --print\` analyzes it (\`parseJson: true\`, \`timeout: 180000\`)
|
|
2238
|
+
3. \`code\` \u2014 parse and structure the output
|
|
2239
|
+
|
|
2240
|
+
Set timeout to at least 180000ms (3 min). Run Claude-heavy flows sequentially, not in parallel.
|
|
2241
|
+
|
|
2242
|
+
## Notes
|
|
2243
|
+
|
|
2244
|
+
- Connection keys are **inputs**, not hardcoded
|
|
2245
|
+
- Action IDs in examples are placeholders \u2014 always use \`actions search\`
|
|
2246
|
+
- Code steps allow \`crypto\`, \`buffer\`, \`url\`, \`path\` \u2014 \`fs\`, \`http\`, \`child_process\` are blocked
|
|
2247
|
+
- Bash steps require \`--allow-bash\` flag
|
|
2248
|
+
- State is persisted after every step \u2014 resume picks up where it left off`);
|
|
2249
|
+
return sections.join("\n");
|
|
2250
|
+
}
|
|
2251
|
+
|
|
1715
2252
|
// src/lib/flow-validator.ts
|
|
1716
|
-
var VALID_STEP_TYPES = [
|
|
1717
|
-
"action",
|
|
1718
|
-
"transform",
|
|
1719
|
-
"code",
|
|
1720
|
-
"condition",
|
|
1721
|
-
"loop",
|
|
1722
|
-
"parallel",
|
|
1723
|
-
"file-read",
|
|
1724
|
-
"file-write",
|
|
1725
|
-
"while",
|
|
1726
|
-
"flow",
|
|
1727
|
-
"paginate",
|
|
1728
|
-
"bash"
|
|
1729
|
-
];
|
|
1730
|
-
var VALID_INPUT_TYPES = ["string", "number", "boolean", "object", "array"];
|
|
1731
|
-
var VALID_ERROR_STRATEGIES = ["fail", "continue", "retry", "fallback"];
|
|
1732
2253
|
function validateFlowSchema(flow2) {
|
|
1733
2254
|
const errors = [];
|
|
1734
2255
|
if (!flow2 || typeof flow2 !== "object") {
|
|
@@ -1738,7 +2259,7 @@ function validateFlowSchema(flow2) {
|
|
|
1738
2259
|
const f = flow2;
|
|
1739
2260
|
if (!f.key || typeof f.key !== "string") {
|
|
1740
2261
|
errors.push({ path: "key", message: 'Flow must have a string "key"' });
|
|
1741
|
-
} else if (
|
|
2262
|
+
} else if (FLOW_SCHEMA.flowFields.key.pattern && !FLOW_SCHEMA.flowFields.key.pattern.test(f.key) && f.key.length > 1) {
|
|
1742
2263
|
errors.push({ path: "key", message: "Flow key must be kebab-case (lowercase letters, numbers, hyphens)" });
|
|
1743
2264
|
}
|
|
1744
2265
|
if (!f.name || typeof f.name !== "string") {
|
|
@@ -1761,8 +2282,8 @@ function validateFlowSchema(flow2) {
|
|
|
1761
2282
|
continue;
|
|
1762
2283
|
}
|
|
1763
2284
|
const d = decl;
|
|
1764
|
-
if (!d.type || !
|
|
1765
|
-
errors.push({ path: `${prefix}.type`, message: `Input type must be one of: ${
|
|
2285
|
+
if (!d.type || !FLOW_SCHEMA.validInputTypes.includes(d.type)) {
|
|
2286
|
+
errors.push({ path: `${prefix}.type`, message: `Input type must be one of: ${FLOW_SCHEMA.validInputTypes.join(", ")}` });
|
|
1766
2287
|
}
|
|
1767
2288
|
if (d.connection !== void 0) {
|
|
1768
2289
|
if (!d.connection || typeof d.connection !== "object") {
|
|
@@ -1784,6 +2305,7 @@ function validateFlowSchema(flow2) {
|
|
|
1784
2305
|
return errors;
|
|
1785
2306
|
}
|
|
1786
2307
|
function validateStepsArray(steps, pathPrefix, errors) {
|
|
2308
|
+
const validTypes = FLOW_SCHEMA.stepTypes.map((st) => st.type);
|
|
1787
2309
|
for (let i = 0; i < steps.length; i++) {
|
|
1788
2310
|
const step = steps[i];
|
|
1789
2311
|
const path4 = `${pathPrefix}[${i}]`;
|
|
@@ -1798,189 +2320,79 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
1798
2320
|
if (!s.name || typeof s.name !== "string") {
|
|
1799
2321
|
errors.push({ path: `${path4}.name`, message: 'Step must have a string "name"' });
|
|
1800
2322
|
}
|
|
1801
|
-
if (!s.type || !
|
|
1802
|
-
errors.push({ path: `${path4}.type`, message: `Step type must be one of: ${
|
|
2323
|
+
if (!s.type || !validTypes.includes(s.type)) {
|
|
2324
|
+
errors.push({ path: `${path4}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
|
|
2325
|
+
continue;
|
|
1803
2326
|
}
|
|
1804
2327
|
if (s.onError && typeof s.onError === "object") {
|
|
1805
2328
|
const oe = s.onError;
|
|
1806
|
-
if (!
|
|
1807
|
-
errors.push({ path: `${path4}.onError.strategy`, message: `Error strategy must be one of: ${
|
|
2329
|
+
if (!FLOW_SCHEMA.errorStrategies.includes(oe.strategy)) {
|
|
2330
|
+
errors.push({ path: `${path4}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
|
|
1808
2331
|
}
|
|
1809
2332
|
}
|
|
1810
|
-
const
|
|
1811
|
-
if (
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
}
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
} else if (type === "code") {
|
|
1830
|
-
if (!s.code || typeof s.code !== "object") {
|
|
1831
|
-
errors.push({ path: `${path4}.code`, message: 'Code step must have a "code" config object' });
|
|
1832
|
-
} else {
|
|
1833
|
-
const c = s.code;
|
|
1834
|
-
if (!c.source || typeof c.source !== "string") {
|
|
1835
|
-
errors.push({ path: `${path4}.code.source`, message: 'Code must have a string "source"' });
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
} else if (type === "condition") {
|
|
1839
|
-
if (!s.condition || typeof s.condition !== "object") {
|
|
1840
|
-
errors.push({ path: `${path4}.condition`, message: 'Condition step must have a "condition" config object' });
|
|
1841
|
-
} else {
|
|
1842
|
-
const c = s.condition;
|
|
1843
|
-
if (!c.expression || typeof c.expression !== "string") {
|
|
1844
|
-
errors.push({ path: `${path4}.condition.expression`, message: 'Condition must have a string "expression"' });
|
|
1845
|
-
}
|
|
1846
|
-
if (!Array.isArray(c.then)) {
|
|
1847
|
-
errors.push({ path: `${path4}.condition.then`, message: 'Condition must have a "then" steps array' });
|
|
1848
|
-
} else {
|
|
1849
|
-
validateStepsArray(c.then, `${path4}.condition.then`, errors);
|
|
1850
|
-
}
|
|
1851
|
-
if (c.else !== void 0) {
|
|
1852
|
-
if (!Array.isArray(c.else)) {
|
|
1853
|
-
errors.push({ path: `${path4}.condition.else`, message: 'Condition "else" must be a steps array' });
|
|
1854
|
-
} else {
|
|
1855
|
-
validateStepsArray(c.else, `${path4}.condition.else`, errors);
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
} else if (type === "loop") {
|
|
1860
|
-
if (!s.loop || typeof s.loop !== "object") {
|
|
1861
|
-
errors.push({ path: `${path4}.loop`, message: 'Loop step must have a "loop" config object' });
|
|
1862
|
-
} else {
|
|
1863
|
-
const l = s.loop;
|
|
1864
|
-
if (!l.over || typeof l.over !== "string") {
|
|
1865
|
-
errors.push({ path: `${path4}.loop.over`, message: 'Loop must have a string "over" selector' });
|
|
1866
|
-
}
|
|
1867
|
-
if (!l.as || typeof l.as !== "string") {
|
|
1868
|
-
errors.push({ path: `${path4}.loop.as`, message: 'Loop must have a string "as" variable name' });
|
|
1869
|
-
}
|
|
1870
|
-
if (!Array.isArray(l.steps)) {
|
|
1871
|
-
errors.push({ path: `${path4}.loop.steps`, message: 'Loop must have a "steps" array' });
|
|
1872
|
-
} else {
|
|
1873
|
-
validateStepsArray(l.steps, `${path4}.loop.steps`, errors);
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
} else if (type === "parallel") {
|
|
1877
|
-
if (!s.parallel || typeof s.parallel !== "object") {
|
|
1878
|
-
errors.push({ path: `${path4}.parallel`, message: 'Parallel step must have a "parallel" config object' });
|
|
1879
|
-
} else {
|
|
1880
|
-
const par = s.parallel;
|
|
1881
|
-
if (!Array.isArray(par.steps)) {
|
|
1882
|
-
errors.push({ path: `${path4}.parallel.steps`, message: 'Parallel must have a "steps" array' });
|
|
1883
|
-
} else {
|
|
1884
|
-
validateStepsArray(par.steps, `${path4}.parallel.steps`, errors);
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
} else if (type === "file-read") {
|
|
1888
|
-
if (!s.fileRead || typeof s.fileRead !== "object") {
|
|
1889
|
-
errors.push({ path: `${path4}.fileRead`, message: 'File-read step must have a "fileRead" config object' });
|
|
1890
|
-
} else {
|
|
1891
|
-
const fr = s.fileRead;
|
|
1892
|
-
if (!fr.path || typeof fr.path !== "string") {
|
|
1893
|
-
errors.push({ path: `${path4}.fileRead.path`, message: 'File-read must have a string "path"' });
|
|
1894
|
-
}
|
|
2333
|
+
const descriptor = getStepTypeDescriptor(s.type);
|
|
2334
|
+
if (!descriptor) continue;
|
|
2335
|
+
const configKey = descriptor.configKey;
|
|
2336
|
+
const configObj = s[configKey];
|
|
2337
|
+
if (!configObj || typeof configObj !== "object") {
|
|
2338
|
+
const hint = detectFlatConfigHint(s, descriptor);
|
|
2339
|
+
errors.push({
|
|
2340
|
+
path: `${path4}.${configKey}`,
|
|
2341
|
+
message: `${capitalize(descriptor.type)} step must have a "${configKey}" config object${hint}`
|
|
2342
|
+
});
|
|
2343
|
+
continue;
|
|
2344
|
+
}
|
|
2345
|
+
const config = configObj;
|
|
2346
|
+
for (const [fieldName, fd] of Object.entries(descriptor.fields)) {
|
|
2347
|
+
const fieldPath = `${path4}.${configKey}.${fieldName}`;
|
|
2348
|
+
const value = config[fieldName];
|
|
2349
|
+
if (fd.required && (value === void 0 || value === null || value === "")) {
|
|
2350
|
+
errors.push({ path: fieldPath, message: `${capitalize(descriptor.type)} must have ${fd.type === "string" ? "a string" : fd.type === "array" ? "a" : "a"} "${fieldName}"` });
|
|
2351
|
+
continue;
|
|
1895
2352
|
}
|
|
1896
|
-
|
|
1897
|
-
if (
|
|
1898
|
-
errors.push({ path:
|
|
1899
|
-
} else {
|
|
1900
|
-
const fw = s.fileWrite;
|
|
1901
|
-
if (!fw.path || typeof fw.path !== "string") {
|
|
1902
|
-
errors.push({ path: `${path4}.fileWrite.path`, message: 'File-write must have a string "path"' });
|
|
1903
|
-
}
|
|
1904
|
-
if (fw.content === void 0) {
|
|
1905
|
-
errors.push({ path: `${path4}.fileWrite.content`, message: 'File-write must have "content"' });
|
|
1906
|
-
}
|
|
2353
|
+
if (value === void 0) continue;
|
|
2354
|
+
if (fd.type === "string" && fd.required && typeof value !== "string") {
|
|
2355
|
+
errors.push({ path: fieldPath, message: `"${fieldName}" must be a string` });
|
|
1907
2356
|
}
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
errors.push({ path: `${path4}.while`, message: 'While step must have a "while" config object' });
|
|
1911
|
-
} else {
|
|
1912
|
-
const w = s.while;
|
|
1913
|
-
if (!w.condition || typeof w.condition !== "string") {
|
|
1914
|
-
errors.push({ path: `${path4}.while.condition`, message: 'While must have a string "condition"' });
|
|
1915
|
-
}
|
|
1916
|
-
if (!Array.isArray(w.steps)) {
|
|
1917
|
-
errors.push({ path: `${path4}.while.steps`, message: 'While must have a "steps" array' });
|
|
1918
|
-
} else {
|
|
1919
|
-
validateStepsArray(w.steps, `${path4}.while.steps`, errors);
|
|
1920
|
-
}
|
|
1921
|
-
if (w.maxIterations !== void 0 && (typeof w.maxIterations !== "number" || w.maxIterations <= 0)) {
|
|
1922
|
-
errors.push({ path: `${path4}.while.maxIterations`, message: "maxIterations must be a positive number" });
|
|
1923
|
-
}
|
|
2357
|
+
if (fd.type === "number" && value !== void 0 && (typeof value !== "number" || value <= 0)) {
|
|
2358
|
+
errors.push({ path: fieldPath, message: `${fieldName} must be a positive number` });
|
|
1924
2359
|
}
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
errors.push({ path: `${path4}.flow`, message: 'Flow step must have a "flow" config object' });
|
|
1928
|
-
} else {
|
|
1929
|
-
const f = s.flow;
|
|
1930
|
-
if (!f.key || typeof f.key !== "string") {
|
|
1931
|
-
errors.push({ path: `${path4}.flow.key`, message: 'Flow must have a string "key"' });
|
|
1932
|
-
}
|
|
1933
|
-
if (f.inputs !== void 0 && (typeof f.inputs !== "object" || Array.isArray(f.inputs))) {
|
|
1934
|
-
errors.push({ path: `${path4}.flow.inputs`, message: "Flow inputs must be an object" });
|
|
1935
|
-
}
|
|
2360
|
+
if (fd.type === "boolean" && value !== void 0 && typeof value !== "boolean") {
|
|
2361
|
+
errors.push({ path: fieldPath, message: `${fieldName} must be a boolean` });
|
|
1936
2362
|
}
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
} else {
|
|
1941
|
-
const p7 = s.paginate;
|
|
1942
|
-
if (!p7.action || typeof p7.action !== "object") {
|
|
1943
|
-
errors.push({ path: `${path4}.paginate.action`, message: 'Paginate must have an "action" config object' });
|
|
2363
|
+
if (fd.stepsArray) {
|
|
2364
|
+
if (!Array.isArray(value)) {
|
|
2365
|
+
errors.push({ path: fieldPath, message: `"${fieldName}" must be a steps array` });
|
|
1944
2366
|
} else {
|
|
1945
|
-
|
|
1946
|
-
if (!a.platform) errors.push({ path: `${path4}.paginate.action.platform`, message: 'Action must have "platform"' });
|
|
1947
|
-
if (!a.actionId) errors.push({ path: `${path4}.paginate.action.actionId`, message: 'Action must have "actionId"' });
|
|
1948
|
-
if (!a.connectionKey) errors.push({ path: `${path4}.paginate.action.connectionKey`, message: 'Action must have "connectionKey"' });
|
|
1949
|
-
}
|
|
1950
|
-
if (!p7.pageTokenField || typeof p7.pageTokenField !== "string") {
|
|
1951
|
-
errors.push({ path: `${path4}.paginate.pageTokenField`, message: 'Paginate must have a string "pageTokenField"' });
|
|
1952
|
-
}
|
|
1953
|
-
if (!p7.resultsField || typeof p7.resultsField !== "string") {
|
|
1954
|
-
errors.push({ path: `${path4}.paginate.resultsField`, message: 'Paginate must have a string "resultsField"' });
|
|
1955
|
-
}
|
|
1956
|
-
if (!p7.inputTokenParam || typeof p7.inputTokenParam !== "string") {
|
|
1957
|
-
errors.push({ path: `${path4}.paginate.inputTokenParam`, message: 'Paginate must have a string "inputTokenParam"' });
|
|
1958
|
-
}
|
|
1959
|
-
if (p7.maxPages !== void 0 && (typeof p7.maxPages !== "number" || p7.maxPages <= 0)) {
|
|
1960
|
-
errors.push({ path: `${path4}.paginate.maxPages`, message: "maxPages must be a positive number" });
|
|
2367
|
+
validateStepsArray(value, fieldPath, errors);
|
|
1961
2368
|
}
|
|
1962
2369
|
}
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
errors.push({ path: `${path4}.bash.command`, message: 'Bash must have a string "command"' });
|
|
1970
|
-
}
|
|
1971
|
-
if (b.timeout !== void 0 && (typeof b.timeout !== "number" || b.timeout <= 0)) {
|
|
1972
|
-
errors.push({ path: `${path4}.bash.timeout`, message: "timeout must be a positive number" });
|
|
1973
|
-
}
|
|
1974
|
-
if (b.parseJson !== void 0 && typeof b.parseJson !== "boolean") {
|
|
1975
|
-
errors.push({ path: `${path4}.bash.parseJson`, message: "parseJson must be a boolean" });
|
|
2370
|
+
if (descriptor.type === "paginate" && fieldName === "action") {
|
|
2371
|
+
if (typeof value === "object" && value !== null) {
|
|
2372
|
+
const a = value;
|
|
2373
|
+
if (!a.platform) errors.push({ path: `${fieldPath}.platform`, message: 'Action must have "platform"' });
|
|
2374
|
+
if (!a.actionId) errors.push({ path: `${fieldPath}.actionId`, message: 'Action must have "actionId"' });
|
|
2375
|
+
if (!a.connectionKey) errors.push({ path: `${fieldPath}.connectionKey`, message: 'Action must have "connectionKey"' });
|
|
1976
2376
|
}
|
|
1977
2377
|
}
|
|
1978
2378
|
}
|
|
1979
2379
|
}
|
|
1980
2380
|
}
|
|
2381
|
+
function detectFlatConfigHint(step, descriptor) {
|
|
2382
|
+
const requiredFields = Object.entries(descriptor.fields).filter(([, fd]) => fd.required).map(([name]) => name);
|
|
2383
|
+
const flatFields = requiredFields.filter((f) => f in step);
|
|
2384
|
+
if (flatFields.length > 0) {
|
|
2385
|
+
return `. Hint: "${flatFields.join('", "')}" must be nested inside "${descriptor.configKey}": { ... }, not placed directly on the step`;
|
|
2386
|
+
}
|
|
2387
|
+
return "";
|
|
2388
|
+
}
|
|
2389
|
+
function capitalize(s) {
|
|
2390
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
2391
|
+
}
|
|
1981
2392
|
function validateStepIds(flow2) {
|
|
1982
2393
|
const errors = [];
|
|
1983
2394
|
const seen = /* @__PURE__ */ new Set();
|
|
2395
|
+
const nestedKeys = getNestedStepsKeys();
|
|
1984
2396
|
function collectIds(steps, pathPrefix) {
|
|
1985
2397
|
for (let i = 0; i < steps.length; i++) {
|
|
1986
2398
|
const step = steps[i];
|
|
@@ -1990,13 +2402,12 @@ function validateStepIds(flow2) {
|
|
|
1990
2402
|
} else {
|
|
1991
2403
|
seen.add(step.id);
|
|
1992
2404
|
}
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
if (
|
|
2405
|
+
for (const { configKey, fieldName } of nestedKeys) {
|
|
2406
|
+
const config = step[configKey];
|
|
2407
|
+
if (config && Array.isArray(config[fieldName])) {
|
|
2408
|
+
collectIds(config[fieldName], `${path4}.${configKey}.${fieldName}`);
|
|
2409
|
+
}
|
|
1996
2410
|
}
|
|
1997
|
-
if (step.loop?.steps) collectIds(step.loop.steps, `${path4}.loop.steps`);
|
|
1998
|
-
if (step.parallel?.steps) collectIds(step.parallel.steps, `${path4}.parallel.steps`);
|
|
1999
|
-
if (step.while?.steps) collectIds(step.while.steps, `${path4}.while.steps`);
|
|
2000
2411
|
}
|
|
2001
2412
|
}
|
|
2002
2413
|
collectIds(flow2.steps, "steps");
|
|
@@ -2005,25 +2416,17 @@ function validateStepIds(flow2) {
|
|
|
2005
2416
|
function validateSelectorReferences(flow2) {
|
|
2006
2417
|
const errors = [];
|
|
2007
2418
|
const inputNames = new Set(Object.keys(flow2.inputs));
|
|
2419
|
+
const nestedKeys = getNestedStepsKeys();
|
|
2008
2420
|
function getAllStepIds(steps) {
|
|
2009
2421
|
const ids = /* @__PURE__ */ new Set();
|
|
2010
2422
|
for (const step of steps) {
|
|
2011
2423
|
ids.add(step.id);
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
if (
|
|
2015
|
-
for (const id of getAllStepIds(
|
|
2424
|
+
for (const { configKey, fieldName } of nestedKeys) {
|
|
2425
|
+
const config = step[configKey];
|
|
2426
|
+
if (config && Array.isArray(config[fieldName])) {
|
|
2427
|
+
for (const id of getAllStepIds(config[fieldName])) ids.add(id);
|
|
2016
2428
|
}
|
|
2017
2429
|
}
|
|
2018
|
-
if (step.loop?.steps) {
|
|
2019
|
-
for (const id of getAllStepIds(step.loop.steps)) ids.add(id);
|
|
2020
|
-
}
|
|
2021
|
-
if (step.parallel?.steps) {
|
|
2022
|
-
for (const id of getAllStepIds(step.parallel.steps)) ids.add(id);
|
|
2023
|
-
}
|
|
2024
|
-
if (step.while?.steps) {
|
|
2025
|
-
for (const id of getAllStepIds(step.while.steps)) ids.add(id);
|
|
2026
|
-
}
|
|
2027
2430
|
}
|
|
2028
2431
|
return ids;
|
|
2029
2432
|
}
|
|
@@ -2070,49 +2473,29 @@ function validateSelectorReferences(flow2) {
|
|
|
2070
2473
|
function checkStep(step, pathPrefix) {
|
|
2071
2474
|
if (step.if) checkSelectors(extractSelectors(step.if), `${pathPrefix}.if`);
|
|
2072
2475
|
if (step.unless) checkSelectors(extractSelectors(step.unless), `${pathPrefix}.unless`);
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
}
|
|
2087
|
-
if (step.parallel) {
|
|
2088
|
-
step.parallel.steps.forEach((s, i) => checkStep(s, `${pathPrefix}.parallel.steps[${i}]`));
|
|
2089
|
-
}
|
|
2090
|
-
if (step.fileRead) {
|
|
2091
|
-
checkSelectors(extractSelectors(step.fileRead.path), `${pathPrefix}.fileRead.path`);
|
|
2092
|
-
}
|
|
2093
|
-
if (step.fileWrite) {
|
|
2094
|
-
checkSelectors(extractSelectors(step.fileWrite.path), `${pathPrefix}.fileWrite.path`);
|
|
2095
|
-
checkSelectors(extractSelectors(step.fileWrite.content), `${pathPrefix}.fileWrite.content`);
|
|
2096
|
-
}
|
|
2097
|
-
if (step.while) {
|
|
2098
|
-
step.while.steps.forEach((s, i) => checkStep(s, `${pathPrefix}.while.steps[${i}]`));
|
|
2099
|
-
}
|
|
2100
|
-
if (step.flow) {
|
|
2101
|
-
checkSelectors(extractSelectors(step.flow.key), `${pathPrefix}.flow.key`);
|
|
2102
|
-
if (step.flow.inputs) {
|
|
2103
|
-
checkSelectors(extractSelectors(step.flow.inputs), `${pathPrefix}.flow.inputs`);
|
|
2104
|
-
}
|
|
2105
|
-
}
|
|
2106
|
-
if (step.paginate) {
|
|
2107
|
-
checkSelectors(extractSelectors(step.paginate.action), `${pathPrefix}.paginate.action`);
|
|
2108
|
-
}
|
|
2109
|
-
if (step.bash) {
|
|
2110
|
-
checkSelectors(extractSelectors(step.bash.command), `${pathPrefix}.bash.command`);
|
|
2111
|
-
if (step.bash.cwd) {
|
|
2112
|
-
checkSelectors(extractSelectors(step.bash.cwd), `${pathPrefix}.bash.cwd`);
|
|
2476
|
+
const descriptor = getStepTypeDescriptor(step.type);
|
|
2477
|
+
if (descriptor) {
|
|
2478
|
+
const config = step[descriptor.configKey];
|
|
2479
|
+
if (config && typeof config === "object") {
|
|
2480
|
+
if (step.type !== "transform" && step.type !== "code") {
|
|
2481
|
+
for (const [fieldName, fd] of Object.entries(descriptor.fields)) {
|
|
2482
|
+
if (fd.stepsArray) continue;
|
|
2483
|
+
const value = config[fieldName];
|
|
2484
|
+
if (value !== void 0) {
|
|
2485
|
+
checkSelectors(extractSelectors(value), `${pathPrefix}.${descriptor.configKey}.${fieldName}`);
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2113
2489
|
}
|
|
2114
|
-
|
|
2115
|
-
|
|
2490
|
+
for (const { configKey, fieldName } of nestedKeys) {
|
|
2491
|
+
if (configKey === descriptor.configKey) {
|
|
2492
|
+
const c = step[configKey];
|
|
2493
|
+
if (c && Array.isArray(c[fieldName])) {
|
|
2494
|
+
c[fieldName].forEach(
|
|
2495
|
+
(s, i) => checkStep(s, `${pathPrefix}.${configKey}.${fieldName}[${i}]`)
|
|
2496
|
+
);
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2116
2499
|
}
|
|
2117
2500
|
}
|
|
2118
2501
|
}
|
|
@@ -2184,10 +2567,19 @@ async function flowCreateCommand(key, options) {
|
|
|
2184
2567
|
intro2(pc7.bgCyan(pc7.black(" One Workflow ")));
|
|
2185
2568
|
let flow2;
|
|
2186
2569
|
if (options.definition) {
|
|
2570
|
+
let raw = options.definition;
|
|
2571
|
+
if (raw.startsWith("@")) {
|
|
2572
|
+
const filePath = raw.slice(1);
|
|
2573
|
+
try {
|
|
2574
|
+
raw = fs3.readFileSync(filePath, "utf-8");
|
|
2575
|
+
} catch (err) {
|
|
2576
|
+
error(`Cannot read file "${filePath}": ${err.message}`);
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2187
2579
|
try {
|
|
2188
|
-
flow2 = JSON.parse(
|
|
2580
|
+
flow2 = JSON.parse(raw);
|
|
2189
2581
|
} catch {
|
|
2190
|
-
error("Invalid JSON in --definition");
|
|
2582
|
+
error("Invalid JSON in --definition. If your JSON contains special characters (like :: in action IDs), try --definition @file.json instead.");
|
|
2191
2583
|
}
|
|
2192
2584
|
} else if (!process.stdin.isTTY) {
|
|
2193
2585
|
const chunks = [];
|
|
@@ -2494,909 +2886,791 @@ function colorStatus(status) {
|
|
|
2494
2886
|
return status;
|
|
2495
2887
|
}
|
|
2496
2888
|
}
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
## The --agent Flag
|
|
2511
|
-
|
|
2512
|
-
Always use \`--agent\` for machine-readable JSON output. It disables colors, spinners, and interactive prompts.
|
|
2513
|
-
|
|
2514
|
-
\`\`\`bash
|
|
2515
|
-
one --agent <command>
|
|
2516
|
-
\`\`\`
|
|
2517
|
-
|
|
2518
|
-
All commands return JSON. If an \`error\` key is present, the command failed.
|
|
2519
|
-
|
|
2520
|
-
## Topics
|
|
2521
|
-
|
|
2522
|
-
This guide has three sections you can request individually:
|
|
2523
|
-
|
|
2524
|
-
- **overview** \u2014 This section. Setup, flag usage, and discovery workflow.
|
|
2525
|
-
- **actions** \u2014 Full workflow for searching, reading docs, and executing platform actions.
|
|
2526
|
-
- **workflows** \u2014 Building and executing multi-step API workflows (JSON-based).
|
|
2527
|
-
|
|
2528
|
-
## Discovery Workflow
|
|
2529
|
-
|
|
2530
|
-
1. \`one --agent connection list\` \u2014 See connected platforms and connection keys
|
|
2531
|
-
2. \`one --agent actions search <platform> <query>\` \u2014 Find actions
|
|
2532
|
-
3. \`one --agent actions knowledge <platform> <actionId>\` \u2014 Read full docs (REQUIRED before execute)
|
|
2533
|
-
4. \`one --agent actions execute <platform> <actionId> <connectionKey>\` \u2014 Execute the action
|
|
2534
|
-
|
|
2535
|
-
For multi-step workflows:
|
|
2536
|
-
1. Discover actions with the workflow above
|
|
2537
|
-
2. Build a workflow JSON definition
|
|
2538
|
-
3. \`one --agent flow create <key> --definition '<json>'\`
|
|
2539
|
-
4. \`one --agent flow execute <key> -i param=value\`
|
|
2540
|
-
|
|
2541
|
-
Platform names are always kebab-case (e.g., \`hub-spot\`, \`google-calendar\`).
|
|
2542
|
-
Run \`one platforms\` to browse all 200+ available platforms.
|
|
2543
|
-
`;
|
|
2544
|
-
var GUIDE_ACTIONS = `# One Actions CLI Workflow
|
|
2545
|
-
|
|
2546
|
-
You have access to the One CLI which lets you interact with 200+ third-party platforms through their APIs. The CLI handles authentication, request building, and execution through One's passthrough proxy.
|
|
2547
|
-
|
|
2548
|
-
## The Workflow
|
|
2549
|
-
|
|
2550
|
-
Always follow this sequence \u2014 each step builds on the previous one:
|
|
2551
|
-
|
|
2552
|
-
1. **List connections** to see what platforms the user has connected
|
|
2553
|
-
2. **Search actions** to find the right API action for what the user wants to do
|
|
2554
|
-
3. **Get knowledge** to understand the action's parameters, requirements, and structure
|
|
2555
|
-
4. **Execute** the action with the correct parameters
|
|
2556
|
-
|
|
2557
|
-
Never skip the knowledge step before executing \u2014 it contains critical information about required parameters, validation rules, and request structure that you need to build a correct request.
|
|
2558
|
-
|
|
2559
|
-
## Commands
|
|
2560
|
-
|
|
2561
|
-
### 1. List Connections
|
|
2562
|
-
|
|
2563
|
-
\`\`\`bash
|
|
2564
|
-
one --agent connection list
|
|
2565
|
-
\`\`\`
|
|
2566
|
-
|
|
2567
|
-
Returns JSON with all connected platforms, their status, and connection keys. You need the **connection key** for executing actions, and the **platform name** (kebab-case) for searching actions.
|
|
2568
|
-
|
|
2569
|
-
Output format:
|
|
2570
|
-
\`\`\`json
|
|
2571
|
-
{"connections": [{"platform": "gmail", "state": "active", "key": "conn_abc123"}, ...]}
|
|
2572
|
-
\`\`\`
|
|
2573
|
-
|
|
2574
|
-
### 2. Search Actions
|
|
2575
|
-
|
|
2576
|
-
\`\`\`bash
|
|
2577
|
-
one --agent actions search <platform> <query>
|
|
2578
|
-
\`\`\`
|
|
2579
|
-
|
|
2580
|
-
Search for actions on a specific platform using natural language. Returns JSON with up to 5 matching actions including their action IDs, HTTP methods, and paths.
|
|
2581
|
-
|
|
2582
|
-
- \`<platform>\` \u2014 Platform name in kebab-case exactly as shown in the connections list (e.g., \`gmail\`, \`shopify\`, \`hub-spot\`)
|
|
2583
|
-
- \`<query>\` \u2014 Natural language description of what you want to do (e.g., \`"send email"\`, \`"list contacts"\`, \`"create order"\`)
|
|
2584
|
-
|
|
2585
|
-
Options:
|
|
2586
|
-
- \`-t, --type <execute|knowledge>\` \u2014 Use \`execute\` when the user wants to perform an action, \`knowledge\` when they want documentation or want to write code. Defaults to \`knowledge\`.
|
|
2587
|
-
|
|
2588
|
-
Example:
|
|
2589
|
-
\`\`\`bash
|
|
2590
|
-
one --agent actions search gmail "send email" -t execute
|
|
2591
|
-
\`\`\`
|
|
2592
|
-
|
|
2593
|
-
Output format:
|
|
2594
|
-
\`\`\`json
|
|
2595
|
-
{"actions": [{"_id": "abc123", "title": "Send Email", "tags": [...], "method": "POST", "path": "/messages/send"}, ...]}
|
|
2596
|
-
\`\`\`
|
|
2597
|
-
|
|
2598
|
-
### 3. Get Action Knowledge
|
|
2599
|
-
|
|
2600
|
-
\`\`\`bash
|
|
2601
|
-
one --agent actions knowledge <platform> <actionId>
|
|
2602
|
-
\`\`\`
|
|
2603
|
-
|
|
2604
|
-
Get comprehensive documentation for an action including parameters, requirements, validation rules, request/response structure, and examples. Returns JSON with the full API knowledge and HTTP method.
|
|
2605
|
-
|
|
2606
|
-
Always call this before executing \u2014 it tells you exactly what parameters are required and how to structure the request.
|
|
2607
|
-
|
|
2608
|
-
Example:
|
|
2609
|
-
\`\`\`bash
|
|
2610
|
-
one --agent actions knowledge gmail 67890abcdef
|
|
2611
|
-
\`\`\`
|
|
2612
|
-
|
|
2613
|
-
Output format:
|
|
2614
|
-
\`\`\`json
|
|
2615
|
-
{"knowledge": "...full API documentation and guidance...", "method": "POST"}
|
|
2616
|
-
\`\`\`
|
|
2617
|
-
|
|
2618
|
-
### 4. Execute Action
|
|
2619
|
-
|
|
2620
|
-
\`\`\`bash
|
|
2621
|
-
one --agent actions execute <platform> <actionId> <connectionKey> [options]
|
|
2622
|
-
\`\`\`
|
|
2623
|
-
|
|
2624
|
-
Execute an action on a connected platform. Returns JSON with the request details and response data. You must have retrieved the knowledge for this action first.
|
|
2625
|
-
|
|
2626
|
-
- \`<platform>\` \u2014 Platform name in kebab-case
|
|
2627
|
-
- \`<actionId>\` \u2014 Action ID from the search results
|
|
2628
|
-
- \`<connectionKey>\` \u2014 Connection key from \`one connection list\`
|
|
2629
|
-
|
|
2630
|
-
Options:
|
|
2631
|
-
- \`-d, --data <json>\` \u2014 Request body as JSON string (for POST, PUT, PATCH)
|
|
2632
|
-
- \`--path-vars <json>\` \u2014 Path variables as JSON (for URLs with \`{id}\` placeholders)
|
|
2633
|
-
- \`--query-params <json>\` \u2014 Query parameters as JSON
|
|
2634
|
-
- \`--headers <json>\` \u2014 Additional headers as JSON
|
|
2635
|
-
- \`--form-data\` \u2014 Send as multipart/form-data instead of JSON
|
|
2636
|
-
- \`--form-url-encoded\` \u2014 Send as application/x-www-form-urlencoded
|
|
2637
|
-
- \`--dry-run\` \u2014 Show the request that would be sent without executing it
|
|
2638
|
-
|
|
2639
|
-
Examples:
|
|
2640
|
-
\`\`\`bash
|
|
2641
|
-
# Simple GET request
|
|
2642
|
-
one --agent actions execute shopify <actionId> <connectionKey>
|
|
2643
|
-
|
|
2644
|
-
# POST with data
|
|
2645
|
-
one --agent actions execute hub-spot <actionId> <connectionKey> \\
|
|
2646
|
-
-d '{"properties": {"email": "jane@example.com", "firstname": "Jane"}}'
|
|
2647
|
-
|
|
2648
|
-
# With path variables and query params
|
|
2649
|
-
one --agent actions execute shopify <actionId> <connectionKey> \\
|
|
2650
|
-
--path-vars '{"order_id": "12345"}' \\
|
|
2651
|
-
--query-params '{"limit": "10"}'
|
|
2652
|
-
\`\`\`
|
|
2653
|
-
|
|
2654
|
-
Output format:
|
|
2655
|
-
\`\`\`json
|
|
2656
|
-
{"request": {"method": "POST", "url": "https://..."}, "response": {...}}
|
|
2657
|
-
\`\`\`
|
|
2658
|
-
|
|
2659
|
-
## Error Handling
|
|
2660
|
-
|
|
2661
|
-
All errors return JSON in agent mode:
|
|
2662
|
-
\`\`\`json
|
|
2663
|
-
{"error": "Error message here"}
|
|
2664
|
-
\`\`\`
|
|
2665
|
-
|
|
2666
|
-
Parse the output as JSON. If the \`error\` key is present, the command failed \u2014 report the error message to the user.
|
|
2667
|
-
|
|
2668
|
-
## Important Notes
|
|
2669
|
-
|
|
2670
|
-
- **Always use \`--agent\` flag** \u2014 it produces structured JSON output without spinners, colors, or interactive prompts
|
|
2671
|
-
- Platform names are always **kebab-case** (e.g., \`hub-spot\` not \`HubSpot\`, \`ship-station\` not \`ShipStation\`)
|
|
2672
|
-
- Always use the **exact action ID** from search results \u2014 don't guess or construct them
|
|
2673
|
-
- Always read the knowledge output carefully \u2014 it tells you which parameters are required vs optional, what format they need to be in, and any caveats specific to that API
|
|
2674
|
-
- JSON values passed to \`-d\`, \`--path-vars\`, \`--query-params\`, and \`--headers\` must be valid JSON strings (use single quotes around the JSON to avoid shell escaping issues)
|
|
2675
|
-
- If search returns no results, try broader queries (e.g., \`"list"\` instead of \`"list active premium customers"\`)
|
|
2676
|
-
- The execute command respects access control settings configured via \`one config\` \u2014 if execution is blocked, the user may need to adjust their permissions
|
|
2677
|
-
`;
|
|
2678
|
-
var GUIDE_FLOWS = `# One Workflows \u2014 Multi-Step API Workflows
|
|
2679
|
-
|
|
2680
|
-
You have access to the One CLI's workflow engine, which lets you create and execute multi-step API workflows as JSON files. Workflows chain actions across platforms \u2014 e.g., look up a Stripe customer, then send them a welcome email via Gmail.
|
|
2681
|
-
|
|
2682
|
-
## 1. Overview
|
|
2683
|
-
|
|
2684
|
-
- Workflows are JSON files stored at \`.one/flows/<key>.flow.json\`
|
|
2685
|
-
- All dynamic values (including connection keys) are declared as **inputs**
|
|
2686
|
-
- Each workflow has a unique **key** used to reference and execute it
|
|
2687
|
-
- Executed via \`one --agent flow execute <key> -i name=value\`
|
|
2688
|
-
|
|
2689
|
-
## 2. Building a Workflow \u2014 Step-by-Step Process
|
|
2690
|
-
|
|
2691
|
-
**You MUST follow this process to build a correct workflow:**
|
|
2692
|
-
|
|
2693
|
-
### Step 1: Discover connections
|
|
2694
|
-
|
|
2695
|
-
\`\`\`bash
|
|
2696
|
-
one --agent connection list
|
|
2697
|
-
\`\`\`
|
|
2698
|
-
|
|
2699
|
-
Find out which platforms are connected and get their connection keys.
|
|
2700
|
-
|
|
2701
|
-
### Step 2: For EACH API action needed, get the knowledge
|
|
2702
|
-
|
|
2703
|
-
\`\`\`bash
|
|
2704
|
-
# Find the action ID
|
|
2705
|
-
one --agent actions search <platform> "<query>" -t execute
|
|
2706
|
-
|
|
2707
|
-
# Read the full docs \u2014 REQUIRED before adding to a workflow
|
|
2708
|
-
one --agent actions knowledge <platform> <actionId>
|
|
2709
|
-
\`\`\`
|
|
2710
|
-
|
|
2711
|
-
**CRITICAL:** You MUST call \`one actions knowledge\` for every action you include in the workflow. The knowledge output tells you the exact request body structure, required fields, path variables, and query parameters. Without this, your workflow JSON will have incorrect data shapes.
|
|
2712
|
-
|
|
2713
|
-
### Step 3: Construct the workflow JSON
|
|
2714
|
-
|
|
2715
|
-
Using the knowledge gathered, build the workflow JSON with:
|
|
2716
|
-
- All inputs declared (connection keys + user parameters)
|
|
2717
|
-
- Each step with the correct actionId, platform, and data structure (from knowledge)
|
|
2718
|
-
- Data wired between steps using \`$.input.*\` and \`$.steps.*\` selectors
|
|
2719
|
-
|
|
2720
|
-
### Step 4: Write the workflow file
|
|
2721
|
-
|
|
2722
|
-
\`\`\`bash
|
|
2723
|
-
one --agent flow create <key> --definition '<json>'
|
|
2724
|
-
\`\`\`
|
|
2725
|
-
|
|
2726
|
-
Or write directly to \`.one/flows/<key>.flow.json\`.
|
|
2727
|
-
|
|
2728
|
-
### Step 5: Validate
|
|
2729
|
-
|
|
2730
|
-
\`\`\`bash
|
|
2731
|
-
one --agent flow validate <key>
|
|
2732
|
-
\`\`\`
|
|
2733
|
-
|
|
2734
|
-
### Step 6: Execute
|
|
2735
|
-
|
|
2736
|
-
\`\`\`bash
|
|
2737
|
-
one --agent flow execute <key> -i connectionKey=xxx -i param=value
|
|
2738
|
-
\`\`\`
|
|
2739
|
-
|
|
2740
|
-
## 3. Workflow JSON Schema Reference
|
|
2741
|
-
|
|
2742
|
-
\`\`\`json
|
|
2743
|
-
{
|
|
2744
|
-
"key": "welcome-customer",
|
|
2745
|
-
"name": "Welcome New Customer",
|
|
2746
|
-
"description": "Look up a Stripe customer and send them a welcome email via Gmail",
|
|
2747
|
-
"version": "1",
|
|
2748
|
-
"inputs": {
|
|
2749
|
-
"stripeConnectionKey": {
|
|
2750
|
-
"type": "string",
|
|
2751
|
-
"required": true,
|
|
2752
|
-
"description": "Stripe connection key from one connection list",
|
|
2753
|
-
"connection": { "platform": "stripe" }
|
|
2889
|
+
var SCAFFOLD_TEMPLATES = {
|
|
2890
|
+
basic: () => ({
|
|
2891
|
+
key: "my-workflow",
|
|
2892
|
+
name: "My Workflow",
|
|
2893
|
+
description: "A basic workflow with a single action step",
|
|
2894
|
+
version: "1",
|
|
2895
|
+
inputs: {
|
|
2896
|
+
connectionKey: {
|
|
2897
|
+
type: "string",
|
|
2898
|
+
required: true,
|
|
2899
|
+
description: "Connection key for the platform",
|
|
2900
|
+
connection: { platform: "PLATFORM_NAME" }
|
|
2901
|
+
}
|
|
2754
2902
|
},
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2903
|
+
steps: [
|
|
2904
|
+
{
|
|
2905
|
+
id: "step1",
|
|
2906
|
+
name: "Execute action",
|
|
2907
|
+
type: "action",
|
|
2908
|
+
action: {
|
|
2909
|
+
platform: "PLATFORM_NAME",
|
|
2910
|
+
actionId: "ACTION_ID_FROM_SEARCH",
|
|
2911
|
+
connectionKey: "$.input.connectionKey",
|
|
2912
|
+
data: {}
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
]
|
|
2916
|
+
}),
|
|
2917
|
+
conditional: () => ({
|
|
2918
|
+
key: "my-conditional-workflow",
|
|
2919
|
+
name: "Conditional Workflow",
|
|
2920
|
+
description: "Fetch data, then branch based on results",
|
|
2921
|
+
version: "1",
|
|
2922
|
+
inputs: {
|
|
2923
|
+
connectionKey: {
|
|
2924
|
+
type: "string",
|
|
2925
|
+
required: true,
|
|
2926
|
+
description: "Connection key",
|
|
2927
|
+
connection: { platform: "PLATFORM_NAME" }
|
|
2928
|
+
}
|
|
2760
2929
|
},
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2930
|
+
steps: [
|
|
2931
|
+
{
|
|
2932
|
+
id: "fetch",
|
|
2933
|
+
name: "Fetch data",
|
|
2934
|
+
type: "action",
|
|
2935
|
+
action: {
|
|
2936
|
+
platform: "PLATFORM_NAME",
|
|
2937
|
+
actionId: "ACTION_ID_FROM_SEARCH",
|
|
2938
|
+
connectionKey: "$.input.connectionKey"
|
|
2939
|
+
}
|
|
2940
|
+
},
|
|
2941
|
+
{
|
|
2942
|
+
id: "decide",
|
|
2943
|
+
name: "Check results",
|
|
2944
|
+
type: "condition",
|
|
2945
|
+
condition: {
|
|
2946
|
+
expression: "$.steps.fetch.response.data && $.steps.fetch.response.data.length > 0",
|
|
2947
|
+
then: [
|
|
2948
|
+
{
|
|
2949
|
+
id: "handleFound",
|
|
2950
|
+
name: "Handle found",
|
|
2951
|
+
type: "transform",
|
|
2952
|
+
transform: { expression: "$.steps.fetch.response.data[0]" }
|
|
2953
|
+
}
|
|
2954
|
+
],
|
|
2955
|
+
else: [
|
|
2956
|
+
{
|
|
2957
|
+
id: "handleNotFound",
|
|
2958
|
+
name: "Handle not found",
|
|
2959
|
+
type: "transform",
|
|
2960
|
+
transform: { expression: "({ error: 'No results found' })" }
|
|
2961
|
+
}
|
|
2962
|
+
]
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
]
|
|
2966
|
+
}),
|
|
2967
|
+
loop: () => ({
|
|
2968
|
+
key: "my-loop-workflow",
|
|
2969
|
+
name: "Loop Workflow",
|
|
2970
|
+
description: "Fetch a list, then process each item",
|
|
2971
|
+
version: "1",
|
|
2972
|
+
inputs: {
|
|
2973
|
+
connectionKey: {
|
|
2974
|
+
type: "string",
|
|
2975
|
+
required: true,
|
|
2976
|
+
description: "Connection key",
|
|
2977
|
+
connection: { platform: "PLATFORM_NAME" }
|
|
2978
|
+
}
|
|
2979
|
+
},
|
|
2980
|
+
steps: [
|
|
2981
|
+
{
|
|
2982
|
+
id: "fetchList",
|
|
2983
|
+
name: "Fetch items",
|
|
2984
|
+
type: "action",
|
|
2985
|
+
action: {
|
|
2986
|
+
platform: "PLATFORM_NAME",
|
|
2987
|
+
actionId: "ACTION_ID_FROM_SEARCH",
|
|
2988
|
+
connectionKey: "$.input.connectionKey"
|
|
2989
|
+
}
|
|
2990
|
+
},
|
|
2991
|
+
{
|
|
2992
|
+
id: "processItems",
|
|
2993
|
+
name: "Process each item",
|
|
2994
|
+
type: "loop",
|
|
2995
|
+
loop: {
|
|
2996
|
+
over: "$.steps.fetchList.response.data",
|
|
2997
|
+
as: "item",
|
|
2998
|
+
steps: [
|
|
2999
|
+
{
|
|
3000
|
+
id: "processItem",
|
|
3001
|
+
name: "Process single item",
|
|
3002
|
+
type: "transform",
|
|
3003
|
+
transform: { expression: "({ id: $.loop.item.id, processed: true })" }
|
|
3004
|
+
}
|
|
3005
|
+
]
|
|
3006
|
+
}
|
|
3007
|
+
},
|
|
3008
|
+
{
|
|
3009
|
+
id: "summary",
|
|
3010
|
+
name: "Generate summary",
|
|
3011
|
+
type: "transform",
|
|
3012
|
+
transform: { expression: "({ total: $.steps.fetchList.response.data.length })" }
|
|
3013
|
+
}
|
|
3014
|
+
]
|
|
3015
|
+
}),
|
|
3016
|
+
ai: () => ({
|
|
3017
|
+
key: "my-ai-workflow",
|
|
3018
|
+
name: "AI Analysis Workflow",
|
|
3019
|
+
description: "Fetch data, analyze with Claude, and send results",
|
|
3020
|
+
version: "1",
|
|
3021
|
+
inputs: {
|
|
3022
|
+
connectionKey: {
|
|
3023
|
+
type: "string",
|
|
3024
|
+
required: true,
|
|
3025
|
+
description: "Connection key for data source",
|
|
3026
|
+
connection: { platform: "PLATFORM_NAME" }
|
|
3027
|
+
}
|
|
3028
|
+
},
|
|
3029
|
+
steps: [
|
|
3030
|
+
{
|
|
3031
|
+
id: "fetchData",
|
|
3032
|
+
name: "Fetch raw data",
|
|
3033
|
+
type: "action",
|
|
3034
|
+
action: {
|
|
3035
|
+
platform: "PLATFORM_NAME",
|
|
3036
|
+
actionId: "ACTION_ID_FROM_SEARCH",
|
|
3037
|
+
connectionKey: "$.input.connectionKey"
|
|
3038
|
+
}
|
|
3039
|
+
},
|
|
3040
|
+
{
|
|
3041
|
+
id: "writeData",
|
|
3042
|
+
name: "Write data for analysis",
|
|
3043
|
+
type: "file-write",
|
|
3044
|
+
fileWrite: {
|
|
3045
|
+
path: "/tmp/workflow-data.json",
|
|
3046
|
+
content: "$.steps.fetchData.response"
|
|
3047
|
+
}
|
|
3048
|
+
},
|
|
3049
|
+
{
|
|
3050
|
+
id: "analyze",
|
|
3051
|
+
name: "Analyze with Claude",
|
|
3052
|
+
type: "bash",
|
|
3053
|
+
bash: {
|
|
3054
|
+
command: `cat /tmp/workflow-data.json | claude --print 'Analyze this data and return JSON with: {"summary": "...", "insights": [...], "recommendations": [...]}. Return ONLY valid JSON.' --output-format json`,
|
|
3055
|
+
timeout: 18e4,
|
|
3056
|
+
parseJson: true
|
|
3057
|
+
}
|
|
3058
|
+
},
|
|
3059
|
+
{
|
|
3060
|
+
id: "formatResult",
|
|
3061
|
+
name: "Format analysis output",
|
|
3062
|
+
type: "code",
|
|
3063
|
+
code: {
|
|
3064
|
+
source: "const a = $.steps.analyze.output;\nreturn {\n summary: a.summary,\n insights: a.insights,\n recommendations: a.recommendations\n};"
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
]
|
|
3068
|
+
})
|
|
3069
|
+
};
|
|
3070
|
+
async function flowScaffoldCommand(template) {
|
|
3071
|
+
const templateName = template || "basic";
|
|
3072
|
+
const templateFn = SCAFFOLD_TEMPLATES[templateName];
|
|
3073
|
+
if (!templateFn) {
|
|
3074
|
+
const available = Object.keys(SCAFFOLD_TEMPLATES).join(", ");
|
|
3075
|
+
if (isAgentMode()) {
|
|
3076
|
+
json({ error: `Unknown template "${templateName}". Available: ${available}` });
|
|
3077
|
+
process.exit(1);
|
|
3078
|
+
}
|
|
3079
|
+
error(`Unknown template "${templateName}". Available: ${available}`);
|
|
3080
|
+
}
|
|
3081
|
+
const scaffold = templateFn();
|
|
3082
|
+
if (isAgentMode()) {
|
|
3083
|
+
json(scaffold);
|
|
3084
|
+
return;
|
|
3085
|
+
}
|
|
3086
|
+
console.log(JSON.stringify(scaffold, null, 2));
|
|
3087
|
+
}
|
|
3088
|
+
|
|
3089
|
+
// src/commands/relay.ts
|
|
3090
|
+
import pc8 from "picocolors";
|
|
3091
|
+
function getConfig3() {
|
|
3092
|
+
const apiKey = getApiKey();
|
|
3093
|
+
if (!apiKey) {
|
|
3094
|
+
error("Not configured. Run `one init` first.");
|
|
3095
|
+
}
|
|
3096
|
+
const ac = getAccessControlFromAllSources();
|
|
3097
|
+
const connectionKeys = ac.connectionKeys || ["*"];
|
|
3098
|
+
return { apiKey, connectionKeys };
|
|
3099
|
+
}
|
|
3100
|
+
function parseJsonArg2(value, argName) {
|
|
3101
|
+
try {
|
|
3102
|
+
return JSON.parse(value);
|
|
3103
|
+
} catch {
|
|
3104
|
+
error(`Invalid JSON for ${argName}: ${value}`);
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
async function relayCreateCommand(options) {
|
|
3108
|
+
const { apiKey, connectionKeys } = getConfig3();
|
|
3109
|
+
if (!connectionKeys.includes("*") && !connectionKeys.includes(options.connectionKey)) {
|
|
3110
|
+
error(`Connection key "${options.connectionKey}" is not allowed.`);
|
|
3111
|
+
}
|
|
3112
|
+
const api = new OneApi(apiKey);
|
|
3113
|
+
const spinner5 = createSpinner();
|
|
3114
|
+
spinner5.start("Creating relay endpoint...");
|
|
3115
|
+
try {
|
|
3116
|
+
const body = {
|
|
3117
|
+
connectionKey: options.connectionKey
|
|
3118
|
+
};
|
|
3119
|
+
if (options.description) body.description = options.description;
|
|
3120
|
+
if (options.eventFilters) body.eventFilters = parseJsonArg2(options.eventFilters, "--event-filters");
|
|
3121
|
+
if (options.tags) body.tags = parseJsonArg2(options.tags, "--tags");
|
|
3122
|
+
if (options.createWebhook) body.createWebhook = true;
|
|
3123
|
+
const result = await api.createRelayEndpoint(body);
|
|
3124
|
+
if (isAgentMode()) {
|
|
3125
|
+
json(result);
|
|
3126
|
+
return;
|
|
3127
|
+
}
|
|
3128
|
+
spinner5.stop("Relay endpoint created");
|
|
3129
|
+
console.log();
|
|
3130
|
+
console.log(` ${pc8.dim("ID:")} ${result.id}`);
|
|
3131
|
+
console.log(` ${pc8.dim("URL:")} ${result.url}`);
|
|
3132
|
+
console.log(` ${pc8.dim("Active:")} ${result.active}`);
|
|
3133
|
+
if (result.description) console.log(` ${pc8.dim("Description:")} ${result.description}`);
|
|
3134
|
+
if (result.eventFilters?.length) console.log(` ${pc8.dim("Events:")} ${result.eventFilters.join(", ")}`);
|
|
3135
|
+
if (result.webhookPayload?.id) console.log(` ${pc8.dim("Webhook ID:")} ${result.webhookPayload.id}`);
|
|
3136
|
+
console.log();
|
|
3137
|
+
} catch (error2) {
|
|
3138
|
+
spinner5.stop("Failed to create relay endpoint");
|
|
3139
|
+
error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
async function relayListCommand(options) {
|
|
3143
|
+
const { apiKey } = getConfig3();
|
|
3144
|
+
const api = new OneApi(apiKey);
|
|
3145
|
+
const spinner5 = createSpinner();
|
|
3146
|
+
spinner5.start("Loading relay endpoints...");
|
|
3147
|
+
try {
|
|
3148
|
+
const query = {};
|
|
3149
|
+
if (options.limit) query.limit = options.limit;
|
|
3150
|
+
if (options.page) query.page = options.page;
|
|
3151
|
+
const result = await api.listRelayEndpoints(query);
|
|
3152
|
+
const endpoints = result.rows || [];
|
|
3153
|
+
if (isAgentMode()) {
|
|
3154
|
+
json({
|
|
3155
|
+
total: result.total,
|
|
3156
|
+
showing: endpoints.length,
|
|
3157
|
+
endpoints: endpoints.map((e) => ({
|
|
3158
|
+
id: e.id,
|
|
3159
|
+
active: e.active,
|
|
3160
|
+
description: e.description,
|
|
3161
|
+
eventFilters: e.eventFilters,
|
|
3162
|
+
actionsCount: e.actions?.length || 0,
|
|
3163
|
+
url: e.url,
|
|
3164
|
+
createdAt: e.createdAt
|
|
3165
|
+
}))
|
|
3166
|
+
});
|
|
3167
|
+
return;
|
|
3168
|
+
}
|
|
3169
|
+
spinner5.stop(`${endpoints.length} relay endpoint${endpoints.length === 1 ? "" : "s"} found`);
|
|
3170
|
+
if (endpoints.length === 0) {
|
|
3171
|
+
console.log("\n No relay endpoints yet.\n");
|
|
3172
|
+
return;
|
|
3173
|
+
}
|
|
3174
|
+
printTable(
|
|
3175
|
+
["Status", "Description", "Events", "Actions", "ID"],
|
|
3176
|
+
endpoints.map((e) => [
|
|
3177
|
+
e.active ? pc8.green("\u25CF") : pc8.dim("\u25CB"),
|
|
3178
|
+
e.description || pc8.dim("(none)"),
|
|
3179
|
+
e.eventFilters?.join(", ") || pc8.dim("all"),
|
|
3180
|
+
String(e.actions?.length || 0),
|
|
3181
|
+
pc8.dim(e.id.slice(0, 8))
|
|
3182
|
+
])
|
|
3183
|
+
);
|
|
3184
|
+
} catch (error2) {
|
|
3185
|
+
spinner5.stop("Failed to list relay endpoints");
|
|
3186
|
+
error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
async function relayGetCommand(id) {
|
|
3190
|
+
const { apiKey } = getConfig3();
|
|
3191
|
+
const api = new OneApi(apiKey);
|
|
3192
|
+
const spinner5 = createSpinner();
|
|
3193
|
+
spinner5.start("Loading relay endpoint...");
|
|
3194
|
+
try {
|
|
3195
|
+
const result = await api.getRelayEndpoint(id);
|
|
3196
|
+
if (isAgentMode()) {
|
|
3197
|
+
json(result);
|
|
3198
|
+
return;
|
|
3199
|
+
}
|
|
3200
|
+
spinner5.stop("Relay endpoint loaded");
|
|
3201
|
+
console.log();
|
|
3202
|
+
console.log(` ${pc8.dim("ID:")} ${result.id}`);
|
|
3203
|
+
console.log(` ${pc8.dim("URL:")} ${result.url}`);
|
|
3204
|
+
console.log(` ${pc8.dim("Active:")} ${result.active}`);
|
|
3205
|
+
if (result.description) console.log(` ${pc8.dim("Description:")} ${result.description}`);
|
|
3206
|
+
if (result.eventFilters?.length) console.log(` ${pc8.dim("Events:")} ${result.eventFilters.join(", ")}`);
|
|
3207
|
+
console.log(` ${pc8.dim("Actions:")} ${result.actions?.length || 0}`);
|
|
3208
|
+
if (result.actions?.length) {
|
|
3209
|
+
for (const [i, action] of result.actions.entries()) {
|
|
3210
|
+
console.log(` ${pc8.dim(`[${i}]`)} type=${action.type}${action.actionId ? ` actionId=${action.actionId}` : ""}${action.url ? ` url=${action.url}` : ""}`);
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
console.log(` ${pc8.dim("Created:")} ${result.createdAt}`);
|
|
3214
|
+
console.log();
|
|
3215
|
+
} catch (error2) {
|
|
3216
|
+
spinner5.stop("Failed to load relay endpoint");
|
|
3217
|
+
error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
async function relayUpdateCommand(id, options) {
|
|
3221
|
+
const { apiKey } = getConfig3();
|
|
3222
|
+
const api = new OneApi(apiKey);
|
|
3223
|
+
const spinner5 = createSpinner();
|
|
3224
|
+
spinner5.start("Updating relay endpoint...");
|
|
3225
|
+
try {
|
|
3226
|
+
const body = {};
|
|
3227
|
+
if (options.description !== void 0) body.description = options.description;
|
|
3228
|
+
if (options.active !== void 0) body.active = options.active;
|
|
3229
|
+
if (options.eventFilters) body.eventFilters = parseJsonArg2(options.eventFilters, "--event-filters");
|
|
3230
|
+
if (options.tags) body.tags = parseJsonArg2(options.tags, "--tags");
|
|
3231
|
+
if (options.actions) body.actions = parseJsonArg2(options.actions, "--actions");
|
|
3232
|
+
const result = await api.updateRelayEndpoint(id, body);
|
|
3233
|
+
if (isAgentMode()) {
|
|
3234
|
+
json(result);
|
|
3235
|
+
return;
|
|
3236
|
+
}
|
|
3237
|
+
spinner5.stop("Relay endpoint updated");
|
|
3238
|
+
console.log(` ${pc8.dim("ID:")} ${result.id}`);
|
|
3239
|
+
console.log(` ${pc8.dim("Active:")} ${result.active}`);
|
|
3240
|
+
console.log(` ${pc8.dim("Actions:")} ${result.actions?.length || 0}`);
|
|
3241
|
+
console.log();
|
|
3242
|
+
} catch (error2) {
|
|
3243
|
+
spinner5.stop("Failed to update relay endpoint");
|
|
3244
|
+
error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
async function relayDeleteCommand(id) {
|
|
3248
|
+
const { apiKey } = getConfig3();
|
|
3249
|
+
const api = new OneApi(apiKey);
|
|
3250
|
+
const spinner5 = createSpinner();
|
|
3251
|
+
spinner5.start("Deleting relay endpoint...");
|
|
3252
|
+
try {
|
|
3253
|
+
const result = await api.deleteRelayEndpoint(id);
|
|
3254
|
+
if (isAgentMode()) {
|
|
3255
|
+
json({ deleted: true, id: result.id });
|
|
3256
|
+
return;
|
|
3257
|
+
}
|
|
3258
|
+
spinner5.stop("Relay endpoint deleted");
|
|
3259
|
+
console.log(` Deleted: ${result.id}`);
|
|
3260
|
+
console.log();
|
|
3261
|
+
} catch (error2) {
|
|
3262
|
+
spinner5.stop("Failed to delete relay endpoint");
|
|
3263
|
+
error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
async function relayActivateCommand(id, options) {
|
|
3267
|
+
const { apiKey } = getConfig3();
|
|
3268
|
+
const api = new OneApi(apiKey);
|
|
3269
|
+
const spinner5 = createSpinner();
|
|
3270
|
+
spinner5.start("Activating relay endpoint...");
|
|
3271
|
+
try {
|
|
3272
|
+
const actions2 = parseJsonArg2(options.actions, "--actions");
|
|
3273
|
+
const body = { actions: actions2 };
|
|
3274
|
+
if (options.webhookSecret) body.webhookSecret = options.webhookSecret;
|
|
3275
|
+
const result = await api.activateRelayEndpoint(id, body);
|
|
3276
|
+
if (isAgentMode()) {
|
|
3277
|
+
json(result);
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3280
|
+
spinner5.stop("Relay endpoint activated");
|
|
3281
|
+
console.log(` ${pc8.dim("ID:")} ${result.id}`);
|
|
3282
|
+
console.log(` ${pc8.dim("Active:")} ${result.active}`);
|
|
3283
|
+
console.log(` ${pc8.dim("Actions:")} ${result.actions?.length || 0}`);
|
|
3284
|
+
console.log();
|
|
3285
|
+
} catch (error2) {
|
|
3286
|
+
spinner5.stop("Failed to activate relay endpoint");
|
|
3287
|
+
error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
async function relayEventsCommand(options) {
|
|
3291
|
+
const { apiKey } = getConfig3();
|
|
3292
|
+
const api = new OneApi(apiKey);
|
|
3293
|
+
const spinner5 = createSpinner();
|
|
3294
|
+
spinner5.start("Loading relay events...");
|
|
3295
|
+
try {
|
|
3296
|
+
const query = {};
|
|
3297
|
+
if (options.limit) query.limit = options.limit;
|
|
3298
|
+
if (options.page) query.page = options.page;
|
|
3299
|
+
if (options.platform) query.platform = options.platform;
|
|
3300
|
+
if (options.eventType) query.eventType = options.eventType;
|
|
3301
|
+
if (options.after) query.after = options.after;
|
|
3302
|
+
if (options.before) query.before = options.before;
|
|
3303
|
+
const result = await api.listRelayEvents(query);
|
|
3304
|
+
const events = result.rows || [];
|
|
3305
|
+
if (isAgentMode()) {
|
|
3306
|
+
json({
|
|
3307
|
+
total: result.total,
|
|
3308
|
+
showing: events.length,
|
|
3309
|
+
events: events.map((e) => ({
|
|
3310
|
+
id: e.id,
|
|
3311
|
+
platform: e.platform,
|
|
3312
|
+
eventType: e.eventType,
|
|
3313
|
+
timestamp: e.timestamp || e.createdAt
|
|
3314
|
+
}))
|
|
3315
|
+
});
|
|
3316
|
+
return;
|
|
3317
|
+
}
|
|
3318
|
+
spinner5.stop(`${events.length} event${events.length === 1 ? "" : "s"} found`);
|
|
3319
|
+
if (events.length === 0) {
|
|
3320
|
+
console.log("\n No events found.\n");
|
|
3321
|
+
return;
|
|
3322
|
+
}
|
|
3323
|
+
printTable(
|
|
3324
|
+
["Platform", "Event Type", "Timestamp", "ID"],
|
|
3325
|
+
events.map((e) => [
|
|
3326
|
+
e.platform,
|
|
3327
|
+
e.eventType || pc8.dim("unknown"),
|
|
3328
|
+
e.timestamp || e.createdAt,
|
|
3329
|
+
pc8.dim(e.id.slice(0, 8))
|
|
3330
|
+
])
|
|
3331
|
+
);
|
|
3332
|
+
} catch (error2) {
|
|
3333
|
+
spinner5.stop("Failed to list relay events");
|
|
3334
|
+
error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
async function relayEventGetCommand(id) {
|
|
3338
|
+
const { apiKey } = getConfig3();
|
|
3339
|
+
const api = new OneApi(apiKey);
|
|
3340
|
+
const spinner5 = createSpinner();
|
|
3341
|
+
spinner5.start("Loading relay event...");
|
|
3342
|
+
try {
|
|
3343
|
+
const result = await api.getRelayEvent(id);
|
|
3344
|
+
if (isAgentMode()) {
|
|
3345
|
+
json(result);
|
|
3346
|
+
return;
|
|
3347
|
+
}
|
|
3348
|
+
spinner5.stop("Relay event loaded");
|
|
3349
|
+
console.log();
|
|
3350
|
+
console.log(` ${pc8.dim("ID:")} ${result.id}`);
|
|
3351
|
+
console.log(` ${pc8.dim("Platform:")} ${result.platform}`);
|
|
3352
|
+
console.log(` ${pc8.dim("Event:")} ${result.eventType}`);
|
|
3353
|
+
console.log(` ${pc8.dim("Timestamp:")} ${result.timestamp || result.createdAt}`);
|
|
3354
|
+
console.log(` ${pc8.dim("Payload:")}`);
|
|
3355
|
+
console.log(JSON.stringify(result.payload, null, 2));
|
|
3356
|
+
console.log();
|
|
3357
|
+
} catch (error2) {
|
|
3358
|
+
spinner5.stop("Failed to load relay event");
|
|
3359
|
+
error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
async function relayDeliveriesCommand(options) {
|
|
3363
|
+
if (!options.endpointId && !options.eventId) {
|
|
3364
|
+
error("Provide either --endpoint-id or --event-id");
|
|
3365
|
+
}
|
|
3366
|
+
const { apiKey } = getConfig3();
|
|
3367
|
+
const api = new OneApi(apiKey);
|
|
3368
|
+
const spinner5 = createSpinner();
|
|
3369
|
+
spinner5.start("Loading deliveries...");
|
|
3370
|
+
try {
|
|
3371
|
+
const deliveries = options.endpointId ? await api.listRelayEndpointDeliveries(options.endpointId) : await api.listRelayEventDeliveries(options.eventId);
|
|
3372
|
+
const items = Array.isArray(deliveries) ? deliveries : deliveries.rows || [];
|
|
3373
|
+
if (isAgentMode()) {
|
|
3374
|
+
json({ deliveries: items });
|
|
3375
|
+
return;
|
|
3376
|
+
}
|
|
3377
|
+
spinner5.stop(`${items.length} deliver${items.length === 1 ? "y" : "ies"} found`);
|
|
3378
|
+
if (items.length === 0) {
|
|
3379
|
+
console.log("\n No deliveries found.\n");
|
|
3380
|
+
return;
|
|
3381
|
+
}
|
|
3382
|
+
printTable(
|
|
3383
|
+
["Status", "Code", "Attempt", "Delivered At", "Error"],
|
|
3384
|
+
items.map((d) => [
|
|
3385
|
+
d.status === "success" ? pc8.green(d.status) : pc8.red(d.status),
|
|
3386
|
+
d.statusCode != null ? String(d.statusCode) : pc8.dim("-"),
|
|
3387
|
+
String(d.attempt),
|
|
3388
|
+
d.deliveredAt || pc8.dim("-"),
|
|
3389
|
+
d.error ? pc8.red(d.error.slice(0, 50)) : pc8.dim("-")
|
|
3390
|
+
])
|
|
3391
|
+
);
|
|
3392
|
+
} catch (error2) {
|
|
3393
|
+
spinner5.stop("Failed to load deliveries");
|
|
3394
|
+
error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
async function relayEventTypesCommand(platform) {
|
|
3398
|
+
const { apiKey } = getConfig3();
|
|
3399
|
+
const api = new OneApi(apiKey);
|
|
3400
|
+
const spinner5 = createSpinner();
|
|
3401
|
+
spinner5.start(`Loading event types for ${pc8.cyan(platform)}...`);
|
|
3402
|
+
try {
|
|
3403
|
+
const eventTypes = await api.listRelayEventTypes(platform);
|
|
3404
|
+
if (isAgentMode()) {
|
|
3405
|
+
json({ platform, eventTypes });
|
|
3406
|
+
return;
|
|
2765
3407
|
}
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
"action": {
|
|
2773
|
-
"platform": "stripe",
|
|
2774
|
-
"actionId": "the-action-id-from-search",
|
|
2775
|
-
"connectionKey": "$.input.stripeConnectionKey",
|
|
2776
|
-
"data": {},
|
|
2777
|
-
"pathVars": {},
|
|
2778
|
-
"queryParams": {},
|
|
2779
|
-
"headers": {}
|
|
2780
|
-
}
|
|
3408
|
+
spinner5.stop(`${eventTypes.length} event type${eventTypes.length === 1 ? "" : "s"} found`);
|
|
3409
|
+
if (eventTypes.length === 0) {
|
|
3410
|
+
console.log(`
|
|
3411
|
+
No event types found for ${platform}.
|
|
3412
|
+
`);
|
|
3413
|
+
return;
|
|
2781
3414
|
}
|
|
2782
|
-
|
|
3415
|
+
console.log();
|
|
3416
|
+
for (const type of eventTypes) {
|
|
3417
|
+
console.log(` ${type}`);
|
|
3418
|
+
}
|
|
3419
|
+
console.log();
|
|
3420
|
+
} catch (error2) {
|
|
3421
|
+
spinner5.stop("Failed to load event types");
|
|
3422
|
+
error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
|
|
3423
|
+
}
|
|
2783
3424
|
}
|
|
2784
|
-
\`\`\`
|
|
2785
|
-
|
|
2786
|
-
### Input declarations
|
|
2787
3425
|
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
| \`type\` | string | \`string\`, \`number\`, \`boolean\`, \`object\`, \`array\` |
|
|
2791
|
-
| \`required\` | boolean | Whether this input must be provided (default: true) |
|
|
2792
|
-
| \`default\` | any | Default value if not provided |
|
|
2793
|
-
| \`description\` | string | Human-readable description |
|
|
2794
|
-
| \`connection\` | object | Connection metadata: \`{ "platform": "gmail" }\` \u2014 enables auto-resolution |
|
|
2795
|
-
|
|
2796
|
-
**Connection inputs** have a \`connection\` field. If the user has exactly one connection for that platform, the workflow engine auto-resolves it.
|
|
3426
|
+
// src/commands/guide.ts
|
|
3427
|
+
import pc9 from "picocolors";
|
|
2797
3428
|
|
|
2798
|
-
|
|
3429
|
+
// src/lib/guide-content.ts
|
|
3430
|
+
var GUIDE_OVERVIEW = `# One CLI \u2014 Agent Guide
|
|
2799
3431
|
|
|
2800
|
-
|
|
2801
|
-
|---|---|
|
|
2802
|
-
| \`$.input.gmailConnectionKey\` | Input value (including connection keys) |
|
|
2803
|
-
| \`$.input.customerEmail\` | Any input parameter |
|
|
2804
|
-
| \`$.steps.stepId.response\` | Full API response from a step |
|
|
2805
|
-
| \`$.steps.stepId.response.data[0].email\` | Nested field with array index |
|
|
2806
|
-
| \`$.steps.stepId.response.data[*].id\` | Wildcard \u2014 maps array to field |
|
|
2807
|
-
| \`$.env.MY_VAR\` | Environment variable |
|
|
2808
|
-
| \`$.loop.item\` | Current loop item |
|
|
2809
|
-
| \`$.loop.i\` | Current loop index |
|
|
2810
|
-
| \`"Hello {{$.steps.getUser.response.data.name}}"\` | String interpolation |
|
|
3432
|
+
## Setup
|
|
2811
3433
|
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
- Selectors inside objects/arrays are resolved recursively
|
|
3434
|
+
1. Run \`one init\` to configure your API key
|
|
3435
|
+
2. Run \`one add <platform>\` to connect platforms via OAuth
|
|
3436
|
+
3. Run \`one --agent connection list\` to verify connections
|
|
2816
3437
|
|
|
2817
|
-
##
|
|
3438
|
+
## The --agent Flag
|
|
2818
3439
|
|
|
2819
|
-
|
|
3440
|
+
Always use \`--agent\` for machine-readable JSON output. It disables colors, spinners, and interactive prompts.
|
|
2820
3441
|
|
|
2821
|
-
\`\`\`
|
|
2822
|
-
|
|
2823
|
-
"id": "findCustomer",
|
|
2824
|
-
"name": "Search Stripe customers",
|
|
2825
|
-
"type": "action",
|
|
2826
|
-
"action": {
|
|
2827
|
-
"platform": "stripe",
|
|
2828
|
-
"actionId": "conn_mod_def::xxx::yyy",
|
|
2829
|
-
"connectionKey": "$.input.stripeConnectionKey",
|
|
2830
|
-
"data": {
|
|
2831
|
-
"query": "email:'{{$.input.customerEmail}}'"
|
|
2832
|
-
}
|
|
2833
|
-
}
|
|
2834
|
-
}
|
|
3442
|
+
\`\`\`bash
|
|
3443
|
+
one --agent <command>
|
|
2835
3444
|
\`\`\`
|
|
2836
3445
|
|
|
2837
|
-
|
|
3446
|
+
All commands return JSON. If an \`error\` key is present, the command failed.
|
|
2838
3447
|
|
|
2839
|
-
|
|
2840
|
-
{
|
|
2841
|
-
"id": "extractNames",
|
|
2842
|
-
"name": "Extract customer names",
|
|
2843
|
-
"type": "transform",
|
|
2844
|
-
"transform": {
|
|
2845
|
-
"expression": "$.steps.findCustomer.response.data.map(c => c.name)"
|
|
2846
|
-
}
|
|
2847
|
-
}
|
|
2848
|
-
\`\`\`
|
|
3448
|
+
## IMPORTANT: Read the guide before you act
|
|
2849
3449
|
|
|
2850
|
-
The
|
|
3450
|
+
Before using any feature, read its guide section first: \`one guide actions\`, \`one guide flows\`, or \`one guide relay\`. The guide teaches you the correct workflow, required fields, and common mistakes. Never guess \u2014 read the guide, then act.
|
|
2851
3451
|
|
|
2852
|
-
|
|
3452
|
+
## Features
|
|
2853
3453
|
|
|
2854
|
-
|
|
3454
|
+
### 1. Actions \u2014 Execute API calls on 200+ platforms
|
|
3455
|
+
Search for actions, read their docs, and execute them. This is the core workflow.
|
|
2855
3456
|
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
"source": "const customers = $.steps.listCustomers.response.data;\\nconst enriched = customers.map(c => ({\\n ...c,\\n tier: c.spend > 1000 ? 'gold' : 'silver'\\n}));\\nreturn enriched;"
|
|
2863
|
-
}
|
|
2864
|
-
}
|
|
3457
|
+
**Quick start:**
|
|
3458
|
+
\`\`\`bash
|
|
3459
|
+
one --agent connection list # See connected platforms
|
|
3460
|
+
one --agent actions search <platform> "<query>" -t execute # Find an action
|
|
3461
|
+
one --agent actions knowledge <platform> <actionId> # Read docs (REQUIRED)
|
|
3462
|
+
one --agent actions execute <platform> <actionId> <key> -d '{}' # Execute it
|
|
2865
3463
|
\`\`\`
|
|
2866
3464
|
|
|
2867
|
-
|
|
3465
|
+
**Parameter flags:**
|
|
3466
|
+
- \`-d <json>\` \u2014 Request body (POST/PUT/PATCH)
|
|
3467
|
+
- \`--path-vars <json>\` \u2014 URL path variables (e.g., \`{"userId": "me"}\`)
|
|
3468
|
+
- \`--query-params <json>\` \u2014 Query parameters (arrays expand to repeated params)
|
|
3469
|
+
- \`--form-url-encoded\` \u2014 Send as form data instead of JSON
|
|
3470
|
+
- \`--dry-run\` \u2014 Preview request without executing
|
|
2868
3471
|
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
\`\`\`json
|
|
2872
|
-
{
|
|
2873
|
-
"id": "checkFound",
|
|
2874
|
-
"name": "Check if customer was found",
|
|
2875
|
-
"type": "condition",
|
|
2876
|
-
"condition": {
|
|
2877
|
-
"expression": "$.steps.findCustomer.response.data.length > 0",
|
|
2878
|
-
"then": [
|
|
2879
|
-
{ "id": "sendEmail", "name": "Send welcome email", "type": "action", "action": { "..." : "..." } }
|
|
2880
|
-
],
|
|
2881
|
-
"else": [
|
|
2882
|
-
{ "id": "logNotFound", "name": "Log not found", "type": "transform", "transform": { "expression": "'Customer not found'" } }
|
|
2883
|
-
]
|
|
2884
|
-
}
|
|
2885
|
-
}
|
|
2886
|
-
\`\`\`
|
|
3472
|
+
Do NOT pass path or query parameters in \`-d\` \u2014 use the correct flags.
|
|
2887
3473
|
|
|
2888
|
-
###
|
|
3474
|
+
### 2. Flows \u2014 Multi-step API workflows
|
|
3475
|
+
Chain actions across platforms as JSON workflow files with conditions, loops, parallel execution, transforms, and AI analysis via bash steps.
|
|
2889
3476
|
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
"over": "$.steps.listOrders.response.data",
|
|
2897
|
-
"as": "order",
|
|
2898
|
-
"indexAs": "i",
|
|
2899
|
-
"maxIterations": 1000,
|
|
2900
|
-
"maxConcurrency": 5,
|
|
2901
|
-
"steps": [
|
|
2902
|
-
{
|
|
2903
|
-
"id": "createInvoice",
|
|
2904
|
-
"name": "Create invoice for order",
|
|
2905
|
-
"type": "action",
|
|
2906
|
-
"action": {
|
|
2907
|
-
"platform": "quickbooks",
|
|
2908
|
-
"actionId": "...",
|
|
2909
|
-
"connectionKey": "$.input.qbConnectionKey",
|
|
2910
|
-
"data": { "amount": "$.loop.order.total" }
|
|
2911
|
-
}
|
|
2912
|
-
}
|
|
2913
|
-
]
|
|
2914
|
-
}
|
|
2915
|
-
}
|
|
3477
|
+
**Quick start:**
|
|
3478
|
+
\`\`\`bash
|
|
3479
|
+
one --agent flow create <key> --definition '<json>' # Create a workflow
|
|
3480
|
+
one --agent flow validate <key> # Validate it
|
|
3481
|
+
one --agent flow execute <key> -i param=value # Execute it
|
|
3482
|
+
one --agent flow list # List all workflows
|
|
2916
3483
|
\`\`\`
|
|
2917
3484
|
|
|
2918
|
-
|
|
3485
|
+
**Key concepts:**
|
|
3486
|
+
- Workflows are JSON files at \`.one/flows/<key>.flow.json\`
|
|
3487
|
+
- 12 step types: action, transform, code, condition, loop, parallel, file-read, file-write, while, flow, paginate, bash
|
|
3488
|
+
- Data wiring via selectors: \`$.input.param\`, \`$.steps.stepId.response\`, \`$.loop.item\`
|
|
3489
|
+
- AI analysis via bash steps: \`claude --print\` with \`parseJson: true\`
|
|
3490
|
+
- Use \`--allow-bash\` to enable bash steps, \`--mock\` for dry-run with mock responses
|
|
2919
3491
|
|
|
2920
|
-
###
|
|
3492
|
+
### 3. Relay \u2014 Webhook event forwarding between platforms
|
|
3493
|
+
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.
|
|
2921
3494
|
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
{ "id": "getStripe", "name": "Get Stripe data", "type": "action", "action": { "...": "..." } },
|
|
2931
|
-
{ "id": "getHubspot", "name": "Get HubSpot data", "type": "action", "action": { "...": "..." } }
|
|
2932
|
-
]
|
|
2933
|
-
}
|
|
2934
|
-
}
|
|
3495
|
+
**Quick start:**
|
|
3496
|
+
\`\`\`bash
|
|
3497
|
+
one --agent relay event-types <platform> # See available events
|
|
3498
|
+
one --agent relay create --connection-key <key> --create-webhook --event-filters '["event.type"]'
|
|
3499
|
+
one --agent relay activate <id> --actions '[{"type":"passthrough","actionId":"...","connectionKey":"...","body":{...}}]'
|
|
3500
|
+
one --agent relay list # List endpoints
|
|
3501
|
+
one --agent relay events --platform <p> # List received events
|
|
3502
|
+
one --agent relay deliveries --endpoint-id <id> # Check delivery status
|
|
2935
3503
|
\`\`\`
|
|
2936
3504
|
|
|
2937
|
-
|
|
3505
|
+
**Key concepts:**
|
|
3506
|
+
- \`passthrough\` actions map webhook fields to another platform's API using Handlebars: \`{{payload.data.object.email}}\`
|
|
3507
|
+
- Template context: \`{{relayEventId}}\`, \`{{platform}}\`, \`{{eventType}}\`, \`{{payload}}\`, \`{{timestamp}}\`, \`{{connectionId}}\`
|
|
3508
|
+
- \`--create-webhook\` auto-registers the webhook URL with the source platform
|
|
3509
|
+
- Use \`actions knowledge\` to learn both the incoming payload shape AND the destination API shape before building templates
|
|
2938
3510
|
|
|
2939
|
-
|
|
2940
|
-
{
|
|
2941
|
-
"id": "readConfig",
|
|
2942
|
-
"name": "Read config file",
|
|
2943
|
-
"type": "file-read",
|
|
2944
|
-
"fileRead": { "path": "./data/config.json", "parseJson": true }
|
|
2945
|
-
}
|
|
2946
|
-
\`\`\`
|
|
3511
|
+
## Topics
|
|
2947
3512
|
|
|
2948
|
-
|
|
3513
|
+
Request specific sections:
|
|
3514
|
+
- \`one guide overview\` \u2014 This section
|
|
3515
|
+
- \`one guide actions\` \u2014 Actions reference (search, knowledge, execute)
|
|
3516
|
+
- \`one guide flows\` \u2014 Workflow engine reference (step types, selectors, examples)
|
|
3517
|
+
- \`one guide relay\` \u2014 Webhook relay reference (templates, passthrough actions)
|
|
3518
|
+
- \`one guide all\` \u2014 Everything
|
|
2949
3519
|
|
|
2950
|
-
|
|
2951
|
-
{
|
|
2952
|
-
"id": "writeResults",
|
|
2953
|
-
"name": "Save results",
|
|
2954
|
-
"type": "file-write",
|
|
2955
|
-
"fileWrite": {
|
|
2956
|
-
"path": "./output/results.json",
|
|
2957
|
-
"content": "$.steps.transform.output",
|
|
2958
|
-
"append": false
|
|
2959
|
-
}
|
|
2960
|
-
}
|
|
2961
|
-
\`\`\`
|
|
3520
|
+
## Important Notes
|
|
2962
3521
|
|
|
2963
|
-
|
|
3522
|
+
- **Always use \`--agent\` flag** for structured JSON output
|
|
3523
|
+
- Platform names are always **kebab-case** (e.g., \`hub-spot\`, \`google-calendar\`)
|
|
3524
|
+
- Always use the **exact action ID** from search results \u2014 don't guess
|
|
3525
|
+
- Always read **knowledge** before executing any action
|
|
3526
|
+
- Connection keys come from \`one connection list\` \u2014 don't hardcode them
|
|
3527
|
+
`;
|
|
3528
|
+
var GUIDE_ACTIONS = `# One Actions \u2014 Reference
|
|
2964
3529
|
|
|
2965
|
-
|
|
3530
|
+
## Workflow: search \u2192 knowledge \u2192 execute
|
|
2966
3531
|
|
|
2967
|
-
|
|
2968
|
-
{
|
|
2969
|
-
"id": "paginate",
|
|
2970
|
-
"name": "Paginate through all pages",
|
|
2971
|
-
"type": "while",
|
|
2972
|
-
"while": {
|
|
2973
|
-
"condition": "$.steps.paginate.output.lastResult.nextPageToken != null",
|
|
2974
|
-
"maxIterations": 50,
|
|
2975
|
-
"steps": [
|
|
2976
|
-
{
|
|
2977
|
-
"id": "fetchPage",
|
|
2978
|
-
"name": "Fetch next page",
|
|
2979
|
-
"type": "action",
|
|
2980
|
-
"action": {
|
|
2981
|
-
"platform": "gmail",
|
|
2982
|
-
"actionId": "GMAIL_LIST_MESSAGES_ACTION_ID",
|
|
2983
|
-
"connectionKey": "$.input.gmailKey",
|
|
2984
|
-
"queryParams": {
|
|
2985
|
-
"pageToken": "$.steps.paginate.output.lastResult.nextPageToken"
|
|
2986
|
-
}
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
|
-
]
|
|
2990
|
-
}
|
|
2991
|
-
}
|
|
2992
|
-
\`\`\`
|
|
3532
|
+
Always follow this sequence. Never skip the knowledge step.
|
|
2993
3533
|
|
|
2994
|
-
|
|
2995
|
-
|---|---|---|
|
|
2996
|
-
| \`condition\` | string | JS expression evaluated before each iteration (after iteration 0) |
|
|
2997
|
-
| \`maxIterations\` | number | Safety cap, default: 100 |
|
|
2998
|
-
| \`steps\` | FlowStep[] | Steps to execute each iteration |
|
|
3534
|
+
### 1. List Connections
|
|
2999
3535
|
|
|
3000
|
-
|
|
3536
|
+
\`\`\`bash
|
|
3537
|
+
one --agent connection list
|
|
3538
|
+
\`\`\`
|
|
3001
3539
|
|
|
3002
|
-
|
|
3540
|
+
Returns platforms, status, connection keys, and tags.
|
|
3003
3541
|
|
|
3004
|
-
|
|
3542
|
+
### 2. Search Actions
|
|
3005
3543
|
|
|
3006
|
-
\`\`\`
|
|
3007
|
-
|
|
3008
|
-
"id": "processCustomer",
|
|
3009
|
-
"name": "Run customer enrichment flow",
|
|
3010
|
-
"type": "flow",
|
|
3011
|
-
"flow": {
|
|
3012
|
-
"key": "enrich-customer",
|
|
3013
|
-
"inputs": {
|
|
3014
|
-
"email": "$.steps.getCustomer.response.email",
|
|
3015
|
-
"connectionKey": "$.input.hubspotConnectionKey"
|
|
3016
|
-
}
|
|
3017
|
-
}
|
|
3018
|
-
}
|
|
3544
|
+
\`\`\`bash
|
|
3545
|
+
one --agent actions search <platform> "<query>" -t execute
|
|
3019
3546
|
\`\`\`
|
|
3020
3547
|
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
| \`inputs\` | object | Input values mapped to the sub-flow's declared inputs (supports selectors) |
|
|
3548
|
+
- Use \`-t execute\` when the user wants to perform an action
|
|
3549
|
+
- Use \`-t knowledge\` (default) for documentation/code generation
|
|
3550
|
+
- Returns up to 5 matching actions with IDs, methods, and paths
|
|
3025
3551
|
|
|
3026
|
-
###
|
|
3552
|
+
### 3. Get Knowledge
|
|
3027
3553
|
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
\`\`\`json
|
|
3031
|
-
{
|
|
3032
|
-
"id": "allMessages",
|
|
3033
|
-
"name": "Fetch all Gmail messages",
|
|
3034
|
-
"type": "paginate",
|
|
3035
|
-
"paginate": {
|
|
3036
|
-
"action": {
|
|
3037
|
-
"platform": "gmail",
|
|
3038
|
-
"actionId": "GMAIL_LIST_MESSAGES_ACTION_ID",
|
|
3039
|
-
"connectionKey": "$.input.gmailKey",
|
|
3040
|
-
"queryParams": { "maxResults": 100 }
|
|
3041
|
-
},
|
|
3042
|
-
"pageTokenField": "nextPageToken",
|
|
3043
|
-
"resultsField": "messages",
|
|
3044
|
-
"inputTokenParam": "queryParams.pageToken",
|
|
3045
|
-
"maxPages": 10
|
|
3046
|
-
}
|
|
3047
|
-
}
|
|
3554
|
+
\`\`\`bash
|
|
3555
|
+
one --agent actions knowledge <platform> <actionId>
|
|
3048
3556
|
\`\`\`
|
|
3049
3557
|
|
|
3050
|
-
|
|
3051
|
-
|---|---|---|
|
|
3052
|
-
| \`action\` | FlowActionConfig | The API action to call (same format as action steps) |
|
|
3053
|
-
| \`pageTokenField\` | string | Dot-path in the API response to the next page token |
|
|
3054
|
-
| \`resultsField\` | string | Dot-path in the API response to the results array |
|
|
3055
|
-
| \`inputTokenParam\` | string | Dot-path in the action config where the page token is injected |
|
|
3056
|
-
| \`maxPages\` | number | Maximum pages to fetch, default: 10 |
|
|
3558
|
+
Returns full API docs: required fields, validation rules, request structure. **REQUIRED before execute.** The output includes a CLI PARAMETER MAPPING section showing which flags to use.
|
|
3057
3559
|
|
|
3058
|
-
###
|
|
3560
|
+
### 4. Execute
|
|
3059
3561
|
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
\`\`\`json
|
|
3063
|
-
{
|
|
3064
|
-
"id": "analyzeData",
|
|
3065
|
-
"name": "Analyze data with Claude",
|
|
3066
|
-
"type": "bash",
|
|
3067
|
-
"bash": {
|
|
3068
|
-
"command": "claude --print 'Analyze: {{$.steps.fetchData.response}}' --output-format json",
|
|
3069
|
-
"timeout": 120000,
|
|
3070
|
-
"parseJson": true
|
|
3071
|
-
}
|
|
3072
|
-
}
|
|
3562
|
+
\`\`\`bash
|
|
3563
|
+
one --agent actions execute <platform> <actionId> <connectionKey> [options]
|
|
3073
3564
|
\`\`\`
|
|
3074
3565
|
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3566
|
+
**Flags:**
|
|
3567
|
+
- \`-d, --data <json>\` \u2014 Request body (POST/PUT/PATCH)
|
|
3568
|
+
- \`--path-vars <json>\` \u2014 Path variables: \`{"userId": "me"}\`
|
|
3569
|
+
- \`--query-params <json>\` \u2014 Query params: \`{"limit": "10"}\`
|
|
3570
|
+
- Arrays expand to repeated params: \`{"metadataHeaders": ["From", "Subject"]}\`
|
|
3571
|
+
- \`--headers <json>\` \u2014 Additional headers
|
|
3572
|
+
- \`--form-data\` / \`--form-url-encoded\` \u2014 Alternative content types
|
|
3573
|
+
- \`--dry-run\` \u2014 Preview without executing
|
|
3082
3574
|
|
|
3083
|
-
**
|
|
3575
|
+
**Do NOT** pass path or query parameters in \`-d\`. Use the correct flags.
|
|
3084
3576
|
|
|
3085
|
-
##
|
|
3577
|
+
## Error Handling
|
|
3086
3578
|
|
|
3087
|
-
|
|
3579
|
+
All errors return JSON: \`{"error": "message"}\`. Check the \`error\` key.
|
|
3088
3580
|
|
|
3089
|
-
|
|
3090
|
-
{
|
|
3091
|
-
"id": "riskyStep",
|
|
3092
|
-
"name": "Might fail",
|
|
3093
|
-
"type": "action",
|
|
3094
|
-
"onError": {
|
|
3095
|
-
"strategy": "retry",
|
|
3096
|
-
"retries": 3,
|
|
3097
|
-
"retryDelayMs": 1000
|
|
3098
|
-
},
|
|
3099
|
-
"action": { "...": "..." }
|
|
3100
|
-
}
|
|
3101
|
-
\`\`\`
|
|
3581
|
+
## Notes
|
|
3102
3582
|
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3583
|
+
- Platform names are **kebab-case** (e.g., \`hub-spot\`)
|
|
3584
|
+
- JSON flags use single quotes around the JSON to avoid shell escaping
|
|
3585
|
+
- If search returns no results, try broader queries
|
|
3586
|
+
- Access control settings from \`one config\` may restrict execution
|
|
3587
|
+
`;
|
|
3588
|
+
var GUIDE_FLOWS = generateFlowGuide();
|
|
3589
|
+
var GUIDE_RELAY = `# One Relay \u2014 Reference
|
|
3109
3590
|
|
|
3110
|
-
|
|
3591
|
+
## Overview
|
|
3111
3592
|
|
|
3112
|
-
|
|
3593
|
+
Webhook relay receives events from platforms (Stripe, GitHub, Airtable, Attio, Google Calendar) and forwards them to any connected platform using passthrough actions with Handlebars templates.
|
|
3113
3594
|
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3595
|
+
## Commands
|
|
3596
|
+
|
|
3597
|
+
\`\`\`bash
|
|
3598
|
+
one --agent relay event-types <platform> # List available events
|
|
3599
|
+
one --agent relay create --connection-key <key> --create-webhook --event-filters '["event.type"]'
|
|
3600
|
+
one --agent relay activate <id> --actions '<json>' # Add forwarding actions
|
|
3601
|
+
one --agent relay list # List endpoints
|
|
3602
|
+
one --agent relay get <id> # Get endpoint details
|
|
3603
|
+
one --agent relay update <id> --actions '<json>' # Update endpoint
|
|
3604
|
+
one --agent relay delete <id> # Delete endpoint
|
|
3605
|
+
one --agent relay events --platform <p> # List received events
|
|
3606
|
+
one --agent relay event <id> # Get event with payload
|
|
3607
|
+
one --agent relay deliveries --endpoint-id <id> # Check delivery status
|
|
3122
3608
|
\`\`\`
|
|
3123
3609
|
|
|
3124
|
-
##
|
|
3610
|
+
## Building a Relay
|
|
3125
3611
|
|
|
3126
|
-
|
|
3612
|
+
1. **Discover connections** \u2014 identify source and destination platforms
|
|
3613
|
+
2. **Get event types** \u2014 \`one --agent relay event-types <platform>\`
|
|
3614
|
+
3. **Get source knowledge** \u2014 understand the incoming webhook payload shape (\`{{payload.*}}\` paths)
|
|
3615
|
+
4. **Get destination knowledge** \u2014 understand the outgoing API body shape
|
|
3616
|
+
5. **Create endpoint** \u2014 with \`--create-webhook\` and \`--event-filters\`
|
|
3617
|
+
6. **Activate** \u2014 with passthrough action mapping source fields to destination fields
|
|
3127
3618
|
|
|
3128
|
-
|
|
3129
|
-
2. Understand its current structure
|
|
3130
|
-
3. Use \`one --agent actions knowledge <platform> <actionId>\` for any new actions
|
|
3131
|
-
4. Modify the JSON (add/remove/update steps, change data mappings, add inputs)
|
|
3132
|
-
5. Write back the updated workflow file
|
|
3133
|
-
6. Validate: \`one --agent flow validate <key>\`
|
|
3619
|
+
## Template Context
|
|
3134
3620
|
|
|
3135
|
-
|
|
3621
|
+
| Variable | Description |
|
|
3622
|
+
|----------|-------------|
|
|
3623
|
+
| \`{{relayEventId}}\` | Unique event UUID |
|
|
3624
|
+
| \`{{platform}}\` | Source platform (e.g., \`stripe\`) |
|
|
3625
|
+
| \`{{eventType}}\` | Event type (e.g., \`customer.created\`) |
|
|
3626
|
+
| \`{{payload}}\` | Full incoming webhook body |
|
|
3627
|
+
| \`{{timestamp}}\` | When the event was received |
|
|
3628
|
+
| \`{{connectionId}}\` | Source connection UUID |
|
|
3629
|
+
| \`{{json payload}}\` | Embed full payload as JSON string |
|
|
3136
3630
|
|
|
3137
|
-
|
|
3631
|
+
Access nested fields: \`{{payload.data.object.email}}\`
|
|
3138
3632
|
|
|
3633
|
+
## Action Types
|
|
3634
|
+
|
|
3635
|
+
**passthrough** (primary) \u2014 forward to another platform's API:
|
|
3139
3636
|
\`\`\`json
|
|
3140
3637
|
{
|
|
3141
|
-
"
|
|
3142
|
-
"
|
|
3143
|
-
"
|
|
3144
|
-
"
|
|
3145
|
-
"
|
|
3146
|
-
"stripeConnectionKey": {
|
|
3147
|
-
"type": "string",
|
|
3148
|
-
"required": true,
|
|
3149
|
-
"description": "Stripe connection key",
|
|
3150
|
-
"connection": { "platform": "stripe" }
|
|
3151
|
-
},
|
|
3152
|
-
"gmailConnectionKey": {
|
|
3153
|
-
"type": "string",
|
|
3154
|
-
"required": true,
|
|
3155
|
-
"description": "Gmail connection key",
|
|
3156
|
-
"connection": { "platform": "gmail" }
|
|
3157
|
-
},
|
|
3158
|
-
"customerEmail": {
|
|
3159
|
-
"type": "string",
|
|
3160
|
-
"required": true,
|
|
3161
|
-
"description": "Customer email to look up"
|
|
3162
|
-
}
|
|
3163
|
-
},
|
|
3164
|
-
"steps": [
|
|
3165
|
-
{
|
|
3166
|
-
"id": "findCustomer",
|
|
3167
|
-
"name": "Search for customer in Stripe",
|
|
3168
|
-
"type": "action",
|
|
3169
|
-
"action": {
|
|
3170
|
-
"platform": "stripe",
|
|
3171
|
-
"actionId": "STRIPE_SEARCH_CUSTOMERS_ACTION_ID",
|
|
3172
|
-
"connectionKey": "$.input.stripeConnectionKey",
|
|
3173
|
-
"data": {
|
|
3174
|
-
"query": "email:'{{$.input.customerEmail}}'"
|
|
3175
|
-
}
|
|
3176
|
-
}
|
|
3177
|
-
},
|
|
3178
|
-
{
|
|
3179
|
-
"id": "sendWelcome",
|
|
3180
|
-
"name": "Send welcome email via Gmail",
|
|
3181
|
-
"type": "action",
|
|
3182
|
-
"if": "$.steps.findCustomer.response.data && $.steps.findCustomer.response.data.length > 0",
|
|
3183
|
-
"action": {
|
|
3184
|
-
"platform": "gmail",
|
|
3185
|
-
"actionId": "GMAIL_SEND_EMAIL_ACTION_ID",
|
|
3186
|
-
"connectionKey": "$.input.gmailConnectionKey",
|
|
3187
|
-
"data": {
|
|
3188
|
-
"to": "{{$.input.customerEmail}}",
|
|
3189
|
-
"subject": "Welcome, {{$.steps.findCustomer.response.data[0].name}}!",
|
|
3190
|
-
"body": "Thank you for being a customer. We're glad to have you!"
|
|
3191
|
-
}
|
|
3192
|
-
}
|
|
3193
|
-
}
|
|
3194
|
-
]
|
|
3638
|
+
"type": "passthrough",
|
|
3639
|
+
"actionId": "<action-id>",
|
|
3640
|
+
"connectionKey": "<dest-connection-key>",
|
|
3641
|
+
"body": { "channel": "#alerts", "text": "New customer: {{payload.data.object.name}}" },
|
|
3642
|
+
"eventFilters": ["customer.created"]
|
|
3195
3643
|
}
|
|
3196
3644
|
\`\`\`
|
|
3197
3645
|
|
|
3198
|
-
|
|
3199
|
-
|
|
3646
|
+
**url** \u2014 forward raw event to a URL:
|
|
3200
3647
|
\`\`\`json
|
|
3201
|
-
{
|
|
3202
|
-
"key": "sync-hubspot-contact",
|
|
3203
|
-
"name": "Sync Contact to HubSpot",
|
|
3204
|
-
"description": "Check if a contact exists in HubSpot, create if new or update if existing",
|
|
3205
|
-
"version": "1",
|
|
3206
|
-
"inputs": {
|
|
3207
|
-
"hubspotConnectionKey": {
|
|
3208
|
-
"type": "string",
|
|
3209
|
-
"required": true,
|
|
3210
|
-
"connection": { "platform": "hub-spot" }
|
|
3211
|
-
},
|
|
3212
|
-
"email": { "type": "string", "required": true },
|
|
3213
|
-
"firstName": { "type": "string", "required": true },
|
|
3214
|
-
"lastName": { "type": "string", "required": true }
|
|
3215
|
-
},
|
|
3216
|
-
"steps": [
|
|
3217
|
-
{
|
|
3218
|
-
"id": "searchContact",
|
|
3219
|
-
"name": "Search for existing contact",
|
|
3220
|
-
"type": "action",
|
|
3221
|
-
"action": {
|
|
3222
|
-
"platform": "hub-spot",
|
|
3223
|
-
"actionId": "HUBSPOT_SEARCH_CONTACTS_ACTION_ID",
|
|
3224
|
-
"connectionKey": "$.input.hubspotConnectionKey",
|
|
3225
|
-
"data": {
|
|
3226
|
-
"filterGroups": [{ "filters": [{ "propertyName": "email", "operator": "EQ", "value": "$.input.email" }] }]
|
|
3227
|
-
}
|
|
3228
|
-
}
|
|
3229
|
-
},
|
|
3230
|
-
{
|
|
3231
|
-
"id": "createOrUpdate",
|
|
3232
|
-
"name": "Create or update contact",
|
|
3233
|
-
"type": "condition",
|
|
3234
|
-
"condition": {
|
|
3235
|
-
"expression": "$.steps.searchContact.response.total > 0",
|
|
3236
|
-
"then": [
|
|
3237
|
-
{
|
|
3238
|
-
"id": "updateContact",
|
|
3239
|
-
"name": "Update existing contact",
|
|
3240
|
-
"type": "action",
|
|
3241
|
-
"action": {
|
|
3242
|
-
"platform": "hub-spot",
|
|
3243
|
-
"actionId": "HUBSPOT_UPDATE_CONTACT_ACTION_ID",
|
|
3244
|
-
"connectionKey": "$.input.hubspotConnectionKey",
|
|
3245
|
-
"pathVars": { "contactId": "$.steps.searchContact.response.results[0].id" },
|
|
3246
|
-
"data": {
|
|
3247
|
-
"properties": { "firstname": "$.input.firstName", "lastname": "$.input.lastName" }
|
|
3248
|
-
}
|
|
3249
|
-
}
|
|
3250
|
-
}
|
|
3251
|
-
],
|
|
3252
|
-
"else": [
|
|
3253
|
-
{
|
|
3254
|
-
"id": "createContact",
|
|
3255
|
-
"name": "Create new contact",
|
|
3256
|
-
"type": "action",
|
|
3257
|
-
"action": {
|
|
3258
|
-
"platform": "hub-spot",
|
|
3259
|
-
"actionId": "HUBSPOT_CREATE_CONTACT_ACTION_ID",
|
|
3260
|
-
"connectionKey": "$.input.hubspotConnectionKey",
|
|
3261
|
-
"data": {
|
|
3262
|
-
"properties": { "email": "$.input.email", "firstname": "$.input.firstName", "lastname": "$.input.lastName" }
|
|
3263
|
-
}
|
|
3264
|
-
}
|
|
3265
|
-
}
|
|
3266
|
-
]
|
|
3267
|
-
}
|
|
3268
|
-
}
|
|
3269
|
-
]
|
|
3270
|
-
}
|
|
3648
|
+
{"type": "url", "url": "https://your-app.com/webhook", "eventFilters": ["customer.created"]}
|
|
3271
3649
|
\`\`\`
|
|
3272
3650
|
|
|
3273
|
-
|
|
3274
|
-
|
|
3651
|
+
**agent** \u2014 send to an agent:
|
|
3275
3652
|
\`\`\`json
|
|
3276
|
-
{
|
|
3277
|
-
"key": "shopify-to-invoices",
|
|
3278
|
-
"name": "Shopify Orders to Invoices",
|
|
3279
|
-
"description": "Fetch recent Shopify orders and create an invoice for each",
|
|
3280
|
-
"version": "1",
|
|
3281
|
-
"inputs": {
|
|
3282
|
-
"shopifyConnectionKey": {
|
|
3283
|
-
"type": "string",
|
|
3284
|
-
"required": true,
|
|
3285
|
-
"connection": { "platform": "shopify" }
|
|
3286
|
-
},
|
|
3287
|
-
"qbConnectionKey": {
|
|
3288
|
-
"type": "string",
|
|
3289
|
-
"required": true,
|
|
3290
|
-
"connection": { "platform": "quick-books" }
|
|
3291
|
-
}
|
|
3292
|
-
},
|
|
3293
|
-
"steps": [
|
|
3294
|
-
{
|
|
3295
|
-
"id": "listOrders",
|
|
3296
|
-
"name": "List recent Shopify orders",
|
|
3297
|
-
"type": "action",
|
|
3298
|
-
"action": {
|
|
3299
|
-
"platform": "shopify",
|
|
3300
|
-
"actionId": "SHOPIFY_LIST_ORDERS_ACTION_ID",
|
|
3301
|
-
"connectionKey": "$.input.shopifyConnectionKey",
|
|
3302
|
-
"queryParams": { "status": "any", "limit": "50" }
|
|
3303
|
-
}
|
|
3304
|
-
},
|
|
3305
|
-
{
|
|
3306
|
-
"id": "createInvoices",
|
|
3307
|
-
"name": "Create invoice for each order",
|
|
3308
|
-
"type": "loop",
|
|
3309
|
-
"loop": {
|
|
3310
|
-
"over": "$.steps.listOrders.response.orders",
|
|
3311
|
-
"as": "order",
|
|
3312
|
-
"indexAs": "i",
|
|
3313
|
-
"steps": [
|
|
3314
|
-
{
|
|
3315
|
-
"id": "createInvoice",
|
|
3316
|
-
"name": "Create QuickBooks invoice",
|
|
3317
|
-
"type": "action",
|
|
3318
|
-
"onError": { "strategy": "continue" },
|
|
3319
|
-
"action": {
|
|
3320
|
-
"platform": "quick-books",
|
|
3321
|
-
"actionId": "QB_CREATE_INVOICE_ACTION_ID",
|
|
3322
|
-
"connectionKey": "$.input.qbConnectionKey",
|
|
3323
|
-
"data": {
|
|
3324
|
-
"Line": [
|
|
3325
|
-
{
|
|
3326
|
-
"Amount": "$.loop.order.total_price",
|
|
3327
|
-
"Description": "Shopify Order #{{$.loop.order.order_number}}"
|
|
3328
|
-
}
|
|
3329
|
-
]
|
|
3330
|
-
}
|
|
3331
|
-
}
|
|
3332
|
-
}
|
|
3333
|
-
]
|
|
3334
|
-
}
|
|
3335
|
-
},
|
|
3336
|
-
{
|
|
3337
|
-
"id": "summary",
|
|
3338
|
-
"name": "Generate summary",
|
|
3339
|
-
"type": "transform",
|
|
3340
|
-
"transform": {
|
|
3341
|
-
"expression": "({ totalOrders: $.steps.listOrders.response.orders.length, processed: $.steps.createInvoices.output.length })"
|
|
3342
|
-
}
|
|
3343
|
-
}
|
|
3344
|
-
]
|
|
3345
|
-
}
|
|
3653
|
+
{"type": "agent", "agentId": "<uuid>", "eventFilters": ["customer.created"]}
|
|
3346
3654
|
\`\`\`
|
|
3347
3655
|
|
|
3348
|
-
##
|
|
3349
|
-
|
|
3350
|
-
\`\`\`bash
|
|
3351
|
-
# Create a workflow
|
|
3352
|
-
one --agent flow create <key> --definition '<json>'
|
|
3353
|
-
|
|
3354
|
-
# List all workflows
|
|
3355
|
-
one --agent flow list
|
|
3356
|
-
|
|
3357
|
-
# Validate a workflow
|
|
3358
|
-
one --agent flow validate <key>
|
|
3656
|
+
## Supported Source Platforms
|
|
3359
3657
|
|
|
3360
|
-
|
|
3361
|
-
one --agent flow execute <key> -i connectionKey=value -i param=value
|
|
3658
|
+
Airtable, Attio, GitHub, Google Calendar, Stripe
|
|
3362
3659
|
|
|
3363
|
-
|
|
3364
|
-
one --agent flow execute <key> --dry-run -i connectionKey=value
|
|
3660
|
+
Any connected platform can be a destination via passthrough actions.
|
|
3365
3661
|
|
|
3366
|
-
|
|
3367
|
-
one --agent flow execute <key> --dry-run --mock -i connectionKey=value
|
|
3662
|
+
## Debugging
|
|
3368
3663
|
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
one --agent flow execute <key> -v -i connectionKey=value
|
|
3374
|
-
|
|
3375
|
-
# List workflow runs
|
|
3376
|
-
one --agent flow runs [flowKey]
|
|
3377
|
-
|
|
3378
|
-
# Resume a paused/failed run
|
|
3379
|
-
one --agent flow resume <runId>
|
|
3380
|
-
\`\`\`
|
|
3381
|
-
|
|
3382
|
-
## Important Notes
|
|
3383
|
-
|
|
3384
|
-
- **Always use \`--agent\` flag** for structured JSON output
|
|
3385
|
-
- **Always call \`one actions knowledge\`** before adding an action step to a workflow
|
|
3386
|
-
- Platform names are **kebab-case** (e.g., \`hub-spot\`, not \`HubSpot\`)
|
|
3387
|
-
- Connection keys are **inputs**, not hardcoded \u2014 makes workflows portable and shareable
|
|
3388
|
-
- Use \`$.input.*\` for input values, \`$.steps.*\` for step results
|
|
3389
|
-
- Action IDs in examples (like \`STRIPE_SEARCH_CUSTOMERS_ACTION_ID\`) are placeholders \u2014 always use \`one actions search\` to find the real IDs
|
|
3390
|
-
- **Parallel step outputs** are accessible both by index (\`$.steps.parallelStep.output[0]\`) and by substep ID (\`$.steps.substepId.response\`)
|
|
3391
|
-
- **Loop step outputs** include iteration details via \`$.steps.myLoop.response.iterations[0].innerStepId.response\`
|
|
3392
|
-
- **Code steps** support \`await require('crypto')\`, \`await require('buffer')\`, \`await require('url')\`, \`await require('path')\` \u2014 \`fs\`, \`http\`, \`child_process\`, etc. are blocked
|
|
3393
|
-
- **Bash steps** require \`--allow-bash\` flag for security
|
|
3394
|
-
- **State is persisted** after every step completion \u2014 resume picks up where it left off
|
|
3664
|
+
1. \`relay get <id>\` \u2014 verify endpoint is active with actions configured
|
|
3665
|
+
2. \`relay events --platform <p>\` \u2014 check events are arriving
|
|
3666
|
+
3. \`relay deliveries --event-id <id>\` \u2014 check delivery status and errors
|
|
3667
|
+
4. \`relay event <id>\` \u2014 inspect full payload to verify template paths
|
|
3395
3668
|
`;
|
|
3396
3669
|
var TOPICS = [
|
|
3397
|
-
{ topic: "overview", description: "Setup,
|
|
3670
|
+
{ topic: "overview", description: "Setup, features, and quick start for each" },
|
|
3398
3671
|
{ topic: "actions", description: "Search, read docs, and execute platform actions" },
|
|
3399
3672
|
{ topic: "flows", description: "Build and execute multi-step workflows" },
|
|
3673
|
+
{ topic: "relay", description: "Receive webhooks and forward to other platforms" },
|
|
3400
3674
|
{ topic: "all", description: "Complete guide (all topics combined)" }
|
|
3401
3675
|
];
|
|
3402
3676
|
function getGuideContent(topic) {
|
|
@@ -3407,10 +3681,12 @@ function getGuideContent(topic) {
|
|
|
3407
3681
|
return { title: "One CLI \u2014 Agent Guide: Actions", content: GUIDE_ACTIONS };
|
|
3408
3682
|
case "flows":
|
|
3409
3683
|
return { title: "One CLI \u2014 Agent Guide: Workflows", content: GUIDE_FLOWS };
|
|
3684
|
+
case "relay":
|
|
3685
|
+
return { title: "One CLI \u2014 Agent Guide: Relay", content: GUIDE_RELAY };
|
|
3410
3686
|
case "all":
|
|
3411
3687
|
return {
|
|
3412
3688
|
title: "One CLI \u2014 Agent Guide: Complete",
|
|
3413
|
-
content: [GUIDE_OVERVIEW, GUIDE_ACTIONS, GUIDE_FLOWS].join("\n---\n\n")
|
|
3689
|
+
content: [GUIDE_OVERVIEW, GUIDE_ACTIONS, GUIDE_FLOWS, GUIDE_RELAY].join("\n---\n\n")
|
|
3414
3690
|
};
|
|
3415
3691
|
}
|
|
3416
3692
|
}
|
|
@@ -3419,7 +3695,7 @@ function getAvailableTopics() {
|
|
|
3419
3695
|
}
|
|
3420
3696
|
|
|
3421
3697
|
// src/commands/guide.ts
|
|
3422
|
-
var VALID_TOPICS = ["overview", "actions", "flows", "all"];
|
|
3698
|
+
var VALID_TOPICS = ["overview", "actions", "flows", "relay", "all"];
|
|
3423
3699
|
async function guideCommand(topic = "all") {
|
|
3424
3700
|
if (!VALID_TOPICS.includes(topic)) {
|
|
3425
3701
|
error(
|
|
@@ -3432,14 +3708,14 @@ async function guideCommand(topic = "all") {
|
|
|
3432
3708
|
json({ topic, title, content, availableTopics });
|
|
3433
3709
|
return;
|
|
3434
3710
|
}
|
|
3435
|
-
intro2(
|
|
3711
|
+
intro2(pc9.bgCyan(pc9.black(" One Guide ")));
|
|
3436
3712
|
console.log();
|
|
3437
3713
|
console.log(content);
|
|
3438
|
-
console.log(
|
|
3714
|
+
console.log(pc9.dim("\u2500".repeat(60)));
|
|
3439
3715
|
console.log(
|
|
3440
|
-
|
|
3716
|
+
pc9.dim("Available topics: ") + availableTopics.map((t) => pc9.cyan(t.topic)).join(", ")
|
|
3441
3717
|
);
|
|
3442
|
-
console.log(
|
|
3718
|
+
console.log(pc9.dim(`Run ${pc9.cyan("one guide <topic>")} for a specific section.`));
|
|
3443
3719
|
}
|
|
3444
3720
|
|
|
3445
3721
|
// src/lib/platform-meta.ts
|
|
@@ -3587,18 +3863,30 @@ The \`--agent\` flag gives structured JSON output. Always include it right
|
|
|
3587
3863
|
after \`one\`:
|
|
3588
3864
|
one --agent <command>
|
|
3589
3865
|
|
|
3866
|
+
### IMPORTANT: Learn before you use
|
|
3867
|
+
Before using any feature (actions, flows, relay), you MUST read the
|
|
3868
|
+
corresponding skill documentation first. The skills are bundled with
|
|
3869
|
+
the CLI and teach you the correct workflow, required steps, and
|
|
3870
|
+
common mistakes to avoid. Never guess \u2014 read the skill, then act.
|
|
3871
|
+
|
|
3590
3872
|
### Quick reference:
|
|
3591
3873
|
- \`one --agent list\` \u2014 See connected platforms and connection keys
|
|
3592
3874
|
- \`one --agent actions search <platform> "<query>"\` \u2014 Find actions
|
|
3593
3875
|
- \`one --agent actions knowledge <platform> <actionId>\` \u2014 Read docs (REQUIRED before execute)
|
|
3594
3876
|
- \`one --agent actions execute <platform> <actionId> <connectionKey>\` \u2014 Execute action
|
|
3595
3877
|
- \`one --agent flow create\` \u2014 Build multi-step workflows
|
|
3878
|
+
- \`one --agent relay create\` \u2014 Set up webhook relay (receive events, forward to other platforms)
|
|
3596
3879
|
- \`one --agent guide\` \u2014 Full documentation
|
|
3597
3880
|
- \`one add <platform>\` \u2014 Connect a new platform (interactive, no --agent)
|
|
3598
3881
|
|
|
3599
3882
|
### Workflow: search -> knowledge -> execute
|
|
3600
3883
|
Always read the knowledge before executing. It tells you required parameters,
|
|
3601
3884
|
validation rules, and platform-specific details.
|
|
3885
|
+
|
|
3886
|
+
### Webhook Relay
|
|
3887
|
+
Use \`one relay\` to receive webhooks from platforms (Stripe, GitHub, etc.)
|
|
3888
|
+
and forward event data to other platforms using passthrough actions with
|
|
3889
|
+
Handlebars templates. No middleware needed.
|
|
3602
3890
|
\`\`\``);
|
|
3603
3891
|
sections.push(buildCurrentState(connections));
|
|
3604
3892
|
sections.push(`## How To Use the CLI
|
|
@@ -3624,6 +3912,17 @@ The \`--agent\` flag goes right after \`one\`, before the subcommand.
|
|
|
3624
3912
|
Use \`one flow create\` to build JSON workflows that chain actions across
|
|
3625
3913
|
platforms with conditions, loops, parallel execution, and transforms.
|
|
3626
3914
|
|
|
3915
|
+
### Webhook Relay:
|
|
3916
|
+
Use \`one relay create\` to receive webhooks from platforms (Stripe, GitHub,
|
|
3917
|
+
Airtable, Attio, Google Calendar) and forward event data to any connected
|
|
3918
|
+
platform using passthrough actions with Handlebars templates.
|
|
3919
|
+
|
|
3920
|
+
### IMPORTANT: Learn before you use
|
|
3921
|
+
Before using flows or relay, you MUST read the corresponding skill
|
|
3922
|
+
documentation first. The skills teach you the correct workflow, template
|
|
3923
|
+
syntax, required steps, and common mistakes. Never guess \u2014 read the
|
|
3924
|
+
skill, then act.
|
|
3925
|
+
|
|
3627
3926
|
Run \`one --agent guide\` for the complete reference documentation with examples.`);
|
|
3628
3927
|
sections.push(`## What to tell the user
|
|
3629
3928
|
|
|
@@ -3849,6 +4148,14 @@ program.name("one").option("--agent", "Machine-readable JSON output (no colors,
|
|
|
3849
4148
|
one flow execute <key> Execute a workflow
|
|
3850
4149
|
one flow validate <key> Validate a flow
|
|
3851
4150
|
|
|
4151
|
+
Webhook Relay:
|
|
4152
|
+
one relay create Create a relay endpoint for a connection
|
|
4153
|
+
one relay list List relay endpoints
|
|
4154
|
+
one relay activate <id> Activate with passthrough actions
|
|
4155
|
+
one relay event-types <platform> List supported event types
|
|
4156
|
+
one relay events List received webhook events
|
|
4157
|
+
one relay deliveries List delivery attempts
|
|
4158
|
+
|
|
3852
4159
|
Example \u2014 send an email through Gmail:
|
|
3853
4160
|
$ one list
|
|
3854
4161
|
# Find: gmail operational live::gmail::default::abc123
|
|
@@ -3943,7 +4250,41 @@ flow.command("resume <runId>").description("Resume a paused or failed workflow r
|
|
|
3943
4250
|
flow.command("runs [flowKey]").description("List workflow runs (optionally filtered by flow key)").action(async (flowKey) => {
|
|
3944
4251
|
await flowRunsCommand(flowKey);
|
|
3945
4252
|
});
|
|
3946
|
-
|
|
4253
|
+
flow.command("scaffold [template]").description("Generate a workflow scaffold (templates: basic, conditional, loop, ai)").action(async (template) => {
|
|
4254
|
+
await flowScaffoldCommand(template);
|
|
4255
|
+
});
|
|
4256
|
+
var relay = program.command("relay").alias("r").description("Receive webhooks from platforms and relay them via passthrough actions");
|
|
4257
|
+
relay.command("create").description("Create a new relay endpoint for a connection").requiredOption("--connection-key <key>", "Connection key for the source platform").option("--description <desc>", "Description of the relay endpoint").option("--event-filters <json>", `JSON array of event types to filter (e.g. '["customer.created"]')`).option("--tags <json>", "JSON array of tags").option("--create-webhook", "Automatically register the webhook with the source platform").action(async (options) => {
|
|
4258
|
+
await relayCreateCommand(options);
|
|
4259
|
+
});
|
|
4260
|
+
relay.command("list").alias("ls").description("List all relay endpoints").option("--limit <n>", "Max results per page").option("--page <n>", "Page number").action(async (options) => {
|
|
4261
|
+
await relayListCommand(options);
|
|
4262
|
+
});
|
|
4263
|
+
relay.command("get <id>").description("Get details of a relay endpoint").action(async (id) => {
|
|
4264
|
+
await relayGetCommand(id);
|
|
4265
|
+
});
|
|
4266
|
+
relay.command("update <id>").description("Update a relay endpoint").option("--description <desc>", "Update description").option("--active", "Set active").option("--no-active", "Set inactive").option("--event-filters <json>", "JSON array of event types").option("--tags <json>", "JSON array of tags").option("--actions <json>", "JSON array of actions (url, passthrough, or agent)").action(async (id, options) => {
|
|
4267
|
+
await relayUpdateCommand(id, options);
|
|
4268
|
+
});
|
|
4269
|
+
relay.command("delete <id>").description("Delete a relay endpoint").action(async (id) => {
|
|
4270
|
+
await relayDeleteCommand(id);
|
|
4271
|
+
});
|
|
4272
|
+
relay.command("activate <id>").description("Activate a relay endpoint with forwarding actions").requiredOption("--actions <json>", "JSON array of actions (url, passthrough, or agent)").option("--webhook-secret <secret>", "Webhook signing secret for signature verification").action(async (id, options) => {
|
|
4273
|
+
await relayActivateCommand(id, options);
|
|
4274
|
+
});
|
|
4275
|
+
relay.command("events").description("List received webhook events").option("--limit <n>", "Max results per page").option("--page <n>", "Page number").option("--platform <platform>", "Filter by platform").option("--event-type <type>", "Filter by event type").option("--after <iso>", "Events after this timestamp").option("--before <iso>", "Events before this timestamp").action(async (options) => {
|
|
4276
|
+
await relayEventsCommand(options);
|
|
4277
|
+
});
|
|
4278
|
+
relay.command("event <id>").description("Get details of a specific webhook event").action(async (id) => {
|
|
4279
|
+
await relayEventGetCommand(id);
|
|
4280
|
+
});
|
|
4281
|
+
relay.command("deliveries").description("List delivery attempts for an endpoint or event").option("--endpoint-id <id>", "Relay endpoint ID").option("--event-id <id>", "Relay event ID").action(async (options) => {
|
|
4282
|
+
await relayDeliveriesCommand(options);
|
|
4283
|
+
});
|
|
4284
|
+
relay.command("event-types <platform>").description("List supported webhook event types for a platform").action(async (platform) => {
|
|
4285
|
+
await relayEventTypesCommand(platform);
|
|
4286
|
+
});
|
|
4287
|
+
program.command("guide [topic]").description("Full CLI usage guide for agents (topics: overview, actions, flows, relay, all)").action(async (topic) => {
|
|
3947
4288
|
await guideCommand(topic);
|
|
3948
4289
|
});
|
|
3949
4290
|
program.command("onboard").description("Agent onboarding \u2014 teaches your agent what the One CLI can do").option("--step <number>", "Run a specific onboarding step (1, 2, or 3)").action(async (options) => {
|