@withone/cli 1.54.1 → 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.
|
@@ -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
|
|
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-
|
|
69
|
+
} from "./chunk-FS4HAKZ6.js";
|
|
70
70
|
import {
|
|
71
71
|
getByDotPath
|
|
72
72
|
} from "./chunk-44CV5IMX.js";
|
|
@@ -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
|
-
|
|
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-
|
|
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-
|
|
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 = {}) {
|
|
@@ -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.
|
|
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
|
|
package/package.json
CHANGED
|
@@ -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}" },
|
package/skills/one/SKILL.md
CHANGED
|
@@ -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:
|