@withone/cli 1.40.0 → 1.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/{chunk-SSEDFOVV.js → chunk-PK2RAVAF.js} +65 -36
- package/dist/{flow-runner-2KVAPP6E.js → flow-runner-Y2CXXU3U.js} +1 -1
- package/dist/index.js +94 -40
- package/package.json +1 -1
- package/skills/one/references/flows.md +8 -1
- package/skills/one/references/relay.md +15 -0
package/README.md
CHANGED
|
@@ -349,6 +349,22 @@ one sync run stripe --full-refresh
|
|
|
349
349
|
|
|
350
350
|
Change hooks (`onInsert`, `onUpdate`, `onChange`) fire per-page during sync — pipe to a shell command, a flow, or an event log. Root-array responses (e.g. Hacker News `/v0/topstories.json` → `[9129911, 9129199, ...]`) are supported by setting `resultsPath` to `""`, `"$"`, or `"."`; primitive elements are auto-wrapped as `{ [idField]: value }`. Run `one guide sync` for the full reference.
|
|
351
351
|
|
|
352
|
+
### `one relay`
|
|
353
|
+
|
|
354
|
+
Receive webhooks from platforms and forward them to any connected platform via passthrough actions.
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
one relay platforms # List relay-capable platforms + event type counts
|
|
358
|
+
one relay event-types <platform> # List supported event types for a platform
|
|
359
|
+
one relay create --connection-key <key> --create-webhook --event-filters '["event.type"]'
|
|
360
|
+
one relay activate <id> --actions '<json>' # Attach passthrough forwarding actions
|
|
361
|
+
one relay list # List existing relay endpoints
|
|
362
|
+
one relay events --platform <p> # Inspect received events
|
|
363
|
+
one relay deliveries --endpoint-id <id> # Check delivery status
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Start with `one relay platforms` to discover which platforms support relay at all, then drill into `event-types <platform>` for the specific events. Run `one guide relay` for the full reference including `--metadata` requirements per platform.
|
|
367
|
+
|
|
352
368
|
### `one guide [topic]`
|
|
353
369
|
|
|
354
370
|
Get the full CLI usage guide, designed for AI agents that only have the binary (no MCP, no IDE skills).
|
|
@@ -442,6 +442,9 @@ var OneApi = class {
|
|
|
442
442
|
async listRelayEventTypes(platform) {
|
|
443
443
|
return this.requestFull({ path: "/webhooks/relay/event-types", queryParams: { platform } });
|
|
444
444
|
}
|
|
445
|
+
async listRelayPlatforms() {
|
|
446
|
+
return this.requestFull({ path: "/webhooks/relay/platforms" });
|
|
447
|
+
}
|
|
445
448
|
async waitForConnection(platform, timeoutMs = 5 * 60 * 1e3, pollIntervalMs = 5e3, onPoll) {
|
|
446
449
|
const startTime = Date.now();
|
|
447
450
|
const existingConnections = await this.listConnections();
|
|
@@ -811,7 +814,29 @@ async function executeActionStep(step, context, api, permissions, allowedActionI
|
|
|
811
814
|
const action = step.action;
|
|
812
815
|
const platform = resolveValue(action.platform, context);
|
|
813
816
|
const actionId = resolveValue(action.actionId, context);
|
|
814
|
-
const
|
|
817
|
+
const hasKey = action.connectionKey !== void 0 && action.connectionKey !== null && action.connectionKey !== "";
|
|
818
|
+
const hasRef = !!action.connection?.platform;
|
|
819
|
+
if (hasKey && hasRef) {
|
|
820
|
+
throw new Error(
|
|
821
|
+
`Action step "${step.id}" has both "connectionKey" and "connection" \u2014 set exactly one. Prefer "connection: { platform, tag? }" so re-auth doesn't break the flow.`
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
if (!hasKey && !hasRef) {
|
|
825
|
+
throw new Error(
|
|
826
|
+
`Action step "${step.id}" must set "connection: { platform: <name> }" (or legacy "connectionKey: <key>").`
|
|
827
|
+
);
|
|
828
|
+
}
|
|
829
|
+
let connectionKey;
|
|
830
|
+
if (hasKey) {
|
|
831
|
+
connectionKey = resolveValue(action.connectionKey, context);
|
|
832
|
+
} else {
|
|
833
|
+
const ref = resolveValue(action.connection, context);
|
|
834
|
+
if (!context._connections) {
|
|
835
|
+
context._connections = await api.listConnections();
|
|
836
|
+
}
|
|
837
|
+
const conn = await api.resolveConnection(ref, context._connections);
|
|
838
|
+
connectionKey = conn.key;
|
|
839
|
+
}
|
|
815
840
|
const data = action.data ? resolveValue(action.data, context) : void 0;
|
|
816
841
|
const pathVars = action.pathVars ? resolveValue(action.pathVars, context) : void 0;
|
|
817
842
|
const queryParams = action.queryParams ? resolveValue(action.queryParams, context) : void 0;
|
|
@@ -1115,7 +1140,7 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
|
|
|
1115
1140
|
if (flowStack.includes(resolvedKey)) {
|
|
1116
1141
|
throw new Error(`Circular flow detected: ${[...flowStack, resolvedKey].join(" \u2192 ")}`);
|
|
1117
1142
|
}
|
|
1118
|
-
const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-
|
|
1143
|
+
const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-Y2CXXU3U.js");
|
|
1119
1144
|
const { flow: subFlow, rootDir: subRootDir } = loadFlowWithMeta2(resolvedKey);
|
|
1120
1145
|
const subContext = await executeFlow(
|
|
1121
1146
|
subFlow,
|
|
@@ -1661,7 +1686,8 @@ var FLOW_SCHEMA = {
|
|
|
1661
1686
|
fields: {
|
|
1662
1687
|
platform: { type: "string", required: true, description: "Platform name (kebab-case)" },
|
|
1663
1688
|
actionId: { type: "string", required: true, description: "Action ID from `actions search`" },
|
|
1664
|
-
|
|
1689
|
+
connection: { type: "object", required: false, description: "Late-bound connection ref { platform, tag? } \u2014 survives re-auth. Exactly one of `connection` or `connectionKey` must be set." },
|
|
1690
|
+
connectionKey: { type: "string", required: false, description: "Literal connection key (or $.input selector). Legacy form \u2014 prefer `connection: { platform, tag? }`. Exactly one of `connection` or `connectionKey` must be set." },
|
|
1665
1691
|
data: { type: "object", required: false, description: "Request body (POST/PUT/PATCH)" },
|
|
1666
1692
|
pathVars: { type: "object", required: false, description: "URL path variables" },
|
|
1667
1693
|
queryParams: { type: "object", required: false, description: "Query parameters" },
|
|
@@ -1674,7 +1700,7 @@ var FLOW_SCHEMA = {
|
|
|
1674
1700
|
action: {
|
|
1675
1701
|
platform: "stripe",
|
|
1676
1702
|
actionId: "conn_mod_def::xxx::yyy",
|
|
1677
|
-
|
|
1703
|
+
connection: { platform: "stripe" },
|
|
1678
1704
|
data: { query: "email:'{{$.input.customerEmail}}'" }
|
|
1679
1705
|
}
|
|
1680
1706
|
}
|
|
@@ -1723,7 +1749,7 @@ var FLOW_SCHEMA = {
|
|
|
1723
1749
|
type: "condition",
|
|
1724
1750
|
condition: {
|
|
1725
1751
|
expression: "$.steps.search.response.data.length > 0",
|
|
1726
|
-
then: [{ id: "notify", name: "Send notification", type: "action", action: { platform: "slack", actionId: "...",
|
|
1752
|
+
then: [{ id: "notify", name: "Send notification", type: "action", action: { platform: "slack", actionId: "...", connection: { platform: "slack" }, data: { text: "Found!" } } }],
|
|
1727
1753
|
else: [{ id: "logMiss", name: "Log not found", type: "transform", transform: { expression: "'Not found'" } }]
|
|
1728
1754
|
}
|
|
1729
1755
|
}
|
|
@@ -1747,7 +1773,7 @@ var FLOW_SCHEMA = {
|
|
|
1747
1773
|
loop: {
|
|
1748
1774
|
over: "$.steps.listOrders.response.data",
|
|
1749
1775
|
as: "order",
|
|
1750
|
-
steps: [{ id: "createInvoice", name: "Create invoice", type: "action", action: { platform: "stripe", actionId: "...",
|
|
1776
|
+
steps: [{ id: "createInvoice", name: "Create invoice", type: "action", action: { platform: "stripe", actionId: "...", connection: { platform: "stripe" }, data: { amount: "$.loop.order.total" } } }]
|
|
1751
1777
|
}
|
|
1752
1778
|
}
|
|
1753
1779
|
},
|
|
@@ -1765,8 +1791,8 @@ var FLOW_SCHEMA = {
|
|
|
1765
1791
|
type: "parallel",
|
|
1766
1792
|
parallel: {
|
|
1767
1793
|
steps: [
|
|
1768
|
-
{ id: "getStripe", name: "Get Stripe data", type: "action", action: { platform: "stripe", actionId: "...",
|
|
1769
|
-
{ id: "getSlack", name: "Get Slack data", type: "action", action: { platform: "slack", actionId: "...",
|
|
1794
|
+
{ id: "getStripe", name: "Get Stripe data", type: "action", action: { platform: "stripe", actionId: "...", connection: { platform: "stripe" } } },
|
|
1795
|
+
{ id: "getSlack", name: "Get Slack data", type: "action", action: { platform: "slack", actionId: "...", connection: { platform: "slack" } } }
|
|
1770
1796
|
]
|
|
1771
1797
|
}
|
|
1772
1798
|
}
|
|
@@ -1818,7 +1844,7 @@ var FLOW_SCHEMA = {
|
|
|
1818
1844
|
while: {
|
|
1819
1845
|
condition: "$.steps.paginate.output.lastResult.nextPageToken != null",
|
|
1820
1846
|
maxIterations: 50,
|
|
1821
|
-
steps: [{ id: "fetchPage", name: "Fetch next page", type: "action", action: { platform: "gmail", actionId: "...",
|
|
1847
|
+
steps: [{ id: "fetchPage", name: "Fetch next page", type: "action", action: { platform: "gmail", actionId: "...", connection: { platform: "gmail" } } }]
|
|
1822
1848
|
}
|
|
1823
1849
|
}
|
|
1824
1850
|
},
|
|
@@ -1842,7 +1868,7 @@ var FLOW_SCHEMA = {
|
|
|
1842
1868
|
configKey: "paginate",
|
|
1843
1869
|
description: "Auto-paginate API results into a single array",
|
|
1844
1870
|
fields: {
|
|
1845
|
-
action: { type: "object", required: true, description: "Action config (same shape as action step: platform, actionId, connectionKey)" },
|
|
1871
|
+
action: { type: "object", required: true, description: "Action config (same shape as action step: platform, actionId, plus exactly one of `connection` or `connectionKey`)" },
|
|
1846
1872
|
pageTokenField: { type: "string", required: true, description: "Dot-path in response to next page token" },
|
|
1847
1873
|
resultsField: { type: "string", required: true, description: "Dot-path in response to results array" },
|
|
1848
1874
|
inputTokenParam: { type: "string", required: true, description: "Dot-path in action config where page token is injected" },
|
|
@@ -1853,7 +1879,7 @@ var FLOW_SCHEMA = {
|
|
|
1853
1879
|
name: "Fetch all Gmail messages",
|
|
1854
1880
|
type: "paginate",
|
|
1855
1881
|
paginate: {
|
|
1856
|
-
action: { platform: "gmail", actionId: "...",
|
|
1882
|
+
action: { platform: "gmail", actionId: "...", connection: { platform: "gmail" }, queryParams: { maxResults: 100 } },
|
|
1857
1883
|
pageTokenField: "nextPageToken",
|
|
1858
1884
|
resultsField: "messages",
|
|
1859
1885
|
inputTokenParam: "queryParams.pageToken",
|
|
@@ -2051,12 +2077,6 @@ The only differences: (1) prepend the stdin-read line, (2) replace \`return X\`
|
|
|
2051
2077
|
"description": "What this flow does",
|
|
2052
2078
|
"version": "1",
|
|
2053
2079
|
"inputs": {
|
|
2054
|
-
"connectionKey": {
|
|
2055
|
-
"type": "string",
|
|
2056
|
-
"required": true,
|
|
2057
|
-
"description": "Platform connection key",
|
|
2058
|
-
"connection": { "platform": "stripe" }
|
|
2059
|
-
},
|
|
2060
2080
|
"param": {
|
|
2061
2081
|
"type": "string",
|
|
2062
2082
|
"required": true,
|
|
@@ -2071,7 +2091,7 @@ The only differences: (1) prepend the stdin-read line, (2) replace \`return X\`
|
|
|
2071
2091
|
"action": {
|
|
2072
2092
|
"platform": "stripe",
|
|
2073
2093
|
"actionId": "conn_mod_def::xxx::yyy",
|
|
2074
|
-
"
|
|
2094
|
+
"connection": { "platform": "stripe" },
|
|
2075
2095
|
"data": { "query": "{{$.input.param}}" }
|
|
2076
2096
|
}
|
|
2077
2097
|
}
|
|
@@ -2165,13 +2185,13 @@ Pipes can be applied to any value (objects/arrays are JSON-stringified first for
|
|
|
2165
2185
|
|
|
2166
2186
|
### When to use bare selectors vs \`{{...}}\` interpolation
|
|
2167
2187
|
|
|
2168
|
-
- **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).
|
|
2188
|
+
- **Bare selectors** (\`$.input.x\`): Use for fields the engine resolves directly \u2014 \`connectionKey\`, the \`tag\` (or \`platform\`) inside \`connection\`, \`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).
|
|
2169
2189
|
- **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.
|
|
2170
2190
|
- **Rule of thumb**: If the value is purely a selector, use bare. If it's a string containing a selector, use \`{{...}}\`.
|
|
2171
2191
|
|
|
2172
2192
|
### Selectors vs expressions
|
|
2173
2193
|
|
|
2174
|
-
Selectors in data fields (\`data\`, \`queryParams\`, \`pathVars\`, \`connectionKey\`) are **dot-path lookups only** \u2014 they do not support JavaScript operators like \`||\` or \`&&\`. For default values, use the \`default\` field on the input definition:
|
|
2194
|
+
Selectors in data fields (\`data\`, \`queryParams\`, \`pathVars\`, \`connectionKey\`, \`connection.tag\`) are **dot-path lookups only** \u2014 they do not support JavaScript operators like \`||\` or \`&&\`. For default values, use the \`default\` field on the input definition:
|
|
2175
2195
|
|
|
2176
2196
|
\`\`\`json
|
|
2177
2197
|
{ "inputs": { "maxResults": { "type": "number", "default": 10 } } }
|
|
@@ -2291,9 +2311,30 @@ A \`flow\` step's \`flow.key\` accepts selectors and Handlebars interpolations,
|
|
|
2291
2311
|
|
|
2292
2312
|
Conditional execution: \`"if": "$.steps.prev.response.data.length > 0"\`
|
|
2293
2313
|
|
|
2294
|
-
##
|
|
2314
|
+
## Connection Resolution \u2014 late-bound by default
|
|
2315
|
+
|
|
2316
|
+
Action steps reference a platform connection in one of two forms:
|
|
2317
|
+
|
|
2318
|
+
\`\`\`json
|
|
2319
|
+
// preferred \u2014 late-bound, survives re-auth
|
|
2320
|
+
"connection": { "platform": "gmail" }
|
|
2295
2321
|
|
|
2296
|
-
|
|
2322
|
+
// multi-account: disambiguate with the connection's tag
|
|
2323
|
+
"connection": { "platform": "gmail", "tag": "work@example.com" }
|
|
2324
|
+
|
|
2325
|
+
// legacy \u2014 works for backwards compat, breaks on re-auth
|
|
2326
|
+
"connectionKey": "live::gmail::default::abc123..."
|
|
2327
|
+
\`\`\`
|
|
2328
|
+
|
|
2329
|
+
The engine resolves the \`connection\` ref once per flow run (cached for the run's lifetime) by calling \`listConnections\` and matching on platform + optional tag. Resolution errors fail the step with a clear message \u2014 \`No connection found for platform "X"\`, \`Multiple "X" connections found (tags: ...). Add a "tag" field\`, or \`No "X" connection has tag "Y"\`.
|
|
2330
|
+
|
|
2331
|
+
Both \`platform\` and \`tag\` accept \`$.input.x\` selectors so a flow can be parameterised per-execution (e.g. multi-tenant orchestrators that pass the user's email as the tag).
|
|
2332
|
+
|
|
2333
|
+
The validator rejects any action that sets both forms or neither, at \`flow validate\` and \`flow execute\` time.
|
|
2334
|
+
|
|
2335
|
+
### Optional input metadata: connection-key auto-resolve (legacy)
|
|
2336
|
+
|
|
2337
|
+
For flows that still use literal \`connectionKey\` strings via inputs, an input declaration can carry a \`"connection": { "platform": "..." }\` hint so \`flow execute\` auto-fills a single matching connection's key. New flows don't need this \u2014 switch the action's connection form to \`{ platform, tag? }\` and skip the input entirely.
|
|
2297
2338
|
|
|
2298
2339
|
## Complete Example: Fetch Data, Transform, Notify
|
|
2299
2340
|
|
|
@@ -2304,18 +2345,6 @@ When an input has \`"connection": { "platform": "stripe" }\`, the flow engine ca
|
|
|
2304
2345
|
"description": "Fetch recent contacts from CRM, build a summary, post to Slack",
|
|
2305
2346
|
"version": "1",
|
|
2306
2347
|
"inputs": {
|
|
2307
|
-
"crmConnectionKey": {
|
|
2308
|
-
"type": "string",
|
|
2309
|
-
"required": true,
|
|
2310
|
-
"description": "CRM platform connection key",
|
|
2311
|
-
"connection": { "platform": "attio" }
|
|
2312
|
-
},
|
|
2313
|
-
"slackConnectionKey": {
|
|
2314
|
-
"type": "string",
|
|
2315
|
-
"required": true,
|
|
2316
|
-
"description": "Slack connection key",
|
|
2317
|
-
"connection": { "platform": "slack" }
|
|
2318
|
-
},
|
|
2319
2348
|
"slackChannel": {
|
|
2320
2349
|
"type": "string",
|
|
2321
2350
|
"required": true,
|
|
@@ -2330,7 +2359,7 @@ When an input has \`"connection": { "platform": "stripe" }\`, the flow engine ca
|
|
|
2330
2359
|
"action": {
|
|
2331
2360
|
"platform": "attio",
|
|
2332
2361
|
"actionId": "ATTIO_LIST_PEOPLE_ACTION_ID",
|
|
2333
|
-
"
|
|
2362
|
+
"connection": { "platform": "attio" },
|
|
2334
2363
|
"queryParams": { "limit": "10" }
|
|
2335
2364
|
}
|
|
2336
2365
|
},
|
|
@@ -2349,7 +2378,7 @@ When an input has \`"connection": { "platform": "stripe" }\`, the flow engine ca
|
|
|
2349
2378
|
"action": {
|
|
2350
2379
|
"platform": "slack",
|
|
2351
2380
|
"actionId": "SLACK_SEND_MESSAGE_ACTION_ID",
|
|
2352
|
-
"
|
|
2381
|
+
"connection": { "platform": "slack" },
|
|
2353
2382
|
"data": {
|
|
2354
2383
|
"channel": "$.input.slackChannel",
|
|
2355
2384
|
"text": "{{$.steps.buildSummary.output.summary}}"
|
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
resolveFlowPath,
|
|
20
20
|
saveFlow,
|
|
21
21
|
validateActionInput
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-PK2RAVAF.js";
|
|
23
23
|
|
|
24
24
|
// src/index.ts
|
|
25
25
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -3269,10 +3269,13 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
3269
3269
|
const a = value;
|
|
3270
3270
|
if (!a.platform) errors.push({ path: `${fieldPath}.platform`, message: 'Action must have "platform"' });
|
|
3271
3271
|
if (!a.actionId) errors.push({ path: `${fieldPath}.actionId`, message: 'Action must have "actionId"' });
|
|
3272
|
-
|
|
3272
|
+
validateConnectionForm(a, fieldPath, errors);
|
|
3273
3273
|
}
|
|
3274
3274
|
}
|
|
3275
3275
|
}
|
|
3276
|
+
if (descriptor.type === "action") {
|
|
3277
|
+
validateConnectionForm(config2, `${path17}.${configKey}`, errors);
|
|
3278
|
+
}
|
|
3276
3279
|
if (descriptor.type === "code") {
|
|
3277
3280
|
const hasSource = typeof config2.source === "string" && config2.source.length > 0;
|
|
3278
3281
|
const hasModule = typeof config2.module === "string" && config2.module.length > 0;
|
|
@@ -3300,6 +3303,43 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
3300
3303
|
}
|
|
3301
3304
|
}
|
|
3302
3305
|
}
|
|
3306
|
+
function validateConnectionForm(config2, pathPrefix, errors) {
|
|
3307
|
+
const hasKey = config2.connectionKey !== void 0 && config2.connectionKey !== null && config2.connectionKey !== "";
|
|
3308
|
+
const conn = config2.connection;
|
|
3309
|
+
const hasRef = !!conn && typeof conn === "object" && !Array.isArray(conn) && typeof conn.platform === "string" && conn.platform.length > 0;
|
|
3310
|
+
if (hasKey && hasRef) {
|
|
3311
|
+
errors.push({
|
|
3312
|
+
path: pathPrefix,
|
|
3313
|
+
message: `Action has both "connectionKey" and "connection" \u2014 set exactly one. Prefer "connection: { platform, tag? }" so re-auth doesn't break the flow.`
|
|
3314
|
+
});
|
|
3315
|
+
return;
|
|
3316
|
+
}
|
|
3317
|
+
if (!hasKey && !hasRef) {
|
|
3318
|
+
errors.push({
|
|
3319
|
+
path: pathPrefix,
|
|
3320
|
+
message: 'Action must set "connection: { platform: <name> }" (or legacy "connectionKey: <key>").'
|
|
3321
|
+
});
|
|
3322
|
+
return;
|
|
3323
|
+
}
|
|
3324
|
+
if (hasRef) {
|
|
3325
|
+
const c = conn;
|
|
3326
|
+
if (c.tag !== void 0 && typeof c.tag !== "string") {
|
|
3327
|
+
errors.push({
|
|
3328
|
+
path: `${pathPrefix}.connection.tag`,
|
|
3329
|
+
message: '"connection.tag" must be a string when set'
|
|
3330
|
+
});
|
|
3331
|
+
}
|
|
3332
|
+
const allowed = /* @__PURE__ */ new Set(["platform", "tag"]);
|
|
3333
|
+
for (const k of Object.keys(c)) {
|
|
3334
|
+
if (!allowed.has(k)) {
|
|
3335
|
+
errors.push({
|
|
3336
|
+
path: `${pathPrefix}.connection.${k}`,
|
|
3337
|
+
message: `Unknown field "${k}" in connection ref. Allowed: platform, tag.`
|
|
3338
|
+
});
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3303
3343
|
function detectFlatConfigHint(step, descriptor) {
|
|
3304
3344
|
const requiredFields = Object.entries(descriptor.fields).filter(([, fd]) => fd.required).map(([name]) => name);
|
|
3305
3345
|
const flatFields = requiredFields.filter((f) => f in step);
|
|
@@ -4107,14 +4147,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4107
4147
|
name: "My Workflow",
|
|
4108
4148
|
description: "A basic workflow with a single action step",
|
|
4109
4149
|
version: "1",
|
|
4110
|
-
inputs: {
|
|
4111
|
-
connectionKey: {
|
|
4112
|
-
type: "string",
|
|
4113
|
-
required: true,
|
|
4114
|
-
description: "Connection key for the platform",
|
|
4115
|
-
connection: { platform: "PLATFORM_NAME" }
|
|
4116
|
-
}
|
|
4117
|
-
},
|
|
4150
|
+
inputs: {},
|
|
4118
4151
|
steps: [
|
|
4119
4152
|
{
|
|
4120
4153
|
id: "step1",
|
|
@@ -4123,7 +4156,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4123
4156
|
action: {
|
|
4124
4157
|
platform: "PLATFORM_NAME",
|
|
4125
4158
|
actionId: "ACTION_ID_FROM_SEARCH",
|
|
4126
|
-
|
|
4159
|
+
connection: { platform: "PLATFORM_NAME" },
|
|
4127
4160
|
data: {}
|
|
4128
4161
|
}
|
|
4129
4162
|
}
|
|
@@ -4134,14 +4167,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4134
4167
|
name: "Conditional Workflow",
|
|
4135
4168
|
description: "Fetch data, then branch based on results",
|
|
4136
4169
|
version: "1",
|
|
4137
|
-
inputs: {
|
|
4138
|
-
connectionKey: {
|
|
4139
|
-
type: "string",
|
|
4140
|
-
required: true,
|
|
4141
|
-
description: "Connection key",
|
|
4142
|
-
connection: { platform: "PLATFORM_NAME" }
|
|
4143
|
-
}
|
|
4144
|
-
},
|
|
4170
|
+
inputs: {},
|
|
4145
4171
|
steps: [
|
|
4146
4172
|
{
|
|
4147
4173
|
id: "fetch",
|
|
@@ -4150,7 +4176,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4150
4176
|
action: {
|
|
4151
4177
|
platform: "PLATFORM_NAME",
|
|
4152
4178
|
actionId: "ACTION_ID_FROM_SEARCH",
|
|
4153
|
-
|
|
4179
|
+
connection: { platform: "PLATFORM_NAME" }
|
|
4154
4180
|
}
|
|
4155
4181
|
},
|
|
4156
4182
|
{
|
|
@@ -4184,14 +4210,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4184
4210
|
name: "Loop Workflow",
|
|
4185
4211
|
description: "Fetch a list, then process each item",
|
|
4186
4212
|
version: "1",
|
|
4187
|
-
inputs: {
|
|
4188
|
-
connectionKey: {
|
|
4189
|
-
type: "string",
|
|
4190
|
-
required: true,
|
|
4191
|
-
description: "Connection key",
|
|
4192
|
-
connection: { platform: "PLATFORM_NAME" }
|
|
4193
|
-
}
|
|
4194
|
-
},
|
|
4213
|
+
inputs: {},
|
|
4195
4214
|
steps: [
|
|
4196
4215
|
{
|
|
4197
4216
|
id: "fetchList",
|
|
@@ -4200,7 +4219,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4200
4219
|
action: {
|
|
4201
4220
|
platform: "PLATFORM_NAME",
|
|
4202
4221
|
actionId: "ACTION_ID_FROM_SEARCH",
|
|
4203
|
-
|
|
4222
|
+
connection: { platform: "PLATFORM_NAME" }
|
|
4204
4223
|
}
|
|
4205
4224
|
},
|
|
4206
4225
|
{
|
|
@@ -4233,14 +4252,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4233
4252
|
name: "AI Analysis Workflow",
|
|
4234
4253
|
description: "Fetch data, analyze with Claude, and send results",
|
|
4235
4254
|
version: "1",
|
|
4236
|
-
inputs: {
|
|
4237
|
-
connectionKey: {
|
|
4238
|
-
type: "string",
|
|
4239
|
-
required: true,
|
|
4240
|
-
description: "Connection key for data source",
|
|
4241
|
-
connection: { platform: "PLATFORM_NAME" }
|
|
4242
|
-
}
|
|
4243
|
-
},
|
|
4255
|
+
inputs: {},
|
|
4244
4256
|
steps: [
|
|
4245
4257
|
{
|
|
4246
4258
|
id: "fetchData",
|
|
@@ -4249,7 +4261,7 @@ var SCAFFOLD_TEMPLATES = {
|
|
|
4249
4261
|
action: {
|
|
4250
4262
|
platform: "PLATFORM_NAME",
|
|
4251
4263
|
actionId: "ACTION_ID_FROM_SEARCH",
|
|
4252
|
-
|
|
4264
|
+
connection: { platform: "PLATFORM_NAME" }
|
|
4253
4265
|
}
|
|
4254
4266
|
},
|
|
4255
4267
|
{
|
|
@@ -4631,6 +4643,42 @@ async function relayDeliveriesCommand(options) {
|
|
|
4631
4643
|
error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
|
|
4632
4644
|
}
|
|
4633
4645
|
}
|
|
4646
|
+
async function relayPlatformsCommand() {
|
|
4647
|
+
const { apiKey } = getConfig3();
|
|
4648
|
+
const api = new OneApi(apiKey, getApiBase());
|
|
4649
|
+
const spinner6 = createSpinner();
|
|
4650
|
+
spinner6.start("Loading relay-capable platforms...");
|
|
4651
|
+
try {
|
|
4652
|
+
const platforms = await api.listRelayPlatforms();
|
|
4653
|
+
if (isAgentMode()) {
|
|
4654
|
+
json({ platforms });
|
|
4655
|
+
return;
|
|
4656
|
+
}
|
|
4657
|
+
spinner6.stop(`${platforms.length} relay-capable platform${platforms.length === 1 ? "" : "s"} found`);
|
|
4658
|
+
if (platforms.length === 0) {
|
|
4659
|
+
console.log("\n No relay-capable platforms available.\n");
|
|
4660
|
+
return;
|
|
4661
|
+
}
|
|
4662
|
+
console.log();
|
|
4663
|
+
printTable(
|
|
4664
|
+
[
|
|
4665
|
+
{ key: "platform", label: "Platform" },
|
|
4666
|
+
{ key: "eventTypeCount", label: "Event types", color: pc8.dim }
|
|
4667
|
+
],
|
|
4668
|
+
platforms.map((p10) => ({ platform: p10.platform, eventTypeCount: String(p10.eventTypeCount) }))
|
|
4669
|
+
);
|
|
4670
|
+
console.log();
|
|
4671
|
+
console.log(
|
|
4672
|
+
pc8.dim(
|
|
4673
|
+
` Run ${pc8.cyan("one relay event-types <platform>")} to see the full event list for a platform.
|
|
4674
|
+
`
|
|
4675
|
+
)
|
|
4676
|
+
);
|
|
4677
|
+
} catch (error2) {
|
|
4678
|
+
spinner6.stop("Failed to load relay platforms");
|
|
4679
|
+
error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4634
4682
|
async function relayEventTypesCommand(platform) {
|
|
4635
4683
|
const { apiKey } = getConfig3();
|
|
4636
4684
|
const api = new OneApi(apiKey, getApiBase());
|
|
@@ -7383,7 +7431,7 @@ function getApi() {
|
|
|
7383
7431
|
if (!apiKey) {
|
|
7384
7432
|
error('No API key configured. Run "one init" first.');
|
|
7385
7433
|
}
|
|
7386
|
-
return new OneApi(apiKey);
|
|
7434
|
+
return new OneApi(apiKey, getApiBase());
|
|
7387
7435
|
}
|
|
7388
7436
|
async function syncProfilesCommand(platform) {
|
|
7389
7437
|
const profiles = listBuiltinProfiles(platform);
|
|
@@ -8302,6 +8350,7 @@ Receive webhooks from platforms (Stripe, GitHub, Airtable, Attio, Google Calenda
|
|
|
8302
8350
|
|
|
8303
8351
|
**Quick start:**
|
|
8304
8352
|
\`\`\`bash
|
|
8353
|
+
one --agent relay platforms # See relay-capable platforms
|
|
8305
8354
|
one --agent relay event-types <platform> # See available events
|
|
8306
8355
|
one --agent relay create --connection-key <key> --create-webhook --event-filters '["event.type"]'
|
|
8307
8356
|
one --agent relay activate <id> --actions '[{"type":"passthrough","actionId":"...","connectionKey":"...","body":{...}}]'
|
|
@@ -8447,6 +8496,7 @@ Webhook relay receives events from platforms (Stripe, GitHub, Airtable, Attio, G
|
|
|
8447
8496
|
## Commands
|
|
8448
8497
|
|
|
8449
8498
|
\`\`\`bash
|
|
8499
|
+
one --agent relay platforms # List relay-capable platforms + event type counts
|
|
8450
8500
|
one --agent relay event-types <platform> # List available events
|
|
8451
8501
|
one --agent relay create --connection-key <key> --create-webhook --event-filters '["event.type"]'
|
|
8452
8502
|
one --agent relay activate <id> --actions '<json>' # Add forwarding actions
|
|
@@ -8461,7 +8511,7 @@ one --agent relay deliveries --endpoint-id <id> # Check delivery status
|
|
|
8461
8511
|
|
|
8462
8512
|
## Building a Relay
|
|
8463
8513
|
|
|
8464
|
-
1. **Discover connections** \u2014 identify source and destination platforms
|
|
8514
|
+
1. **Discover connections** \u2014 identify source and destination platforms. Use \`one --agent relay platforms\` to see which platforms support relay at all before digging into a specific one.
|
|
8465
8515
|
2. **Get event types** \u2014 \`one --agent relay event-types <platform>\`
|
|
8466
8516
|
3. **Get source knowledge** \u2014 understand the incoming webhook payload shape (\`{{payload.*}}\` paths)
|
|
8467
8517
|
4. **Get destination knowledge** \u2014 understand the outgoing API body shape
|
|
@@ -9460,6 +9510,7 @@ program.name("one").option("--agent", "Machine-readable JSON output (no colors,
|
|
|
9460
9510
|
one cache update-all Re-fetch fresh data for all cached entries
|
|
9461
9511
|
|
|
9462
9512
|
Webhook Relay:
|
|
9513
|
+
one relay platforms List platforms that support relay + event type counts
|
|
9463
9514
|
one relay create Create a relay endpoint for a connection
|
|
9464
9515
|
one relay list List relay endpoints
|
|
9465
9516
|
one relay activate <id> Activate with passthrough actions
|
|
@@ -9763,6 +9814,9 @@ relay.command("deliveries").description("List delivery attempts for an endpoint
|
|
|
9763
9814
|
relay.command("event-types <platform>").description("List supported webhook event types for a platform").action(async (platform) => {
|
|
9764
9815
|
await relayEventTypesCommand(platform);
|
|
9765
9816
|
});
|
|
9817
|
+
relay.command("platforms").description("List all platforms that support webhook relay, with their event type counts").action(async () => {
|
|
9818
|
+
await relayPlatformsCommand();
|
|
9819
|
+
});
|
|
9766
9820
|
registerSyncCommands(program);
|
|
9767
9821
|
var cache = program.command("cache").description("Manage the local knowledge and search cache");
|
|
9768
9822
|
cache.command("clear [actionId]").description("Clear all cached data, or a specific action by ID").action(async (actionId) => {
|
package/package.json
CHANGED
|
@@ -282,12 +282,19 @@ The `if`, `unless`, `condition.expression`, `while.condition`, `transform.expres
|
|
|
282
282
|
"action": {
|
|
283
283
|
"platform": "stripe",
|
|
284
284
|
"actionId": "conn_mod_def::xxx::yyy",
|
|
285
|
-
"
|
|
285
|
+
"connection": { "platform": "stripe" },
|
|
286
286
|
"data": { "query": "email:'{{$.input.customerEmail}}'" }
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
289
|
```
|
|
290
290
|
|
|
291
|
+
**Connection forms.** Each action step sets exactly one of:
|
|
292
|
+
|
|
293
|
+
- **`connection: { platform: "<name>", "tag"?: "<tag>" }`** (preferred) — late-bound, resolved at flow-execute time. Survives re-auth (which always mints a new key). Use `tag` to disambiguate when a platform has multiple connections (e.g. multi-account Gmail). Both `platform` and `tag` accept `$.input.x` selectors so flows can be parameterised per-execution.
|
|
294
|
+
- **`connectionKey: "<literal-or-selector>"`** (legacy) — passes the key string straight through. Still supported for backwards compat, but breaks on re-auth and forces manual edits across every flow that references the stale key. Migrate to `connection` when convenient.
|
|
295
|
+
|
|
296
|
+
The validator rejects an action that sets both forms (or neither) at `flow validate` and `flow execute` time.
|
|
297
|
+
|
|
291
298
|
### `transform` — JS expression (implicit return)
|
|
292
299
|
|
|
293
300
|
```json
|
|
@@ -16,6 +16,14 @@ one --agent connection list
|
|
|
16
16
|
|
|
17
17
|
Identify source (sends webhooks) and destination (receives forwarded data). Note both connection keys.
|
|
18
18
|
|
|
19
|
+
If you're unsure whether the source platform supports relay at all, list relay-capable platforms first:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
one --agent relay platforms
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This returns `[{ "platform": "<name>", "eventTypeCount": <n> }, ...]` for every platform that One can receive webhooks from. If your source isn't in the list, relay won't work for it.
|
|
26
|
+
|
|
19
27
|
### Step 2: Get event types
|
|
20
28
|
|
|
21
29
|
```bash
|
|
@@ -173,6 +181,13 @@ one --agent relay activate <relay-id> --actions '[{
|
|
|
173
181
|
}]'
|
|
174
182
|
```
|
|
175
183
|
|
|
184
|
+
## Discovery Commands
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
one --agent relay platforms # All relay-capable platforms + event type counts
|
|
188
|
+
one --agent relay event-types <platform> # Event types for a specific platform
|
|
189
|
+
```
|
|
190
|
+
|
|
176
191
|
## Management Commands
|
|
177
192
|
|
|
178
193
|
```bash
|