@withone/cli 1.36.0 → 1.37.1
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/dist/index.js +181 -101
- package/package.json +1 -1
- package/skills/one/references/relay.md +23 -0
package/dist/index.js
CHANGED
|
@@ -4334,6 +4334,7 @@ async function relayCreateCommand(options) {
|
|
|
4334
4334
|
if (options.description) body.description = options.description;
|
|
4335
4335
|
if (options.eventFilters) body.eventFilters = parseJsonArg2(options.eventFilters, "--event-filters");
|
|
4336
4336
|
if (options.tags) body.tags = parseJsonArg2(options.tags, "--tags");
|
|
4337
|
+
if (options.metadata) body.metadata = parseJsonArg2(options.metadata, "--metadata");
|
|
4337
4338
|
if (options.createWebhook) body.createWebhook = true;
|
|
4338
4339
|
const result = await api.createRelayEndpoint(body);
|
|
4339
4340
|
if (isAgentMode()) {
|
|
@@ -4348,6 +4349,10 @@ async function relayCreateCommand(options) {
|
|
|
4348
4349
|
if (result.description) console.log(` ${pc8.dim("Description:")} ${result.description}`);
|
|
4349
4350
|
if (result.eventFilters?.length) console.log(` ${pc8.dim("Events:")} ${result.eventFilters.join(", ")}`);
|
|
4350
4351
|
if (result.webhookPayload?.id) console.log(` ${pc8.dim("Webhook ID:")} ${result.webhookPayload.id}`);
|
|
4352
|
+
if (result.warning) {
|
|
4353
|
+
console.log();
|
|
4354
|
+
console.log(` ${pc8.yellow("\u26A0 Warning:")} ${result.warning}`);
|
|
4355
|
+
}
|
|
4351
4356
|
console.log();
|
|
4352
4357
|
} catch (error2) {
|
|
4353
4358
|
spinner6.stop("Failed to create relay endpoint");
|
|
@@ -5262,6 +5267,80 @@ function runShellHook(command, events) {
|
|
|
5262
5267
|
});
|
|
5263
5268
|
}
|
|
5264
5269
|
|
|
5270
|
+
// src/lib/sync/transform.ts
|
|
5271
|
+
import { spawn as spawn3 } from "child_process";
|
|
5272
|
+
var TRANSFORM_TIMEOUT_MS = 6e4;
|
|
5273
|
+
async function transformRecords(command, records) {
|
|
5274
|
+
const input = JSON.stringify(records);
|
|
5275
|
+
return new Promise((resolve) => {
|
|
5276
|
+
const child = spawn3("sh", ["-c", command], {
|
|
5277
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5278
|
+
});
|
|
5279
|
+
let stdout = "";
|
|
5280
|
+
let stderr = "";
|
|
5281
|
+
child.stdout.on("data", (chunk) => {
|
|
5282
|
+
stdout += chunk.toString();
|
|
5283
|
+
});
|
|
5284
|
+
child.stderr.on("data", (chunk) => {
|
|
5285
|
+
stderr += chunk.toString();
|
|
5286
|
+
});
|
|
5287
|
+
const timer = setTimeout(() => {
|
|
5288
|
+
try {
|
|
5289
|
+
child.kill();
|
|
5290
|
+
} catch {
|
|
5291
|
+
}
|
|
5292
|
+
if (!isAgentMode()) {
|
|
5293
|
+
process.stderr.write(` Transform timed out after ${TRANSFORM_TIMEOUT_MS / 1e3}s \u2014 using original records
|
|
5294
|
+
`);
|
|
5295
|
+
}
|
|
5296
|
+
resolve(null);
|
|
5297
|
+
}, TRANSFORM_TIMEOUT_MS);
|
|
5298
|
+
child.on("exit", (code) => {
|
|
5299
|
+
clearTimeout(timer);
|
|
5300
|
+
if (stderr.trim() && !isAgentMode()) {
|
|
5301
|
+
process.stderr.write(` Transform stderr: ${stderr.trim()}
|
|
5302
|
+
`);
|
|
5303
|
+
}
|
|
5304
|
+
if (code !== 0) {
|
|
5305
|
+
if (!isAgentMode()) {
|
|
5306
|
+
process.stderr.write(` Transform exited with code ${code} \u2014 using original records
|
|
5307
|
+
`);
|
|
5308
|
+
}
|
|
5309
|
+
resolve(null);
|
|
5310
|
+
return;
|
|
5311
|
+
}
|
|
5312
|
+
try {
|
|
5313
|
+
const parsed = JSON.parse(stdout.trim());
|
|
5314
|
+
if (!Array.isArray(parsed)) {
|
|
5315
|
+
if (!isAgentMode()) {
|
|
5316
|
+
process.stderr.write(` Transform returned non-array JSON \u2014 using original records
|
|
5317
|
+
`);
|
|
5318
|
+
}
|
|
5319
|
+
resolve(null);
|
|
5320
|
+
return;
|
|
5321
|
+
}
|
|
5322
|
+
resolve(parsed);
|
|
5323
|
+
} catch {
|
|
5324
|
+
if (!isAgentMode()) {
|
|
5325
|
+
process.stderr.write(` Transform returned invalid JSON \u2014 using original records
|
|
5326
|
+
`);
|
|
5327
|
+
}
|
|
5328
|
+
resolve(null);
|
|
5329
|
+
}
|
|
5330
|
+
});
|
|
5331
|
+
child.on("error", (err) => {
|
|
5332
|
+
clearTimeout(timer);
|
|
5333
|
+
if (!isAgentMode()) {
|
|
5334
|
+
process.stderr.write(` Transform failed to start: ${err.message} \u2014 using original records
|
|
5335
|
+
`);
|
|
5336
|
+
}
|
|
5337
|
+
resolve(null);
|
|
5338
|
+
});
|
|
5339
|
+
child.stdin.write(input);
|
|
5340
|
+
child.stdin.end();
|
|
5341
|
+
});
|
|
5342
|
+
}
|
|
5343
|
+
|
|
5265
5344
|
// src/lib/sync/enrich.ts
|
|
5266
5345
|
var DEFAULT_CONCURRENCY = 5;
|
|
5267
5346
|
var MAX_RETRIES = 3;
|
|
@@ -5351,7 +5430,7 @@ function pickFields(obj, fields) {
|
|
|
5351
5430
|
}
|
|
5352
5431
|
return result;
|
|
5353
5432
|
}
|
|
5354
|
-
async function enrichPhase(api, db, config2, model, idField, connectionKey, platform) {
|
|
5433
|
+
async function enrichPhase(api, db, config2, model, idField, connectionKey, platform, ctx = {}) {
|
|
5355
5434
|
const startTime = Date.now();
|
|
5356
5435
|
const tsField = config2.timestampField ?? "_enriched_at";
|
|
5357
5436
|
const safeTable = model.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
@@ -5399,6 +5478,7 @@ async function enrichPhase(api, db, config2, model, idField, connectionKey, plat
|
|
|
5399
5478
|
);
|
|
5400
5479
|
let batchHitRateLimit = false;
|
|
5401
5480
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5481
|
+
const pending = [];
|
|
5402
5482
|
for (let j = 0; j < results.length; j++) {
|
|
5403
5483
|
const result = results[j];
|
|
5404
5484
|
const row = batch[j];
|
|
@@ -5413,28 +5493,7 @@ async function enrichPhase(api, db, config2, model, idField, connectionKey, plat
|
|
|
5413
5493
|
}
|
|
5414
5494
|
const merged = config2.merge !== false ? deepMerge(row, enrichedData) : { ...enrichedData, [idField]: id };
|
|
5415
5495
|
merged[tsField] = now;
|
|
5416
|
-
|
|
5417
|
-
const values = [];
|
|
5418
|
-
for (const [key, val] of Object.entries(merged)) {
|
|
5419
|
-
if (key === idField) continue;
|
|
5420
|
-
setClauses.push(`"${key}" = ?`);
|
|
5421
|
-
values.push(prepareValue2(val));
|
|
5422
|
-
}
|
|
5423
|
-
values.push(prepareValue2(id));
|
|
5424
|
-
if (setClauses.length > 0) {
|
|
5425
|
-
const existingCols = new Set(db.prepare(`PRAGMA table_info("${safeTable}")`).all().map((c) => c.name));
|
|
5426
|
-
for (const [key, val] of Object.entries(merged)) {
|
|
5427
|
-
if (!existingCols.has(key)) {
|
|
5428
|
-
const colType = detectColumnType2(val);
|
|
5429
|
-
db.exec(`ALTER TABLE "${safeTable}" ADD COLUMN "${key}" ${colType}`);
|
|
5430
|
-
existingCols.add(key);
|
|
5431
|
-
}
|
|
5432
|
-
}
|
|
5433
|
-
db.prepare(
|
|
5434
|
-
`UPDATE "${safeTable}" SET ${setClauses.join(", ")} WHERE "${safeIdField}" = ?`
|
|
5435
|
-
).run(...values);
|
|
5436
|
-
}
|
|
5437
|
-
enriched++;
|
|
5496
|
+
pending.push({ merged, id });
|
|
5438
5497
|
} else if (result.status === "fulfilled" && result.value === null) {
|
|
5439
5498
|
rateLimited++;
|
|
5440
5499
|
skipped++;
|
|
@@ -5443,6 +5502,62 @@ async function enrichPhase(api, db, config2, model, idField, connectionKey, plat
|
|
|
5443
5502
|
skipped++;
|
|
5444
5503
|
}
|
|
5445
5504
|
}
|
|
5505
|
+
let writes = pending;
|
|
5506
|
+
if (ctx.transform && pending.length > 0) {
|
|
5507
|
+
const transformed = await transformRecords(ctx.transform, pending.map((p10) => p10.merged));
|
|
5508
|
+
if (transformed) {
|
|
5509
|
+
const byId = /* @__PURE__ */ new Map();
|
|
5510
|
+
for (const r of transformed) byId.set(r[idField], r);
|
|
5511
|
+
writes = [];
|
|
5512
|
+
for (const p10 of pending) {
|
|
5513
|
+
const t = byId.get(p10.id);
|
|
5514
|
+
if (!t) continue;
|
|
5515
|
+
if (!t[tsField]) t[tsField] = now;
|
|
5516
|
+
writes.push({ merged: t, id: p10.id });
|
|
5517
|
+
}
|
|
5518
|
+
}
|
|
5519
|
+
}
|
|
5520
|
+
const hookEvents = [];
|
|
5521
|
+
for (const { merged, id } of writes) {
|
|
5522
|
+
if (ctx.exclude && ctx.exclude.length > 0) {
|
|
5523
|
+
stripExcludedFields(merged, ctx.exclude);
|
|
5524
|
+
}
|
|
5525
|
+
if (ctx.identityKey) {
|
|
5526
|
+
const raw = getByDotPath2(merged, ctx.identityKey);
|
|
5527
|
+
if (raw !== null && raw !== void 0) {
|
|
5528
|
+
merged._identity = String(raw).toLowerCase().trim();
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5531
|
+
const setClauses = [];
|
|
5532
|
+
const values = [];
|
|
5533
|
+
for (const [key, val] of Object.entries(merged)) {
|
|
5534
|
+
if (key === idField) continue;
|
|
5535
|
+
setClauses.push(`"${key}" = ?`);
|
|
5536
|
+
values.push(prepareValue2(val));
|
|
5537
|
+
}
|
|
5538
|
+
values.push(prepareValue2(id));
|
|
5539
|
+
if (setClauses.length > 0) {
|
|
5540
|
+
const existingCols = new Set(db.prepare(`PRAGMA table_info("${safeTable}")`).all().map((c) => c.name));
|
|
5541
|
+
for (const [key, val] of Object.entries(merged)) {
|
|
5542
|
+
if (!existingCols.has(key)) {
|
|
5543
|
+
const colType = detectColumnType2(val);
|
|
5544
|
+
db.exec(`ALTER TABLE "${safeTable}" ADD COLUMN "${key}" ${colType}`);
|
|
5545
|
+
existingCols.add(key);
|
|
5546
|
+
}
|
|
5547
|
+
}
|
|
5548
|
+
db.prepare(
|
|
5549
|
+
`UPDATE "${safeTable}" SET ${setClauses.join(", ")} WHERE "${safeIdField}" = ?`
|
|
5550
|
+
).run(...values);
|
|
5551
|
+
}
|
|
5552
|
+
enriched++;
|
|
5553
|
+
if (ctx.onUpdate || ctx.onChange) {
|
|
5554
|
+
hookEvents.push({ type: "update", platform, model, record: merged, timestamp: now });
|
|
5555
|
+
}
|
|
5556
|
+
}
|
|
5557
|
+
if (hookEvents.length > 0) {
|
|
5558
|
+
const hook = ctx.onUpdate || ctx.onChange;
|
|
5559
|
+
if (hook) await fireHooks(hook, hookEvents);
|
|
5560
|
+
}
|
|
5446
5561
|
if (batchHitRateLimit) {
|
|
5447
5562
|
concurrency = Math.max(1, Math.floor(concurrency / 2));
|
|
5448
5563
|
if (!isAgentMode()) {
|
|
@@ -5522,80 +5637,6 @@ function detectColumnType2(value) {
|
|
|
5522
5637
|
return "TEXT";
|
|
5523
5638
|
}
|
|
5524
5639
|
|
|
5525
|
-
// src/lib/sync/transform.ts
|
|
5526
|
-
import { spawn as spawn3 } from "child_process";
|
|
5527
|
-
var TRANSFORM_TIMEOUT_MS = 6e4;
|
|
5528
|
-
async function transformRecords(command, records) {
|
|
5529
|
-
const input = JSON.stringify(records);
|
|
5530
|
-
return new Promise((resolve) => {
|
|
5531
|
-
const child = spawn3("sh", ["-c", command], {
|
|
5532
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
5533
|
-
});
|
|
5534
|
-
let stdout = "";
|
|
5535
|
-
let stderr = "";
|
|
5536
|
-
child.stdout.on("data", (chunk) => {
|
|
5537
|
-
stdout += chunk.toString();
|
|
5538
|
-
});
|
|
5539
|
-
child.stderr.on("data", (chunk) => {
|
|
5540
|
-
stderr += chunk.toString();
|
|
5541
|
-
});
|
|
5542
|
-
const timer = setTimeout(() => {
|
|
5543
|
-
try {
|
|
5544
|
-
child.kill();
|
|
5545
|
-
} catch {
|
|
5546
|
-
}
|
|
5547
|
-
if (!isAgentMode()) {
|
|
5548
|
-
process.stderr.write(` Transform timed out after ${TRANSFORM_TIMEOUT_MS / 1e3}s \u2014 using original records
|
|
5549
|
-
`);
|
|
5550
|
-
}
|
|
5551
|
-
resolve(null);
|
|
5552
|
-
}, TRANSFORM_TIMEOUT_MS);
|
|
5553
|
-
child.on("exit", (code) => {
|
|
5554
|
-
clearTimeout(timer);
|
|
5555
|
-
if (stderr.trim() && !isAgentMode()) {
|
|
5556
|
-
process.stderr.write(` Transform stderr: ${stderr.trim()}
|
|
5557
|
-
`);
|
|
5558
|
-
}
|
|
5559
|
-
if (code !== 0) {
|
|
5560
|
-
if (!isAgentMode()) {
|
|
5561
|
-
process.stderr.write(` Transform exited with code ${code} \u2014 using original records
|
|
5562
|
-
`);
|
|
5563
|
-
}
|
|
5564
|
-
resolve(null);
|
|
5565
|
-
return;
|
|
5566
|
-
}
|
|
5567
|
-
try {
|
|
5568
|
-
const parsed = JSON.parse(stdout.trim());
|
|
5569
|
-
if (!Array.isArray(parsed)) {
|
|
5570
|
-
if (!isAgentMode()) {
|
|
5571
|
-
process.stderr.write(` Transform returned non-array JSON \u2014 using original records
|
|
5572
|
-
`);
|
|
5573
|
-
}
|
|
5574
|
-
resolve(null);
|
|
5575
|
-
return;
|
|
5576
|
-
}
|
|
5577
|
-
resolve(parsed);
|
|
5578
|
-
} catch {
|
|
5579
|
-
if (!isAgentMode()) {
|
|
5580
|
-
process.stderr.write(` Transform returned invalid JSON \u2014 using original records
|
|
5581
|
-
`);
|
|
5582
|
-
}
|
|
5583
|
-
resolve(null);
|
|
5584
|
-
}
|
|
5585
|
-
});
|
|
5586
|
-
child.on("error", (err) => {
|
|
5587
|
-
clearTimeout(timer);
|
|
5588
|
-
if (!isAgentMode()) {
|
|
5589
|
-
process.stderr.write(` Transform failed to start: ${err.message} \u2014 using original records
|
|
5590
|
-
`);
|
|
5591
|
-
}
|
|
5592
|
-
resolve(null);
|
|
5593
|
-
});
|
|
5594
|
-
child.stdin.write(input);
|
|
5595
|
-
child.stdin.end();
|
|
5596
|
-
});
|
|
5597
|
-
}
|
|
5598
|
-
|
|
5599
5640
|
// src/lib/sync/runner.ts
|
|
5600
5641
|
var MAX_RETRIES_PER_PAGE = 3;
|
|
5601
5642
|
var DEFAULT_SINCE_DAYS = 90;
|
|
@@ -5995,11 +6036,22 @@ async function syncModel(api, profile, options) {
|
|
|
5995
6036
|
model,
|
|
5996
6037
|
profile.idField,
|
|
5997
6038
|
profile.connectionKey,
|
|
5998
|
-
platform
|
|
6039
|
+
platform,
|
|
6040
|
+
{
|
|
6041
|
+
transform: profile.transform,
|
|
6042
|
+
exclude: profile.exclude,
|
|
6043
|
+
identityKey: profile.identityKey,
|
|
6044
|
+
onInsert: profile.onInsert,
|
|
6045
|
+
onUpdate: profile.onUpdate,
|
|
6046
|
+
onChange: profile.onChange
|
|
6047
|
+
}
|
|
5999
6048
|
);
|
|
6000
6049
|
enrichedTotal = enrichResult.enriched;
|
|
6001
6050
|
enrichSkipped = enrichResult.skipped;
|
|
6002
6051
|
enrichRateLimited = enrichResult.rateLimited;
|
|
6052
|
+
if (hasHooks && (profile.onUpdate || profile.onChange)) {
|
|
6053
|
+
hooksUpdated += enrichResult.enriched;
|
|
6054
|
+
}
|
|
6003
6055
|
if (enrichResult.enriched > 0) {
|
|
6004
6056
|
rebuildFtsIndex(db, model);
|
|
6005
6057
|
}
|
|
@@ -8201,8 +8253,32 @@ one --agent relay deliveries --endpoint-id <id> # Check delivery status
|
|
|
8201
8253
|
2. **Get event types** \u2014 \`one --agent relay event-types <platform>\`
|
|
8202
8254
|
3. **Get source knowledge** \u2014 understand the incoming webhook payload shape (\`{{payload.*}}\` paths)
|
|
8203
8255
|
4. **Get destination knowledge** \u2014 understand the outgoing API body shape
|
|
8204
|
-
5. **Create endpoint** \u2014 with \`--create-webhook
|
|
8205
|
-
6. **Activate** \u2014 with passthrough action mapping source fields to destination fields
|
|
8256
|
+
5. **Create endpoint** \u2014 with \`--create-webhook\`, \`--event-filters\`, and \`--metadata\` if the source platform requires it
|
|
8257
|
+
6. **Activate** \u2014 with passthrough action mapping source fields to destination fields. **Do NOT pass \`--webhook-secret\`** when the endpoint was created with \`--create-webhook\` \u2014 the correct secret is auto-stored, and supplying a wrong one silently drops every delivery (events arrive, 0 deliveries).
|
|
8258
|
+
|
|
8259
|
+
## Platform-Specific Metadata (\`--metadata\`)
|
|
8260
|
+
|
|
8261
|
+
Some source platforms need extra identifiers to register a webhook. Pass these via \`--metadata '<json>'\` on \`relay create\`. Without them, \`--create-webhook\` silently fails:
|
|
8262
|
+
|
|
8263
|
+
| Platform | Required metadata keys |
|
|
8264
|
+
|---|---|
|
|
8265
|
+
| \`github\` | \`GITHUB_OWNER\`, \`GITHUB_REPOSITORY\` |
|
|
8266
|
+
| \`typeform\` | \`TYPEFORM_FORM_ID\` |
|
|
8267
|
+
| \`stripe\` | (none) |
|
|
8268
|
+
| \`airtable\` | (none) |
|
|
8269
|
+
| \`attio\` | (none) |
|
|
8270
|
+
| \`google-calendar\` | (none) |
|
|
8271
|
+
|
|
8272
|
+
Example (GitHub):
|
|
8273
|
+
|
|
8274
|
+
\`\`\`bash
|
|
8275
|
+
one --agent relay create \\
|
|
8276
|
+
--connection-key "live::github::default::<key>" \\
|
|
8277
|
+
--event-filters '["issues","pull_request"]' \\
|
|
8278
|
+
--metadata '{"GITHUB_OWNER":"my-org","GITHUB_REPOSITORY":"my-repo"}' \\
|
|
8279
|
+
--description "GitHub relay" \\
|
|
8280
|
+
--create-webhook
|
|
8281
|
+
\`\`\`
|
|
8206
8282
|
|
|
8207
8283
|
## Template Context
|
|
8208
8284
|
|
|
@@ -8253,6 +8329,8 @@ Any connected platform can be a destination via passthrough actions.
|
|
|
8253
8329
|
2. \`relay events --platform <p>\` \u2014 check events are arriving
|
|
8254
8330
|
3. \`relay deliveries --event-id <id>\` \u2014 check delivery status and errors
|
|
8255
8331
|
4. \`relay event <id>\` \u2014 inspect full payload to verify template paths
|
|
8332
|
+
|
|
8333
|
+
**If events arrive but 0 deliveries succeed**: you likely passed a wrong \`--webhook-secret\` on \`relay activate\`. When you created the endpoint with \`--create-webhook\`, the secret was registered with the source platform and stored automatically \u2014 do not pass it again on activate. Signature verification will fail silently and every event will be dropped.
|
|
8256
8334
|
`;
|
|
8257
8335
|
var GUIDE_CACHE = `# One Cache \u2014 Reference
|
|
8258
8336
|
|
|
@@ -8478,6 +8556,8 @@ The transform can be any command: \`jq\`, \`python3\`, a bash script, or \`one f
|
|
|
8478
8556
|
|
|
8479
8557
|
**Pipeline order:** fetch \u2192 enrich \u2192 transform \u2192 **exclude** \u2192 create table \u2192 schema evolution \u2192 upsert \u2192 hooks
|
|
8480
8558
|
|
|
8559
|
+
Transform, exclude, identityKey, and hooks all fire in **both** phases. In Phase 1 they run on the raw list page; in Phase 2 they run again on the merged (list + enriched) record so that transforms can extract columns from fields that only appear after enrichment. Phase 2 fires \`onUpdate\`/\`onChange\` for every row it writes \u2014 \`onInsert\` is Phase-1-only because the row already exists in SQL by the time enrichment runs.
|
|
8560
|
+
|
|
8481
8561
|
## Cross-Platform Identity
|
|
8482
8562
|
|
|
8483
8563
|
Add \`identityKey\` to a sync profile to extract a stable cross-platform identifier (e.g. email) into a normalized \`_identity\` column:
|
|
@@ -9416,7 +9496,7 @@ flow.command("scaffold [template]").description("Generate a workflow scaffold (t
|
|
|
9416
9496
|
await flowScaffoldCommand(template);
|
|
9417
9497
|
});
|
|
9418
9498
|
var relay = program.command("relay").alias("r").description("Receive webhooks from platforms and relay them via passthrough actions");
|
|
9419
|
-
relay.command("create").description("Create a new relay endpoint for a connection").requiredOption("--connection-key <key>", "Connection key for the source platform").option("--description <desc>", "Description of the relay endpoint").option("--event-filters <json>", `JSON array of event types to filter (e.g. '["customer.created"]')`).option("--tags <json>", "JSON array of tags").option("--create-webhook", "Automatically register the webhook with the source platform").action(async (options) => {
|
|
9499
|
+
relay.command("create").description("Create a new relay endpoint for a connection").requiredOption("--connection-key <key>", "Connection key for the source platform").option("--description <desc>", "Description of the relay endpoint").option("--event-filters <json>", `JSON array of event types to filter (e.g. '["customer.created"]')`).option("--tags <json>", "JSON array of tags").option("--metadata <json>", `JSON object of platform-specific metadata required to register the webhook (e.g. GitHub: '{"GITHUB_OWNER":"org","GITHUB_REPOSITORY":"repo"}', Typeform: '{"TYPEFORM_FORM_ID":"abc"}')`).option("--create-webhook", "Automatically register the webhook with the source platform").action(async (options) => {
|
|
9420
9500
|
await relayCreateCommand(options);
|
|
9421
9501
|
});
|
|
9422
9502
|
relay.command("list").alias("ls").description("List all relay endpoints").option("--limit <n>", "Max results per page").option("--page <n>", "Page number").action(async (options) => {
|
package/package.json
CHANGED
|
@@ -52,6 +52,25 @@ one --agent relay create \
|
|
|
52
52
|
|
|
53
53
|
Always use `--create-webhook` — it registers the webhook URL with the source platform automatically.
|
|
54
54
|
|
|
55
|
+
**Some source platforms require extra identifiers via `--metadata`:**
|
|
56
|
+
|
|
57
|
+
| Platform | Required metadata keys |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `github` | `GITHUB_OWNER`, `GITHUB_REPOSITORY` |
|
|
60
|
+
| `typeform` | `TYPEFORM_FORM_ID` |
|
|
61
|
+
| `stripe`, `airtable`, `attio`, `google-calendar` | (none) |
|
|
62
|
+
|
|
63
|
+
Without metadata, `--create-webhook` silently fails for these platforms. Example for GitHub:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
one --agent relay create \
|
|
67
|
+
--connection-key "live::github::default::<key>" \
|
|
68
|
+
--event-filters '["issues","pull_request"]' \
|
|
69
|
+
--metadata '{"GITHUB_OWNER":"my-org","GITHUB_REPOSITORY":"my-repo"}' \
|
|
70
|
+
--description "GitHub relay" \
|
|
71
|
+
--create-webhook
|
|
72
|
+
```
|
|
73
|
+
|
|
55
74
|
### Step 6: Activate with a passthrough action
|
|
56
75
|
|
|
57
76
|
```bash
|
|
@@ -66,6 +85,8 @@ one --agent relay activate <relay-id> --actions '[{
|
|
|
66
85
|
}]'
|
|
67
86
|
```
|
|
68
87
|
|
|
88
|
+
**Do NOT pass `--webhook-secret` on activate** when you created the endpoint with `--create-webhook`. The correct secret is registered with the source platform and stored automatically. Supplying a wrong one does not error — events arrive but every delivery is dropped during signature verification (0 deliveries). If you don't have a reason to override the secret, omit the flag.
|
|
89
|
+
|
|
69
90
|
## Template Context
|
|
70
91
|
|
|
71
92
|
| Variable | Description |
|
|
@@ -177,3 +198,5 @@ one --agent relay deliveries --event-id <id>
|
|
|
177
198
|
- Event filters on both the endpoint and individual actions must match
|
|
178
199
|
- Multiple actions can be attached to a single relay endpoint
|
|
179
200
|
- Missing template variables resolve to empty strings — verify `{{payload.*}}` paths against the actual payload
|
|
201
|
+
- GitHub and Typeform relays require `--metadata` on create; without it `--create-webhook` silently fails
|
|
202
|
+
- Never pass `--webhook-secret` on activate when the endpoint was created with `--create-webhook` — the auto-stored secret is correct, and a wrong one causes signature verification to drop every event silently
|