@withone/cli 1.54.0 → 1.55.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
@@ -24,7 +24,7 @@
24
24
  <a href="https://withone.ai/knowledge"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fapi.withone.ai%2Fopen%2Fcount%2Ftools" alt="tools"></a>
25
25
  </p>
26
26
 
27
- One gives your AI agent authenticated access to 400+ platforms - Gmail, Slack, Shopify, HubSpot, Stripe, Notion, and everything else - through a single interface. No API keys to juggle, no OAuth flows to build, no request formats to memorize. Connect a platform once, and your agent can search for actions, read the docs, and execute API calls in seconds.
27
+ One gives your AI agent authenticated access to 600+ platforms - Gmail, Slack, Shopify, HubSpot, Stripe, Notion, and everything else - through a single interface. No API keys to juggle, no OAuth flows to build, no request formats to memorize. Connect a platform once, and your agent can search for actions, read the docs, and execute API calls in seconds.
28
28
 
29
29
  ## Install
30
30
 
@@ -48,7 +48,10 @@ one login # Opens browser for authentication (global or per-directo
48
48
  one logout # Clear credentials (with scope picker and confirmation)
49
49
  ```
50
50
 
51
- Requires Node.js 18+.
51
+ Requires Node.js 18+. `one sync` additionally needs Node 20+, because its
52
+ local SQLite engine (`better-sqlite3`, an optional dependency) ships prebuilt
53
+ binaries only for Node 20 and above. On Node 18 every other command works
54
+ normally; `one sync` reports that the engine isn't installed.
52
55
 
53
56
  ## Quick start
54
57
 
@@ -241,7 +244,7 @@ Shows the connection details and asks for confirmation before deleting. Use `--f
241
244
 
242
245
  ### `one platforms`
243
246
 
244
- Browse all 200+ available platforms.
247
+ Browse all 600+ available platforms.
245
248
 
