@withone/cli 1.47.5 → 1.47.7

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
@@ -168,10 +168,15 @@ Connect a new platform via OAuth.
168
168
  one add shopify
169
169
  one add hubspot
170
170
  one add gmail
171
+ one add gmail --tag work # tag the connection
171
172
  ```
172
173
 
173
174
  Opens your browser, you authorize, done. The CLI polls until the connection is live. Platform names are lowercase (with dashes for multi-word names) - run `one platforms` to see them all.
174
175
 
176
+ | Flag | What it does |
177
+ |------|-------------|
178
+ | `--tag <name>` | Tag the new connection once it's created. Lets sync/flow profiles target a specific connection via `"connection": { "platform": "gmail", "tag": "work" }` when you have several connections for one platform (e.g. personal vs work Gmail). |
179
+
175
180
  ### `one list`
176
181
 
177
182
  List your active connections with their status and connection keys.
@@ -106,6 +106,16 @@ var OneApi = class {
106
106
  async deleteConnection(id) {
107
107
  await this.requestFull({ path: `/vault/connections/${id}`, method: "DELETE" });
108
108
  }
109
+ /**
110
+ * Set the tag set on a connection (replaces existing tags). Backs
111
+ * `one add <platform> --tag <name>`, which tags a connection right after
112
+ * the OAuth flow creates it so sync/flow profiles can reference it via
113
+ * `connection: { platform, tag }` when several connections share a platform.
114
+ * Maps to `PATCH /v1/vault/connections/{id}` (UpdateConnection { tags }).
115
+ */
116
+ async updateConnectionTags(id, tags) {
117
+ await this.requestFull({ path: `/vault/connections/${id}`, method: "PATCH", body: { tags } });
118
+ }
109
119
  /**
110
120
  * Resolve a late-bound `ConnectionRef` to a current `Connection`. Pass
111
121
  * `cache` (a pre-fetched connection list) when resolving many refs in a
@@ -1279,7 +1289,7 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
1279
1289
  if (flowStack.includes(resolvedKey)) {
1280
1290
  throw new Error(`Circular flow detected: ${[...flowStack, resolvedKey].join(" \u2192 ")}`);
1281
1291
  }
1282
- const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-V5FALC6N.js");
1292
+ const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-7VRNSKWU.js");
1283
1293
  const { flow: subFlow, rootDir: subRootDir } = loadFlowWithMeta2(resolvedKey);
1284
1294
  const subContext = await executeFlow(
1285
1295
  subFlow,
@@ -1493,16 +1503,17 @@ async function executeSingleStep(step, context, api, permissions, allowedActionI
1493
1503
  }
1494
1504
  const startTime = Date.now();
1495
1505
  let lastError;
1496
- const maxAttempts = step.onError?.strategy === "retry" && step.onError.retries ? step.onError.retries + 1 : 1;
1506
+ const onError = step.onError ?? context._defaultOnError;
1507
+ const maxAttempts = onError?.strategy === "retry" && onError.retries ? onError.retries + 1 : 1;
1497
1508
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1498
1509
  try {
1499
1510
  if (attempt > 1) {
1500
- const delay = computeRetryDelay(step.onError, attempt);
1511
+ const delay = computeRetryDelay(onError, attempt);
1501
1512
  options.onEvent?.({
1502
1513
  event: "step:retry",
1503
1514
  stepId: step.id,
1504
1515
  attempt,
1505
- maxRetries: step.onError.retries,
1516
+ maxRetries: onError.retries,
1506
1517
  delayMs: delay
1507
1518
  });
1508
1519
  await sleep2(delay);
@@ -1589,8 +1600,8 @@ async function executeSingleStep(step, context, api, permissions, allowedActionI
1589
1600
  if (attempt === maxAttempts) {
1590
1601
  break;
1591
1602
  }
1592
- if (step.onError?.strategy === "retry" && (step.onError.retryOn || step.onError.failFastOn)) {
1593
- const decision = shouldRetryError(lastError, step.onError);
1603
+ if (onError?.strategy === "retry" && (onError.retryOn || onError.failFastOn)) {
1604
+ const decision = shouldRetryError(lastError, onError);
1594
1605
  if (!decision.retry) {
1595
1606
  options.onEvent?.({
1596
1607
  event: "step:retry-skip",
@@ -1604,7 +1615,7 @@ async function executeSingleStep(step, context, api, permissions, allowedActionI
1604
1615
  }
1605
1616
  }
1606
1617
  const errorMessage = lastError?.message || "Unknown error";
1607
- const strategy = step.onError?.strategy || "fail";
1618
+ const strategy = onError?.strategy || "fail";
1608
1619
  const retriesUsed = Math.max(0, maxAttempts - 1);
1609
1620
  const isTimeout = lastError instanceof StepTimeoutError;
1610
1621
  const errorCode = lastError?.errorCode;
@@ -1619,7 +1630,7 @@ async function executeSingleStep(step, context, api, permissions, allowedActionI
1619
1630
  context.steps[step.id] = result;
1620
1631
  return result;
1621
1632
  }
1622
- if (strategy === "fallback" && step.onError?.fallbackStepId) {
1633
+ if (strategy === "fallback" && onError?.fallbackStepId) {
1623
1634
  const result = {
1624
1635
  status: isTimeout ? "timeout" : "failed",
1625
1636
  error: errorMessage,
@@ -1740,6 +1751,7 @@ async function executeFlow(flow, inputs, api, permissions, allowedActionIds, opt
1740
1751
  loop: {}
1741
1752
  };
1742
1753
  context.input = resolvedInputs;
1754
+ context._defaultOnError = flow.defaultOnError;
1743
1755
  const completedStepIds = resumeState ? new Set(resumeState.completedSteps) : void 0;
1744
1756
  if (options.dryRun && !options.mock) {
1745
1757
  options.onEvent?.({
@@ -1797,7 +1809,8 @@ var FLOW_SCHEMA = {
1797
1809
  description: { type: "string", required: false, description: "What this flow does" },
1798
1810
  version: { type: "string", required: false, description: "Semver or arbitrary version string" },
1799
1811
  inputs: { type: "object", required: true, description: "Input declarations (Record<string, InputDeclaration>)" },
1800
- steps: { type: "array", required: true, description: "Ordered array of steps", stepsArray: true }
1812
+ steps: { type: "array", required: true, description: "Ordered array of steps", stepsArray: true },
1813
+ defaultOnError: { type: "object", required: false, description: 'Default error strategy inherited by every step without its own `onError` (e.g. { "strategy": "continue" }). A step opts out with its own `onError`.' }
1801
1814
  },
1802
1815
  inputFields: {
1803
1816
  type: { type: "string", required: true, description: "Data type: string, number, boolean, object, array", enum: ["string", "number", "boolean", "object", "array"] },
@@ -12,7 +12,7 @@ import {
12
12
  stripStepsAlias,
13
13
  summarizeFlowInputs,
14
14
  walkSteps
15
- } from "./chunk-DMLYQFMV.js";
15
+ } from "./chunk-KWGN3RJR.js";
16
16
  import "./chunk-44CV5IMX.js";
17
17
  import "./chunk-K6MWE2ZH.js";
18
18
  export {
package/dist/index.js CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  searchCachePath,
31
31
  validateActionInput,
32
32
  writeCache
33
- } from "./chunk-DMLYQFMV.js";
33
+ } from "./chunk-KWGN3RJR.js";
34
34
  import {
35
35
  memSqlCommand
36
36
  } from "./chunk-QV3Y5N5G.js";
@@ -2012,7 +2012,7 @@ function stripAnsi(str) {
2012
2012
  }
2013
2013
 
2014
2014
  // src/commands/connection.ts
2015
- async function connectionAddCommand(platformArg) {
2015
+ async function connectionAddCommand(platformArg, options) {
2016
2016
  if (isAgentMode()) {
2017
2017
  error("This command requires interactive input. Run without --agent.");
2018
2018
  }
@@ -2107,7 +2107,22 @@ ${url}`);
2107
2107
  try {
2108
2108
  const connection2 = await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
2109
2109
  pollSpinner.stop(`${platform} connected!`);
2110
- p4.log.success(`${pc4.green("\u2713")} ${connection2.platform} is now available to your AI agents.`);
2110
+ const tag = options?.tag?.trim();
2111
+ if (tag) {
2112
+ const tagSpinner = p4.spinner();
2113
+ tagSpinner.start(`Tagging connection "${tag}"...`);
2114
+ try {
2115
+ await api.updateConnectionTags(connection2.id, [tag]);
2116
+ tagSpinner.stop(`Tagged "${tag}"`);
2117
+ } catch (err) {
2118
+ tagSpinner.stop("Could not set tag");
2119
+ p4.log.warn(
2120
+ `Connected, but tagging failed: ${err instanceof Error ? err.message : "Unknown error"}
2121
+ The connection is usable; set the tag later in the dashboard or retry.`
2122
+ );
2123
+ }
2124
+ }
2125
+ p4.log.success(`${pc4.green("\u2713")} ${connection2.platform} is now available to your AI agents.${tag ? ` (tag: ${tag})` : ""}`);
2111
2126
  p4.outro("Connection complete!");
2112
2127
  } catch (error2) {
2113
2128
  pollSpinner.stop("Connection timed out");
@@ -3086,6 +3101,16 @@ function validateFlowSchema(flow2) {
3086
3101
  if (f.version !== void 0 && typeof f.version !== "string") {
3087
3102
  errors.push({ path: "version", message: '"version" must be a string' });
3088
3103
  }
3104
+ if (f.defaultOnError !== void 0) {
3105
+ if (!f.defaultOnError || typeof f.defaultOnError !== "object" || Array.isArray(f.defaultOnError)) {
3106
+ errors.push({ path: "defaultOnError", message: '"defaultOnError" must be an object (e.g. { "strategy": "continue" })' });
3107
+ } else {
3108
+ const oe = f.defaultOnError;
3109
+ if (!FLOW_SCHEMA.errorStrategies.includes(oe.strategy)) {
3110
+ errors.push({ path: "defaultOnError.strategy", message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
3111
+ }
3112
+ }
3113
+ }
3089
3114
  if (!f.inputs || typeof f.inputs !== "object" || Array.isArray(f.inputs)) {
3090
3115
  errors.push({ path: "inputs", message: 'Flow must have an "inputs" object' });
3091
3116
  } else {
@@ -11285,8 +11310,8 @@ config.command("reset").description("Remove the project config for the current d
11285
11310
  }
11286
11311
  });
11287
11312
  var connection = program.command("connection").description("Manage connections");
11288
- connection.command("add [platform]").alias("a").description("Add a new connection").action(async (platform) => {
11289
- await connectionAddCommand(platform);
11313
+ connection.command("add [platform]").alias("a").description("Add a new connection").option("--tag <name>", "Tag the new connection (disambiguates multiple connections per platform in sync/flow profiles)").action(async (platform, options) => {
11314
+ await connectionAddCommand(platform, { tag: options.tag });
11290
11315
  });
11291
11316
  connection.command("list").alias("ls").description("List your connections").option("-s, --search <query>", "Filter connections by platform name").option("-l, --limit <n>", "Max connections to return (agent mode default: 20)").action(async (options) => {
11292
11317
  await connectionListCommand(options);
@@ -11450,8 +11475,8 @@ program.command("whoami").description("Show the user, organization, and project
11450
11475
  console.log(` ${pc14.dim("API:")} ${apiBase}`);
11451
11476
  console.log();
11452
11477
  });
11453
- program.command("add [platform]").description("Shortcut for: connection add").action(async (platform) => {
11454
- await connectionAddCommand(platform);
11478
+ program.command("add [platform]").description("Shortcut for: connection add").option("--tag <name>", "Tag the new connection (disambiguates multiple connections per platform in sync/flow profiles)").action(async (platform, options) => {
11479
+ await connectionAddCommand(platform, { tag: options.tag });
11455
11480
  });
11456
11481
  program.command("list").alias("ls").description("Shortcut for: connection list").option("-s, --search <query>", "Filter connections by platform name").option("-l, --limit <n>", "Max connections to return (agent mode default: 20)").action(async (options) => {
11457
11482
  await connectionListCommand(options);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/cli",
3
- "version": "1.47.5",
3
+ "version": "1.47.7",
4
4
  "description": "CLI for managing One",
5
5
  "type": "module",
6
6
  "files": [
@@ -251,7 +251,7 @@ Without declared paths, the default walker concatenates every string in the reco
251
251
 
252
252
  **Sync rejects custom actions** — profiles must use passthrough. `sync init` only surfaces passthrough models; `sync run` aborts if the list or enrich action is tagged `custom`. If no passthrough exists, compose a flow instead.
253
253
 
254
- **Connections are late-bound** — profiles use `"connection": { "platform": "<name>" }`, not literal `connectionKey` strings. The key is resolved at sync time, so `one add <platform>` (re-auth) doesn't break the profile. For multi-account platforms, add `"tag": "<connection-tag>"` to disambiguate. Don't hardcode connection keys in profiles.
254
+ **Connections are late-bound** — profiles use `"connection": { "platform": "<name>" }`, not literal `connectionKey` strings. The key is resolved at sync time, so `one add <platform>` (re-auth) doesn't break the profile. For multi-account platforms, add `"tag": "<connection-tag>"` to disambiguate, and create the tagged connection with `one add <platform> --tag <name>`. Don't hardcode connection keys in profiles.
255
255
 
256
256
  **Advanced features** (enrich, transform, exclude, identityKey, hooks, --full-refresh, alternative backends, embedding tuning): run `one guide memory` or `one guide sync` for the full reference.
257
257
 
@@ -267,8 +267,9 @@ One also supports more advanced patterns. Read the relevant reference file befor
267
267
  If the user needs a platform that isn't connected yet, tell them to run:
268
268
  ```bash
269
269
  one add <platform>
270
+ one add <platform> --tag <name> # tag it (for multiple connections per platform)
270
271
  ```
271
- This is interactive and opens the browser for OAuth. After connecting, the platform will appear in `one --agent connection list`.
272
+ This is interactive and opens the browser for OAuth. After connecting, the platform will appear in `one --agent connection list`. Use `--tag` when the user has (or will have) more than one connection for the same platform so sync/flow profiles can target a specific one via `"connection": { "platform": "<name>", "tag": "<name>" }`.
272
273
 
273
274
  ## Removing Connections
274
275
 
@@ -566,6 +566,22 @@ the outputSchema declaration.
566
566
 
567
567
  Strategies: `fail` (default), `continue`, `retry`, `fallback`.
568
568
 
569
+ **Flow-level default (cli#93).** Set `defaultOnError` at the top of the flow and every step without its own `onError` inherits it — handy when most steps should `continue` (e.g. rendering/formatting pipelines) and you don't want to repeat it N times. A step opts out by declaring its own `onError`:
570
+
571
+ ```json
572
+ {
573
+ "key": "render-report",
574
+ "defaultOnError": { "strategy": "continue" },
575
+ "steps": [
576
+ { "id": "critical", "onError": { "strategy": "fail" }, ... }, // stays fatal
577
+ { "id": "chart", ... }, // inherits continue
578
+ { "id": "thumbnail", ... } // inherits continue
579
+ ]
580
+ }
581
+ ```
582
+
583
+ Scoped per-flow: a sub-flow uses its own `defaultOnError`, not the parent's.
584
+
569
585
  **Retry backoff.** By default each retry waits exactly `retryDelayMs`. For rate-limited APIs add `"backoff": "exponential"` (or `"exponential-jitter"`) and an optional `"maxDelayMs"` cap (defaults to 30000):
570
586
 
571
587
  ```json