@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 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 connectionKey = resolveValue(action.connectionKey, context);
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-2KVAPP6E.js");
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
- connectionKey: { type: "string", required: true, description: "Connection key (use $.input selector)" },
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
- connectionKey: "$.input.stripeConnectionKey",
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: "...", connectionKey: "$.input.slackKey", data: { text: "Found!" } } }],
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: "...", connectionKey: "$.input.stripeKey", data: { amount: "$.loop.order.total" } } }]
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: "...", connectionKey: "$.input.stripeKey" } },
1769
- { id: "getSlack", name: "Get Slack data", type: "action", action: { platform: "slack", actionId: "...", connectionKey: "$.input.slackKey" } }
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: "...", connectionKey: "$.input.gmailKey" } }]
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: "...", connectionKey: "$.input.gmailKey", queryParams: { maxResults: 100 } },
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
- "connectionKey": "$.input.connectionKey",
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
- ## Input Connection Auto-Resolution
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
- 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.
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
- "connectionKey": "$.input.crmConnectionKey",
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
- "connectionKey": "$.input.slackConnectionKey",
2381
+ "connection": { "platform": "slack" },
2353
2382
  "data": {
2354
2383
  "channel": "$.input.slackChannel",
2355
2384
  "text": "{{$.steps.buildSummary.output.summary}}"
@@ -11,7 +11,7 @@ import {
11
11
  saveFlow,
12
12
  summarizeFlowInputs,
13
13
  walkSteps
14
- } from "./chunk-SSEDFOVV.js";
14
+ } from "./chunk-PK2RAVAF.js";
15
15
  export {
16
16
  FlowRunner,
17
17
  collectStepTypes,
package/dist/index.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  resolveFlowPath,
20
20
  saveFlow,
21
21
  validateActionInput
22
- } from "./chunk-SSEDFOVV.js";
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
- if (!a.connectionKey) errors.push({ path: `${fieldPath}.connectionKey`, message: 'Action must have "connectionKey"' });
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
- connectionKey: "$.input.connectionKey",
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
- connectionKey: "$.input.connectionKey"
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
- connectionKey: "$.input.connectionKey"
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
- connectionKey: "$.input.connectionKey"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/cli",
3
- "version": "1.40.0",
3
+ "version": "1.42.0",
4
4
  "description": "CLI for managing One",
5
5
  "type": "module",
6
6
  "files": [
@@ -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
- "connectionKey": "$.input.stripeConnectionKey",
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