246
249
  ```bash
247
250
  one platforms # all platforms
@@ -523,7 +523,9 @@ async function writePageToMemory(profile, records, opts = {}) {
523
523
  const searchablePaths = getSearchablePaths(profile);
524
524
  const declaresIdentityKeys = (profile.identityKeys?.length ?? 0) > 0;
525
525
  const enrichTimestampField = profile.enrich?.timestampField ?? "_enriched_at";
526
- for (const record of records) {
526
+ for (const rawRecord of records) {
527
+ const derived = deriveFields(rawRecord, profile.derive);
528
+ const record = Object.keys(derived).length > 0 ? { ...rawRecord, ...derived } : rawRecord;
527
529
  report.attempted++;
528
530
  const externalId = getByDotPath(record, profile.idField);
529
531
  if (externalId === void 0 || externalId === null || externalId === "") {
@@ -608,6 +610,24 @@ function identityValuesFor(prefix, raw, opts = {}) {
608
610
  const v = s.toLowerCase().trim();
609
611
  return v ? [v] : [];
610
612
  }
613
+ function deriveFields(record, derive) {
614
+ const out = {};
615
+ if (!derive) return out;
616
+ for (const [field, spec] of Object.entries(derive)) {
617
+ const { path: path5, extract } = typeof spec === "string" ? { path: spec, extract: void 0 } : spec;
618
+ if (!path5) continue;
619
+ const values = resolveIdentityPath(record, path5).filter((v) => v !== null && v !== void 0 && typeof v !== "object");
620
+ if (values.length === 0) continue;
621
+ if (extract === "email") {
622
+ const emails = values.flatMap((v) => identityValuesFor("email", v));
623
+ if (emails.length === 0) continue;
624
+ out[field] = emails[0];
625
+ continue;
626
+ }
627
+ out[field] = values[0];
628
+ }
629
+ return out;
630
+ }
611
631
  function tokenizeIdentityPath(path5) {
612
632
  const tokens = [];
613
633
  const re = /([^.[\]]+)|\[([^\]]*)\]/g;
package/dist/index.js CHANGED
@@ -66,7 +66,7 @@ import {
66
66
  writeDraftProfile,
67
67
  writePageToMemory,
68
68
  writeProfile
69
- } from "./chunk-XN4ZWMLX.js";
69
+ } from "./chunk-FS4HAKZ6.js";
70
70
  import {
71
71
  getByDotPath
72
72
  } from "./chunk-44CV5IMX.js";
@@ -1104,7 +1104,7 @@ async function loginCommand() {
1104
1104
  console.log(pc15.cyan(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1105
1105
  console.log(pc15.cyan(" \u2502"));
1106
1106
  console.log(pc15.cyan(" \u2502 The One CLI (`one`) is now installed on this machine. It"));
1107
- console.log(pc15.cyan(" \u2502 lets you interact with 400+ third-party platforms (Gmail,"));
1107
+ console.log(pc15.cyan(" \u2502 lets you interact with 600+ third-party platforms (Gmail,"));
1108
1108
  console.log(pc15.cyan(" \u2502 Slack, Shopify, HubSpot, Stripe, GitHub, etc.) through"));
1109
1109
  console.log(pc15.cyan(" \u2502 their APIs. Whenever you need to interact with any"));
1110
1110
  console.log(pc15.cyan(" \u2502 third-party platform or external service, use the `one`"));
@@ -1711,7 +1711,7 @@ function printOnboardingPrompt() {
1711
1711
  console.log(pc2.cyan(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1712
1712
  console.log(pc2.cyan(" \u2502"));
1713
1713
  console.log(pc2.cyan(" \u2502 The One CLI (`one`) is now installed on this machine. It"));
1714
- console.log(pc2.cyan(" \u2502 lets you interact with 400+ third-party platforms (Gmail,"));
1714
+ console.log(pc2.cyan(" \u2502 lets you interact with 600+ third-party platforms (Gmail,"));
1715
1715
  console.log(pc2.cyan(" \u2502 Slack, Shopify, HubSpot, Stripe, GitHub, etc.) through"));
1716
1716
  console.log(pc2.cyan(" \u2502 their APIs. Whenever you need to interact with any"));
1717
1717
  console.log(pc2.cyan(" \u2502 third-party platform or external service, use the `one`"));
@@ -1888,7 +1888,7 @@ async function promptConnectIntegrations(apiKey, connParams) {
1888
1888
  label: i.label,
1889
1889
  hint: i.hint
1890
1890
  })),
1891
- { value: "more", label: "Browse all 200+ platforms" },
1891
+ { value: "more", label: "Browse all 600+ platforms" },
1892
1892
  { value: "skip", label: "Skip for now", hint: "you can always run one add later" }
1893
1893
  ];
1894
1894
  const message = first ? "Connect your first integration?" : "Connect another?";
@@ -5888,6 +5888,21 @@ async function enrichPhase(api, db, config2, model, idField, connectionKey, plat
5888
5888
  const duration = elapsed < 1e3 ? `${elapsed}ms` : elapsed < 6e4 ? `${(elapsed / 1e3).toFixed(1)}s` : `${Math.floor(elapsed / 6e4)}m ${Math.floor(elapsed % 6e4 / 1e3)}s`;
5889
5889
  return { enriched, skipped, rateLimited, total, duration };
5890
5890
  }
5891
+ async function enrichOneForPreview(api, profile, record, connectionKey) {
5892
+ const config2 = profile.enrich;
5893
+ if (!config2) return null;
5894
+ try {
5895
+ const detailAction = (await resolveActionDetails(api, config2.actionId)).details;
5896
+ const detail = await enrichSingleRow(api, detailAction, config2, record, connectionKey, profile.platform);
5897
+ if (!detail) return null;
5898
+ let enrichedData = detail;
5899
+ if (config2.fields && config2.fields.length > 0) enrichedData = pickFields(enrichedData, config2.fields);
5900
+ if (config2.exclude && config2.exclude.length > 0) stripExcludedFields(enrichedData, config2.exclude);
5901
+ return config2.merge !== false ? deepMerge(record, enrichedData) : { ...enrichedData, [profile.idField]: record[profile.idField] };
5902
+ } catch {
5903
+ return null;
5904
+ }
5905
+ }
5891
5906
  async function enrichSingleRow(api, detailAction, config2, row, connectionKey, platform) {
5892
5907
  const pathVars = interpolateParams(config2.pathVars, row);
5893
5908
  const queryParams = interpolateParams(config2.queryParams, row);
@@ -6879,7 +6894,14 @@ async function testSyncProfile(api, profile) {
6879
6894
  }));
6880
6895
  report.sample = first;
6881
6896
  report.samples = records.slice(0, SEARCHABLE_SAMPLE_SIZE);
6882
- report.identityKeysPreview = buildIdentityKeysPreview(report.samples, profile);
6897
+ if (profile.enrich && (profile.identityKeys?.length ?? 0) > 0 && report.samples.length > 0) {
6898
+ const merged = await enrichOneForPreview(api, profile, report.samples[0], connectionKey);
6899
+ if (merged) {
6900
+ const preview = buildIdentityKeysPreview([merged], profile);
6901
+ if (preview) report.identityKeysPreview = { ...preview, previewedAfterEnrich: true };
6902
+ }
6903
+ }
6904
+ report.identityKeysPreview ??= buildIdentityKeysPreview(report.samples, profile);
6883
6905
  report.ok = checks.every((c) => c.ok);
6884
6906
  return report;
6885
6907
  }
@@ -8379,7 +8401,7 @@ async function maybeAutoMigrateLegacy(platform, models) {
8379
8401
  ` detected legacy .one/sync/data/${platform}.db (${dbSize}) \u2014 auto-migrating into memory before sync.
