@withone/cli 1.39.0 → 1.40.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 +4 -2
- package/dist/{chunk-MNJJE4PJ.js → chunk-SSEDFOVV.js} +37 -1
- package/dist/{flow-runner-OVOGDXNR.js → flow-runner-2KVAPP6E.js} +1 -1
- package/dist/index.js +82 -13
- package/package.json +1 -1
- package/profiles/attio/attioCompanies.json +1 -1
- package/profiles/attio/attioPeople.json +1 -1
- package/profiles/fathom/meetings.json +1 -1
- package/profiles/gmail/gmailThreads.json +1 -1
- package/profiles/google-calendar/events.json +1 -1
- package/profiles/hacker-news/topStories.json +1 -1
- package/profiles/notion/search.json +1 -1
- package/profiles/stripe/balanceTransactions.json +1 -1
- package/profiles/stripe/customers.json +1 -1
- package/skills/one/SKILL.md +2 -0
package/README.md
CHANGED
|
@@ -312,9 +312,9 @@ one sync install && one sync doctor
|
|
|
312
312
|
```
|
|
313
313
|
|
|
314
314
|
```bash
|
|
315
|
-
# Discover → init (one command: infer +
|
|
315
|
+
# Discover → init (one command: infer + late-bound connection + auto-test) → run
|
|
316
316
|
one sync models stripe
|
|
317
|
-
one sync init stripe balanceTransactions #
|
|
317
|
+
one sync init stripe balanceTransactions # connection: { platform } baked in, test auto-run
|
|
318
318
|
one sync run stripe --since 90d
|
|
319
319
|
|
|
320
320
|
# Query, search, SQL
|
|
@@ -332,6 +332,8 @@ one sync run stripe --full-refresh
|
|
|
332
332
|
|
|
333
333
|
> **Sync uses passthrough actions only.** Profiles referencing a custom/composer action are rejected at runtime. `sync models` already filters to passthrough-only; if a model has no passthrough list endpoint, compose a flow instead of syncing.
|
|
334
334
|
|
|
335
|
+
> **Connections are late-bound.** Profiles use `"connection": { "platform": "<name>", "tag"?: "..." }` instead of literal `connectionKey` strings. The key is resolved at sync time, so `one add <platform>` (re-auth) doesn't break the profile. `tag` only needed for multi-account platforms (e.g. two Gmail accounts).
|
|
336
|
+
|
|
335
337
|
| Subcommand | What it does |
|
|
336
338
|
|------------|-------------|
|
|
337
339
|
| `install` / `doctor` | Install + verify the SQLite engine |
|
|
@@ -98,6 +98,42 @@ var OneApi = class {
|
|
|
98
98
|
async deleteConnection(id) {
|
|
99
99
|
await this.requestFull({ path: `/vault/connections/${id}`, method: "DELETE" });
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Resolve a late-bound `ConnectionRef` to a current `Connection`. Pass
|
|
103
|
+
* `cache` (a pre-fetched connection list) when resolving many refs in a
|
|
104
|
+
* loop, so each resolve doesn't repeat the listConnections round-trip.
|
|
105
|
+
*
|
|
106
|
+
* Errors are deliberately verbose: a sync profile or flow that fails to
|
|
107
|
+
* resolve a connection should surface *why* (no match / wrong tag /
|
|
108
|
+
* ambiguous) so the agent can fix the ref without trial and error.
|
|
109
|
+
*/
|
|
110
|
+
async resolveConnection(ref, cache) {
|
|
111
|
+
const all = cache ?? await this.listConnections();
|
|
112
|
+
const platformLower = ref.platform.toLowerCase();
|
|
113
|
+
const candidates = all.filter((c) => c.platform.toLowerCase() === platformLower);
|
|
114
|
+
if (candidates.length === 0) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`No connection found for platform "${ref.platform}". Run 'one add ${ref.platform}' to connect.`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
let matches = candidates;
|
|
120
|
+
if (ref.tag) {
|
|
121
|
+
matches = candidates.filter((c) => c.tags?.includes(ref.tag));
|
|
122
|
+
if (matches.length === 0) {
|
|
123
|
+
const availableTags = candidates.flatMap((c) => c.tags ?? []);
|
|
124
|
+
throw new Error(
|
|
125
|
+
`No "${ref.platform}" connection has tag "${ref.tag}". Available tags: ${availableTags.length > 0 ? availableTags.join(", ") : "(none)"}.`
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (matches.length > 1) {
|
|
130
|
+
const tagList = matches.map((c) => c.tags?.length ? c.tags.join(",") : "(no tag)").join("; ");
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Multiple "${ref.platform}" connections found (tags: ${tagList}). Add a "tag" field to the connection ref to disambiguate.`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
return matches[0];
|
|
136
|
+
}
|
|
101
137
|
async listPlatforms() {
|
|
102
138
|
const allPlatforms = [];
|
|
103
139
|
let page = 1;
|
|
@@ -1079,7 +1115,7 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
|
|
|
1079
1115
|
if (flowStack.includes(resolvedKey)) {
|
|
1080
1116
|
throw new Error(`Circular flow detected: ${[...flowStack, resolvedKey].join(" \u2192 ")}`);
|
|
1081
1117
|
}
|
|
1082
|
-
const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-
|
|
1118
|
+
const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-2KVAPP6E.js");
|
|
1083
1119
|
const { flow: subFlow, rootDir: subRootDir } = loadFlowWithMeta2(resolvedKey);
|
|
1084
1120
|
const subContext = await executeFlow(
|
|
1085
1121
|
subFlow,
|
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-SSEDFOVV.js";
|
|
23
23
|
|
|
24
24
|
// src/index.ts
|
|
25
25
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -4732,12 +4732,24 @@ function readProfile(platform, model) {
|
|
|
4732
4732
|
}
|
|
4733
4733
|
}
|
|
4734
4734
|
function writeProfile(profile) {
|
|
4735
|
-
const required = ["platform", "model", "
|
|
4735
|
+
const required = ["platform", "model", "actionId", "idField", "pagination"];
|
|
4736
4736
|
for (const field of required) {
|
|
4737
4737
|
if (!profile[field]) {
|
|
4738
4738
|
throw new Error(`Missing required field: ${field}`);
|
|
4739
4739
|
}
|
|
4740
4740
|
}
|
|
4741
|
+
const hasKey = !!profile.connectionKey;
|
|
4742
|
+
const hasRef = !!profile.connection?.platform;
|
|
4743
|
+
if (hasKey && hasRef) {
|
|
4744
|
+
throw new Error(
|
|
4745
|
+
"Profile has both `connectionKey` and `connection` \u2014 set exactly one. Prefer `connection: { platform, tag? }` so re-auth doesn't break the profile."
|
|
4746
|
+
);
|
|
4747
|
+
}
|
|
4748
|
+
if (!hasKey && !hasRef) {
|
|
4749
|
+
throw new Error(
|
|
4750
|
+
'Missing connection: set `connection: { platform: "<name>" }` (or legacy `connectionKey: "<key>"`).'
|
|
4751
|
+
);
|
|
4752
|
+
}
|
|
4741
4753
|
if (profile.resultsPath === void 0) {
|
|
4742
4754
|
throw new Error('Missing required field: resultsPath (use "" or "$" for root-array responses)');
|
|
4743
4755
|
}
|
|
@@ -4748,6 +4760,16 @@ function writeProfile(profile) {
|
|
|
4748
4760
|
const filePath = profilePath(profile.platform, profile.model);
|
|
4749
4761
|
fs8.writeFileSync(filePath, JSON.stringify(profile, null, 2));
|
|
4750
4762
|
}
|
|
4763
|
+
async function resolveProfileConnectionKey(api, profile, cache2) {
|
|
4764
|
+
if (profile.connectionKey) return profile.connectionKey;
|
|
4765
|
+
if (!profile.connection?.platform) {
|
|
4766
|
+
throw new Error(
|
|
4767
|
+
`Profile ${profile.platform}/${profile.model} has no connectionKey or connection ref.`
|
|
4768
|
+
);
|
|
4769
|
+
}
|
|
4770
|
+
const conn = await api.resolveConnection(profile.connection, cache2);
|
|
4771
|
+
return conn.key;
|
|
4772
|
+
}
|
|
4751
4773
|
function writeDraftProfile(platform, model, draft) {
|
|
4752
4774
|
fs8.mkdirSync(PROFILES_DIR, { recursive: true });
|
|
4753
4775
|
const filePath = profilePath(platform, model);
|
|
@@ -4773,7 +4795,9 @@ function generateTemplate(platform, model, actionId) {
|
|
|
4773
4795
|
return {
|
|
4774
4796
|
platform,
|
|
4775
4797
|
model,
|
|
4776
|
-
|
|
4798
|
+
// Late-bound ref — survives re-auth. Use { platform, tag } when the
|
|
4799
|
+
// platform has multiple connections (e.g. multiple Gmail accounts).
|
|
4800
|
+
connection: { platform },
|
|
4777
4801
|
actionId: actionId ?? "FILL_IN",
|
|
4778
4802
|
resultsPath: "FILL_IN",
|
|
4779
4803
|
idField: "FILL_IN",
|
|
@@ -5833,6 +5857,7 @@ async function syncModel(api, profile, options) {
|
|
|
5833
5857
|
"--full-refresh and --since cannot be used together. --full-refresh always fetches the whole collection."
|
|
5834
5858
|
);
|
|
5835
5859
|
}
|
|
5860
|
+
const connectionKey = await resolveProfileConnectionKey(api, profile);
|
|
5836
5861
|
const lock = options.dryRun ? null : acquireSyncLock(platform, model);
|
|
5837
5862
|
const existingState = getModelState(platform, model);
|
|
5838
5863
|
if (existingState?.status === "syncing" && !options.dryRun && !isAgentMode()) {
|
|
@@ -5960,7 +5985,7 @@ async function syncModel(api, profile, options) {
|
|
|
5960
5985
|
const result = await api.executePassthroughRequest({
|
|
5961
5986
|
platform,
|
|
5962
5987
|
actionId: profile.actionId,
|
|
5963
|
-
connectionKey
|
|
5988
|
+
connectionKey,
|
|
5964
5989
|
pathVariables: profile.pathVars,
|
|
5965
5990
|
queryParams: currentPageQueryParams,
|
|
5966
5991
|
headers: currentPageHeaders,
|
|
@@ -6169,7 +6194,7 @@ async function syncModel(api, profile, options) {
|
|
|
6169
6194
|
profile.enrich,
|
|
6170
6195
|
model,
|
|
6171
6196
|
profile.idField,
|
|
6172
|
-
|
|
6197
|
+
connectionKey,
|
|
6173
6198
|
platform,
|
|
6174
6199
|
{
|
|
6175
6200
|
transform: profile.transform,
|
|
@@ -6275,6 +6300,18 @@ async function testSyncProfile(api, profile) {
|
|
|
6275
6300
|
if (limitLocation === "body") bodyParams[limitParam] = pageSize;
|
|
6276
6301
|
else queryParams[limitParam] = pageSize;
|
|
6277
6302
|
}
|
|
6303
|
+
let connectionKey;
|
|
6304
|
+
try {
|
|
6305
|
+
connectionKey = await resolveProfileConnectionKey(api, profile);
|
|
6306
|
+
checks.push({ name: "connection resolves", ok: true });
|
|
6307
|
+
} catch (err) {
|
|
6308
|
+
checks.push({
|
|
6309
|
+
name: "connection resolves",
|
|
6310
|
+
ok: false,
|
|
6311
|
+
detail: err instanceof Error ? err.message : String(err)
|
|
6312
|
+
});
|
|
6313
|
+
return report;
|
|
6314
|
+
}
|
|
6278
6315
|
let actionDetails;
|
|
6279
6316
|
try {
|
|
6280
6317
|
actionDetails = await api.getActionDetails(profile.actionId);
|
|
@@ -6287,12 +6324,20 @@ async function testSyncProfile(api, profile) {
|
|
|
6287
6324
|
});
|
|
6288
6325
|
return report;
|
|
6289
6326
|
}
|
|
6327
|
+
if (actionDetails.tags?.includes("custom")) {
|
|
6328
|
+
checks.push({
|
|
6329
|
+
name: "action is passthrough (not custom)",
|
|
6330
|
+
ok: false,
|
|
6331
|
+
detail: `Action ${profile.actionId} is tagged "custom". Sync only supports passthrough actions. Run 'one actions search ${profile.platform} "${profile.model}"' to find one.`
|
|
6332
|
+
});
|
|
6333
|
+
return report;
|
|
6334
|
+
}
|
|
6290
6335
|
let responseData;
|
|
6291
6336
|
try {
|
|
6292
6337
|
const result = await api.executePassthroughRequest({
|
|
6293
6338
|
platform: profile.platform,
|
|
6294
6339
|
actionId: profile.actionId,
|
|
6295
|
-
connectionKey
|
|
6340
|
+
connectionKey,
|
|
6296
6341
|
pathVariables: profile.pathVars,
|
|
6297
6342
|
queryParams,
|
|
6298
6343
|
data: Object.keys(bodyParams).length > 0 ? bodyParams : void 0
|
|
@@ -7451,11 +7496,11 @@ async function syncInitCommand(platform, model, options) {
|
|
|
7451
7496
|
(c) => c.platform === platform
|
|
7452
7497
|
);
|
|
7453
7498
|
if (platformConns.length === 1) {
|
|
7454
|
-
|
|
7455
|
-
inferred?.reasoning.push(`connectionKey: auto-resolved (only one ${platform} connection)`);
|
|
7499
|
+
inferred?.reasoning.push(`connection: { platform: "${platform}" } resolves to the single available connection`);
|
|
7456
7500
|
} else if (platformConns.length > 1) {
|
|
7501
|
+
const tags = platformConns.map((c) => c.tags?.join(",") ?? "(no tag)").join("; ");
|
|
7457
7502
|
inferred?.reasoning.push(
|
|
7458
|
-
`
|
|
7503
|
+
`connection: ${platformConns.length} ${platform} connections found (tags: ${tags}). Add a \`tag\` field to the connection ref to disambiguate.`
|
|
7459
7504
|
);
|
|
7460
7505
|
}
|
|
7461
7506
|
} catch {
|
|
@@ -7539,6 +7584,11 @@ Run with --config to save:
|
|
|
7539
7584
|
...patch.pagination ?? {}
|
|
7540
7585
|
}
|
|
7541
7586
|
};
|
|
7587
|
+
if (patch.connection && !patch.connectionKey) {
|
|
7588
|
+
delete profile.connectionKey;
|
|
7589
|
+
} else if (patch.connectionKey && !patch.connection) {
|
|
7590
|
+
delete profile.connection;
|
|
7591
|
+
}
|
|
7542
7592
|
try {
|
|
7543
7593
|
writeProfile(profile);
|
|
7544
7594
|
if (isAgentMode()) {
|
|
@@ -8630,12 +8680,12 @@ one --agent sync models stripe
|
|
|
8630
8680
|
# 2. Init \u2014 one command does everything:
|
|
8631
8681
|
# - resolves action ID
|
|
8632
8682
|
# - infers pagination, resultsPath, idField, pathVars from knowledge
|
|
8633
|
-
# -
|
|
8683
|
+
# - sets connection: { platform } so the profile survives re-auth
|
|
8634
8684
|
# - auto-runs sync test if profile is complete
|
|
8635
8685
|
one --agent sync init stripe balanceTransactions
|
|
8636
8686
|
# Response includes _complete:true and _test results when fully resolved.
|
|
8637
|
-
#
|
|
8638
|
-
one --agent sync init
|
|
8687
|
+
# Multi-account platforms (e.g. two Gmail connections) need a tag:
|
|
8688
|
+
one --agent sync init gmail gmailThreads --config '{"connection":{"platform":"gmail","tag":"work@example.com"}}'
|
|
8639
8689
|
|
|
8640
8690
|
# 3. Sync
|
|
8641
8691
|
one --agent sync run stripe
|
|
@@ -8646,10 +8696,29 @@ one --agent sync search "refund" --platform stripe
|
|
|
8646
8696
|
one --agent sync sql stripe "SELECT count(*) FROM balanceTransactions"
|
|
8647
8697
|
\`\`\`
|
|
8648
8698
|
|
|
8699
|
+
## Connection Resolution \u2014 late-bound by default
|
|
8700
|
+
|
|
8701
|
+
Sync profiles use a late-bound connection ref instead of a hardcoded key, so re-auth (which always mints a new key) doesn't break the profile:
|
|
8702
|
+
|
|
8703
|
+
\`\`\`json
|
|
8704
|
+
// recommended \u2014 survives re-auth
|
|
8705
|
+
"connection": { "platform": "gmail" }
|
|
8706
|
+
|
|
8707
|
+
// multi-account: disambiguate with the connection's tag
|
|
8708
|
+
"connection": { "platform": "gmail", "tag": "work@example.com" }
|
|
8709
|
+
|
|
8710
|
+
// legacy \u2014 still works for backwards compat, but breaks on re-auth
|
|
8711
|
+
"connectionKey": "live::gmail::default::abc123..."
|
|
8712
|
+
\`\`\`
|
|
8713
|
+
|
|
8714
|
+
The resolver runs at \`sync test\` and \`sync run\` time. Resolution errors (no connection, ambiguous tag, missing tag with multiple connections) surface as the first check in the test report, before any HTTP call.
|
|
8715
|
+
|
|
8716
|
+
To migrate an existing profile: replace the \`connectionKey\` field with \`connection: { platform: "<platform>" }\`. Tags only needed when more than one connection exists for the platform.
|
|
8717
|
+
|
|
8649
8718
|
## Auto-Inference
|
|
8650
8719
|
|
|
8651
8720
|
\`sync init\` without \`--config\` does all of this automatically:
|
|
8652
|
-
- **
|
|
8721
|
+
- **connection** \u2014 defaults to \`{ platform: "<platform>" }\` (late-bound). When multiple connections exist, init surfaces the available tags so the agent can add one to the ref.
|
|
8653
8722
|
- **Pagination** \u2014 Stripe id-pagination, Notion body-cursor, HubSpot/Google token, offset, link. Inapplicable fields stripped (no nextPath for offset, no passAs for none)
|
|
8654
8723
|
- **resultsPath** \u2014 generic keys (data, results, items) + platform-specific (model name stripped of platform prefix: attioCompanies \u2192 companies). Use \`""\`, \`"$"\`, or \`"."\` for responses that return a bare array at the root (e.g. Hacker News \`/v0/topstories.json\`); primitive array elements are auto-wrapped as \`{ [idField]: value }\`.
|
|
8655
8724
|
- **idField** \u2014 id, _id, uuid
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"description": "Attio companies — CRM company records with domains, industry, and custom attributes",
|
|
3
3
|
"platform": "attio",
|
|
4
4
|
"model": "attioCompanies",
|
|
5
|
-
"
|
|
5
|
+
"connection": { "platform": "attio" },
|
|
6
6
|
"actionId": "conn_mod_def::GJt0lFZ6kpk::attio-companies-list",
|
|
7
7
|
"resultsPath": "companies",
|
|
8
8
|
"idField": "id",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"description": "Attio people — CRM contact records with emails, phone numbers, and custom attributes",
|
|
3
3
|
"platform": "attio",
|
|
4
4
|
"model": "attioPeople",
|
|
5
|
-
"
|
|
5
|
+
"connection": { "platform": "attio" },
|
|
6
6
|
"identityKey": "primary_email_address",
|
|
7
7
|
"actionId": "conn_mod_def::GJt0lFZ6kpk::attio-people-list",
|
|
8
8
|
"resultsPath": "people",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"description": "Fathom meetings — recorded meetings with transcripts, summaries, action items, and attendees",
|
|
3
3
|
"platform": "fathom",
|
|
4
4
|
"model": "meetings",
|
|
5
|
-
"
|
|
5
|
+
"connection": { "platform": "fathom" },
|
|
6
6
|
"actionId": "conn_mod_def::fathom::meetings-list",
|
|
7
7
|
"resultsPath": "items",
|
|
8
8
|
"idField": "recording_id",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"description": "Gmail email threads — primary inbox with full message bodies (no attachments)",
|
|
3
3
|
"platform": "gmail",
|
|
4
4
|
"model": "gmailThreads",
|
|
5
|
-
"
|
|
5
|
+
"connection": { "platform": "gmail" },
|
|
6
6
|
"actionId": "conn_mod_def::GJ3ok-Q0D40::oLWNlcx4QDORaL_18z-MsQ",
|
|
7
7
|
"resultsPath": "threads",
|
|
8
8
|
"idField": "id",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"description": "Google Calendar events — meetings, appointments, and all-day events with attendees and location",
|
|
3
3
|
"platform": "google-calendar",
|
|
4
4
|
"model": "events",
|
|
5
|
-
"
|
|
5
|
+
"connection": { "platform": "google-calendar" },
|
|
6
6
|
"actionId": "conn_mod_def::GJ5x5pOh2TU::gcal-events-list",
|
|
7
7
|
"resultsPath": "items",
|
|
8
8
|
"idField": "id",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"description": "Hacker News top story IDs — up to 500 item IDs at /v0/topstories.json (root-array response, primitives wrapped as { id })",
|
|
3
3
|
"platform": "hacker-news",
|
|
4
4
|
"model": "topStories",
|
|
5
|
-
"
|
|
5
|
+
"connection": { "platform": "hacker-news" },
|
|
6
6
|
"actionId": "conn_mod_def::GJ3108Dwmm4::avAMAq7HQtW6PT8JhPg5vA",
|
|
7
7
|
"resultsPath": "",
|
|
8
8
|
"idField": "id",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"description": "Notion pages and databases — full workspace search with titles, properties, and metadata",
|
|
3
3
|
"platform": "notion",
|
|
4
4
|
"model": "search",
|
|
5
|
-
"
|
|
5
|
+
"connection": { "platform": "notion" },
|
|
6
6
|
"actionId": "conn_mod_def::GJ5En67fz04::-CJAS419SVWm7L2l6brp6A",
|
|
7
7
|
"resultsPath": "results",
|
|
8
8
|
"idField": "id",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"description": "Stripe balance transactions — payments, refunds, payouts, and fees with amount, currency, and status",
|
|
3
3
|
"platform": "stripe",
|
|
4
4
|
"model": "balanceTransactions",
|
|
5
|
-
"
|
|
5
|
+
"connection": { "platform": "stripe" },
|
|
6
6
|
"actionId": "conn_mod_def::GGx6clhYjSQ::3kEaM3HQTA2JRfW3DzXC4g",
|
|
7
7
|
"resultsPath": "data",
|
|
8
8
|
"idField": "id",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"description": "Stripe customers — customer records with email, name, payment methods, and subscription status",
|
|
3
3
|
"platform": "stripe",
|
|
4
4
|
"model": "customers",
|
|
5
|
-
"
|
|
5
|
+
"connection": { "platform": "stripe" },
|
|
6
6
|
"actionId": "conn_mod_def::GGx6clhYjSQ::customers-list",
|
|
7
7
|
"resultsPath": "data",
|
|
8
8
|
"idField": "id",
|
package/skills/one/SKILL.md
CHANGED
|
@@ -175,6 +175,8 @@ one sync schedule add stripe --every 1h
|
|
|
175
175
|
|
|
176
176
|
**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.
|
|
177
177
|
|
|
178
|
+
**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.
|
|
179
|
+
|
|
178
180
|
**Advanced features** (enrich, transform, exclude, identityKey, hooks, --full-refresh, --where-sql delete, cursor resume): run `one guide sync` for the full reference.
|
|
179
181
|
|
|
180
182
|
## Beyond Single Actions
|