8380
8402
  `
8381
8403
  );
8382
- const { memMigrateCommand: memMigrateCommand3 } = await import("./migrate-UGDDHWGY.js");
8404
+ const { memMigrateCommand: memMigrateCommand3 } = await import("./migrate-37A463L6.js");
8383
8405
  await memMigrateCommand3({ platform, yes: true });
8384
8406
  return;
8385
8407
  }
@@ -8388,7 +8410,7 @@ async function maybeAutoMigrateLegacy(platform, models) {
8388
8410
  initialValue: true
8389
8411
  });
8390
8412
  if (p7.isCancel(shouldMigrate) || !shouldMigrate) return;
8391
- const { memMigrateCommand: memMigrateCommand2 } = await import("./migrate-UGDDHWGY.js");
8413
+ const { memMigrateCommand: memMigrateCommand2 } = await import("./migrate-37A463L6.js");
8392
8414
  await memMigrateCommand2({ platform, yes: true });
8393
8415
  }
8394
8416
  async function syncSuggestSearchableCommand(platformModel, options = {}) {
@@ -10036,7 +10058,7 @@ Before using any feature, read its guide section first: \`one guide actions\`, \
10036
10058
 
10037
10059
  ## Features
10038
10060
 
10039
- ### 1. Actions \u2014 Execute API calls on 400+ platforms
10061
+ ### 1. Actions \u2014 Execute API calls on 600+ platforms
10040
10062
  Search for actions, read their docs, and execute them. This is the core workflow.
10041
10063
 
10042
10064
  **Quick start:**
@@ -10859,6 +10881,30 @@ Enrichment runs after list sync completes (Phase 2), not inline. It's inherently
10859
10881
 
10860
10882
  **Limitation:** Each profile supports one enrich action. If you need multiple enrichments (e.g. both summary and transcript from Fathom), create a second profile/model for the second enrichment.
10861
10883
 
10884
+ ## Derived fields (\`derive\`)
10885
+
10886
+ Add flat, queryable top-level fields computed from paths already in the record \u2014 no shell, no \`jq\`:
10887
+
10888
+ \`\`\`json
10889
+ {
10890
+ "derive": {
10891
+ "from_email": {
10892
+ "path": "messages[0].payload.headers[name=From].value",
10893
+ "extract": "email"
10894
+ },
10895
+ "company": "organization.name"
10896
+ }
10897
+ }
10898
+ \`\`\`
10899
+
10900
+ - Paths use the same resolver as \`identityKeys\`, so \`[]\` wildcards, \`[0]\` indexes and \`[name=From]\` filters all work
10901
+ - \`extract: "email"\` pulls the address out of a display-name header (\`"Jane <jane@acme.com>"\` \u2192 \`jane@acme.com\`), lowercased
10902
+ - A path resolving to nothing **omits** the field rather than writing null, so \`--where\` filters behave
10903
+ - A path resolving to several values takes the first \u2014 it's a flat field by definition, and the path syntax lets you be specific
10904
+ - Applied on both sync phases, so an enriching profile gets the same field whether the record came from the list or the detail pass
10905
+
10906
+ **Prefer \`derive\` over \`transform\` for extracting a field.** \`transform\` spawns \`sh -c\`, so it needs \`jq\` (or whatever you invoke) on PATH and does nothing on Windows. \`derive\` is pure and works everywhere \u2014 which is why the built-in \`gmail/gmailThreads\` profile uses it for \`from_email\`. Reach for \`transform\` when you need real computation, not field extraction.
10907
+
10862
10908
  ## Record Transform
10863
10909
 
10864
10910
  Pipe records through any shell command or flow between fetch and store. The command receives a JSON array on stdin and must return a JSON array on stdout.
@@ -10933,7 +10979,9 @@ Two ways to tag a record with a cross-platform identifier (e.g. email), dependin
10933
10979
  ]}
10934
10980
  \`\`\`
10935
10981
 
10936
- Each \`path\` supports \`[]\` wildcards (one key per element) and a \`[name=From]\` equality filter (e.g. Gmail \`messages[].payload.headers[name=From].value\`). \`email\`-prefixed values are email-extracted, so display-name headers (\`"Jane <jane@acme.com>"\`) and comma-lists normalize cleanly. Values are lowercased/trimmed/deduped. \`sync test\` previews how many identity keys each record resolves.
10982
+ Each \`path\` supports \`[]\` wildcards (one key per element) and a \`[name=From]\` equality filter (e.g. Gmail \`messages[].payload.headers[name=From].value\`). \`email\`-prefixed values are email-extracted, so display-name headers (\`"Jane <jane@acme.com>"\`) and comma-lists normalize cleanly. Values are lowercased/trimmed/deduped.
10983
+
10984
+ \`sync test\` previews how many identity keys each record resolves. For an **enriching** profile the participant paths live in the detail payload, so \`sync test\` spends one detail call on the first sample and previews against the merged shape \u2014 you see the keys a real sync would write, not zero plus a promise. If that call can't be made (rate limit, permissions), it falls back to the list-shape preview and says the keys resolve after enrichment.
10937
10985
 
10938
10986
  The built-in \`gmail/gmailThreads\` profile collects From/To/**Cc/Bcc**. Gmail only returns a \`Bcc\` header on messages the authenticated user sent \u2014 it is stripped for recipients \u2014 so Bcc keys appear on your own sent threads and nowhere else.
10939
10987
 
@@ -11229,7 +11277,7 @@ function buildStep1() {
11229
11277
  nextCommand: "one onboard --step 2",
11230
11278
  message: `# One CLI \u2014 Agent Onboarding
11231
11279
 
11232
- You now have access to the One CLI. It connects you to 400+ platforms
11280
+ You now have access to the One CLI. It connects you to 600+ platforms
11233
11281
  (Gmail, Slack, Stripe, Notion, etc.) \u2014 you'll learn the details shortly.
11234
11282
 
11235
11283
  IMPORTANT: Your only task right now is to send the message below to the
@@ -11239,7 +11287,7 @@ Do not offer demos yet. Just send this message and wait for their reply:
11239
11287
  ---
11240
11288
 
11241
11289
  I just got access to the One CLI \u2014 it connects me to your platforms like
11242
- Gmail, Slack, Stripe, and 400+ others so I can work with them directly.
11290
+ Gmail, Slack, Stripe, and 600+ others so I can work with them directly.
11243
11291
  I'll show you what I can do in a moment.
11244
11292
 
11245
11293
  First, one quick setup step: right now only this conversation knows about
@@ -11268,7 +11316,7 @@ If they declined, skip this section.
11268
11316
  ## One CLI \u2014 Platform Integrations
11269
11317
 
11270
11318
  The One CLI (\`one\`) is installed and configured on this machine. It is a
11271
- command-line tool that gives AI agents the ability to interact with 400+
11319
+ command-line tool that gives AI agents the ability to interact with 600+
11272
11320
  third-party platforms (Gmail, Slack, Shopify, HubSpot, Stripe, GitHub,
11273
11321
  Notion, Salesforce, etc.) through a single, unified interface.
11274
11322
 
@@ -11363,7 +11411,7 @@ function buildStep3(connections) {
11363
11411
  The user can connect any of these popular platforms:
11364
11412
  Gmail, Google Calendar, Slack, Notion, Shopify, Stripe, HubSpot, GitHub,
11365
11413
  Salesforce, QuickBooks, Asana, Jira, Linear, Intercom, Zendesk, Twilio,
11366
- and 400+ more. Run \`one platforms\` for the full list.`);
11414
+ and 600+ more. Run \`one platforms\` for the full list.`);
11367
11415
  sections.push("## Onboarding complete!\n\nYou're all set. Use `one --agent guide` any time you need the full reference.");
11368
11416
  return {
11369
11417
  step: 3,
@@ -11394,7 +11442,7 @@ platform before you can start using actions. Suggest they run:
11394
11442
  one add slack
11395
11443
  one add <any-platform>
11396
11444
 
11397
- Run \`one platforms\` to see all 400+ available platforms.`;
11445
+ Run \`one platforms\` to see all 600+ available platforms.`;
11398
11446
  }
11399
11447
  const header = `## Current State
11400
11448
 
@@ -11831,7 +11879,7 @@ silenceWarningsInAgentMode();
11831
11879
  var require4 = createRequire3(import.meta.url);
11832
11880
  var { version } = require4("../package.json");
11833
11881
  var program = new Command();
11834
- program.name("one").option("--agent", "Machine-readable JSON output (no colors, spinners, or prompts)").description(`One CLI \u2014 Connect AI agents to 400+ platforms through one interface.
11882
+ program.name("one").option("--agent", "Machine-readable JSON output (no colors, spinners, or prompts)").description(`One CLI \u2014 Connect AI agents to 600+ platforms through one interface.
11835
11883
 
11836
11884
  Setup:
11837
11885
  one login Authenticate via browser (opens app.withone.ai)
@@ -11903,7 +11951,7 @@ program.name("one").option("--agent", "Machine-readable JSON output (no colors,
11903
11951
  -d '{"to":"j@example.com","subject":"Hello","body":"Hi!","connectionKey":"live::gmail::default::abc123"}'
11904
11952
 
11905
11953
  Platform names are lowercase; multi-word names use dashes (e.g. hubspot, ship-station, google-calendar).
11906
- Run 'one platforms' to browse all 400+ available platforms.`).version(version);
11954
+ Run 'one platforms' to browse all 600+ available platforms.`).version(version);
11907
11955
  var updateCheckPromise;
11908
11956
  program.hook("preAction", (thisCommand, actionCommand) => {
11909
11957
  const opts = program.opts();
@@ -3,7 +3,7 @@ import {
3
3
  dotPathToJsonbExpr,
4
4
  memMigrateCommand,
5
5
  reviveStringifiedJson
6
- } from "./chunk-XN4ZWMLX.js";
6
+ } from "./chunk-FS4HAKZ6.js";
7
7
  import "./chunk-44CV5IMX.js";
8
8
  import "./chunk-GNSR3NYN.js";
9
9
  import "./chunk-DDCDPVJH.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/cli",
3
- "version": "1.54.0",
3
+ "version": "1.55.0",
4
4
  "description": "CLI for managing One",
5
5
  "type": "module",
6
6
  "files": [
@@ -36,7 +36,7 @@
36
36
  },
37
37
  "optionalDependencies": {
38
38
  "@electric-sql/pglite": "^0.2.17",
39
- "better-sqlite3": "^11.7.0",
39
+ "better-sqlite3": "^12.11.1",
40
40
  "pg": "^8.20.0"
41
41
  },
42
42
  "devDependencies": {
@@ -21,6 +21,12 @@
21
21
  { "prefix": "email", "path": "messages[].payload.headers[name=Cc].value" },
22
22
  { "prefix": "email", "path": "messages[].payload.headers[name=Bcc].value" }
23
23
  ],
24
+ "derive": {
25
+ "from_email": {
26
+ "path": "messages[0].payload.headers[name=From].value",
27
+ "extract": "email"
28
+ }
29
+ },
24
30
  "enrich": {
25
31
  "actionId": "conn_mod_def::GJ3ok0Eq0R8::AAzgZVLqTg2iBuITKpJLZg",
26
32
  "pathVars": { "userId": "me", "id": "{id}" },
@@ -20,7 +20,7 @@ description: |
20
20
 
21
21
  # One CLI
22
22
 
23
- You have access to the One CLI which lets you interact with 400+ third-party platforms through their APIs. Always include the `--agent` flag right after `one` for structured JSON output.
23
+ You have access to the One CLI which lets you interact with 600+ third-party platforms through their APIs. Always include the `--agent` flag right after `one` for structured JSON output.
24
24
 
25
25
  If the user wants a separate API key / connections for a specific project (vs. their default), walk them through running `one init` from that project folder and picking the "project" scope — see `references/scoping.md`. For monorepo subprojects (where a parent already has `.git`/`package.json`), have them `mkdir .one` in the subproject first so the config is keyed to that dir, not the monorepo root.
26
26
 
@@ -311,6 +311,8 @@ Without declared paths, the default walker concatenates every string in the reco
311
311
 
312
312
  **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.
313
313
 
314
+ **Extracting a flat field? Use `derive`, not `transform`.** `derive` computes top-level fields from paths already in the record (`"derive": { "from_email": { "path": "messages[0].payload.headers[name=From].value", "extract": "email" } }`), using the same path syntax as `identityKeys`. `transform` spawns `sh -c`, so it needs `jq` on PATH and silently does nothing on Windows — never put one in a profile you intend to share. A path that resolves to nothing omits the field rather than writing null.
315
+
314
316
  **Installed profiles do not auto-update.** `sync run` reads only `.one/sync/profiles/<platform>_<model>.json` and never merges the shipped built-in, so a profile created before a capability shipped silently lacks it — a pre-#167 gmail profile writes zero identity keys, forever, with no change in record counts. `sync run` warns when the built-in declares `identityKeys` / `identityKey` / `enrich` / `dateFilter` / `memory` that your copy lacks (agent mode: a `profileDrift` array). Fix with `one sync init <platform> <model>`, which patches rather than overwrites.
315
317
 
316
318
  **Cross-platform identity on a profile.** Two separate fields, and picking the wrong one silently mangles data: