relay-companion 0.1.548 → 0.1.549
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.
|
@@ -10,6 +10,10 @@ function applicationReleaseBase(channel = "stable") {
|
|
|
10
10
|
return `${CHANNEL_ORIGINS[channel]}/v1/application-releases${channel === "stable" ? "" : `/${channel}`}`;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
function applicationManifestUrl(channel = "stable") {
|
|
14
|
+
return `${applicationReleaseBase(channel)}/${channel === "stable" ? "stable-v3/" : ""}manifest.json`;
|
|
15
|
+
}
|
|
16
|
+
|
|
13
17
|
function validateApplicationRelease(payload, { version, sourceSha, channel } = {}) {
|
|
14
18
|
if (![1, 2].includes(payload?.schema) || payload.product !== "Relay Application" || payload.version !== version
|
|
15
19
|
|| payload.sourceSha !== sourceSha || !/^\d+\.\d+\.\d+$/.test(version || "") || !/^[a-f0-9]{40}$/.test(sourceSha || "")
|
|
@@ -59,4 +63,4 @@ async function verifyApplicationArtifact(file, artifact) {
|
|
|
59
63
|
return true;
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
module.exports = { PLATFORMS, CHANNEL_ORIGINS, applicationReleaseBase, validateApplicationRelease, verifyApplicationRelease, verifyApplicationArtifact };
|
|
66
|
+
module.exports = { PLATFORMS, CHANNEL_ORIGINS, applicationReleaseBase, applicationManifestUrl, validateApplicationRelease, verifyApplicationRelease, verifyApplicationArtifact };
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
// Capability only. Every background handoff additionally requires a fresh,
|
|
3
3
|
// signed channel-specific policy. An empty device list migrates nobody.
|
|
4
4
|
module.exports = Object.freeze({ enabled: true,
|
|
5
|
-
manifestUrl: "
|
|
5
|
+
manifestUrl: require("./application-release.cjs").applicationManifestUrl("stable") });
|
|
@@ -11,7 +11,7 @@ const rollout = require("./application-rollout.cjs");
|
|
|
11
11
|
const CHECK_MS = 6 * 60 * 60_000;
|
|
12
12
|
|
|
13
13
|
async function readOffer({ fetchImpl = globalThis.fetch, trustStore = trust, channel = "stable" } = {}) {
|
|
14
|
-
const response = await fetchImpl(
|
|
14
|
+
const response = await fetchImpl(release.applicationManifestUrl(channel), { redirect: "error", signal: AbortSignal.timeout(15_000), headers: { "Cache-Control": "no-cache" } });
|
|
15
15
|
if (response.status === 404) return null;
|
|
16
16
|
if (!response.ok) throw new Error(`Native application offer unavailable (${response.status})`);
|
|
17
17
|
let bytes = 0; const chunks = [];
|
package/package.json
CHANGED
package/skill/manifest.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"name": "relay",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.67",
|
|
5
5
|
"consentVersion": 2,
|
|
6
|
-
"baseUrl": "https://sendrelays.com/skills/relay/v1.1.
|
|
6
|
+
"baseUrl": "https://sendrelays.com/skills/relay/v1.1.67",
|
|
7
7
|
"files": [
|
|
8
8
|
{
|
|
9
9
|
"path": "SKILL.md",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
{
|
|
23
23
|
"path": "scripts/relay-protocol.mjs",
|
|
24
|
-
"sha256": "
|
|
24
|
+
"sha256": "bfbc56ecaf202f77002da7eec4a33c5bfc7f3c6574df3dd385766684eb5458a3"
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"path": "scripts/relay-local.mjs",
|
|
@@ -22,6 +22,7 @@ let transport = "auto";
|
|
|
22
22
|
let lastTransport = "";
|
|
23
23
|
const DIRECT_RECOVERY = "To renew browser approval, use connect-start <approved-api-origin> <invite-token> codex|claude_code, approve the returned URL in your browser, then connect-finish. Your own invitation from the Relay website can be used; no Companion or device enrollment is needed.";
|
|
24
24
|
const SAFE_GET = [
|
|
25
|
+
/^\/v1\/share-links\/[A-Za-z0-9_-]+\/stats(?:\?.*)?$/,
|
|
25
26
|
/^\/v1\/contact-groups$/,
|
|
26
27
|
/^\/v1\/chats(?:\?.*)?$/,
|
|
27
28
|
/^\/v1\/chats\/[A-Za-z0-9_-]+(?:\?.*)?$/,
|
|
@@ -35,6 +36,7 @@ const SAFE_GET = [
|
|
|
35
36
|
/^\/v1\/share-links\/[A-Za-z0-9_-]+$/,
|
|
36
37
|
];
|
|
37
38
|
const SAFE_POST = [
|
|
39
|
+
/^\/v1\/share-links\/[A-Za-z0-9_-]+\/placements$/,
|
|
38
40
|
/^\/v1\/relays$/,
|
|
39
41
|
/^\/v1\/relays\/[A-Za-z0-9_-]+\/forward$/,
|
|
40
42
|
/^\/v1\/relays\/[A-Za-z0-9_-]+\/read$/,
|
|
@@ -45,6 +47,7 @@ const SAFE_POST = [
|
|
|
45
47
|
// A person may correct a message they sent, or take back a link they minted
|
|
46
48
|
// or a message they sent, from the same conversation. Both are sender-only on
|
|
47
49
|
// the server and converge on an exact retry.
|
|
50
|
+
const SAFE_PUT = [/^\/v1\/share-links\/[A-Za-z0-9_-]+\/placements\/[A-Za-z0-9_-]+\/snapshot$/];
|
|
48
51
|
const SAFE_PATCH = [
|
|
49
52
|
/^\/v1\/messages\/[A-Za-z0-9_-]+$/,
|
|
50
53
|
];
|
|
@@ -217,7 +220,7 @@ async function readStdin() {
|
|
|
217
220
|
}
|
|
218
221
|
|
|
219
222
|
function allowed(method, requestPath) {
|
|
220
|
-
const list = method === "GET" ? SAFE_GET : method === "POST" ? SAFE_POST : method === "PATCH" ? SAFE_PATCH : method === "DELETE" ? SAFE_DELETE : [];
|
|
223
|
+
const list = method === "PUT" ? SAFE_PUT : method === "GET" ? SAFE_GET : method === "POST" ? SAFE_POST : method === "PATCH" ? SAFE_PATCH : method === "DELETE" ? SAFE_DELETE : [];
|
|
221
224
|
return list.some((pattern) => pattern.test(requestPath));
|
|
222
225
|
}
|
|
223
226
|
|
|
@@ -691,6 +694,9 @@ const DIRECT_TOOLS = {
|
|
|
691
694
|
relay_chat_fetch: directTool("Read a page of one chat without receipts. Defaults to the newest 25, oldest first. HTTPS requires chatId. Continue with nextBeforeCursor or nextAfterCursor; a page is not the full history.", { chatId: idField, limit: { type: "integer", minimum: 1, maximum: 200 }, beforeCursor: { type: "string" }, afterCursor: { type: "string" } }, ["chatId"], { full: true }),
|
|
692
695
|
relay_thread_fetch: directTool("Read related Relays by their internal threadId without receipts.", { threadId: idField }, ["threadId"]),
|
|
693
696
|
relay_inbox_list: directTool("Read recent inbox metadata, or up to 20 exact Relay packet envelopes in items [{relayId, ...response}]. Does not send receipts. Todo queries require Companion.", { relayIds: { type: "array", items: idField, minItems: 1, maxItems: 20 } }),
|
|
697
|
+
relay_share_stats: directTool("Read owner-only share statistics. Separates legacy opens, estimated external browsers, button attempts, successful copies, agent fetches and account outcomes. Does not mark read. Browser estimates are not people; owner/test events are excluded. Optional from/to are ISO timestamps.", {"relayId": {"type": "string", "minLength": 1}, "from": {"type": "string"}, "to": {"type": "string"}}, ["relayId"], { full: true, readOnly: true }),
|
|
698
|
+
relay_share_placement: directTool("Create an attributed URL for an existing share link. This sends nothing. Use separate placements for X replies and internal previews; test=true excludes that placement from acquisition. Reuse the same idempotency key on retries.", {"relayId": {"type": "string", "minLength": 1}, "idempotencyKey": {"type": "string", "minLength": 8}, "label": {"type": "string", "maxLength": 120}, "source": {"type": "string", "enum": ["x", "relay", "internal", "other"]}, "postId": {"type": "string", "pattern": "^[0-9]{1,30}$"}, "test": {"type": "boolean"}}, ["relayId", "idempotencyKey", "label", "source"], { full: true, readOnly: false }),
|
|
699
|
+
relay_share_snapshot: directTool("Save a manually observed X analytics snapshot for an X placement. Keep X aggregate impressions and link clicks separate from Relay visits; never infer unique people or subtract guessed self clicks.", {"relayId": {"type": "string", "minLength": 1}, "placementId": {"type": "string"}, "observedAt": {"type": "string"}, "impressions": {"type": "integer", "minimum": 0}, "linkClicks": {"type": "integer", "minimum": 0}}, ["relayId", "placementId", "observedAt", "impressions", "linkClicks"], { full: true, readOnly: false }),
|
|
694
700
|
relay_sent_list: directTool("Read sent history. Optional recipient matches a name or address.", { recipient: stringField, limit: { type: "integer", minimum: 1, maximum: 100 } }),
|
|
695
701
|
relay_mark_read: directTool("Send a read receipt only when the person requested reading this exact Relay and you present it.", { relayId: idField, idempotencyKey: stringField }, ["relayId", "idempotencyKey"], { readOnly: false }),
|
|
696
702
|
relay_send: directTool("Send authorized correspondence or a Task using a resolved recipient, a title and both documents. Preserve the exact body and idempotency key on retry. HTTPS cannot send to unresolved names or email addresses; resolve an existing contact first or mint a link.", {
|
|
@@ -772,6 +778,15 @@ async function directToolCommand(command, body, config) {
|
|
|
772
778
|
for (const relayId of new Set(args.relayIds)) items.push({ relayId, ...await request("GET", `/v1/relays/${relayId}`) });
|
|
773
779
|
value = { items, readStateChanged: false, readReceiptsSent: false };
|
|
774
780
|
} else value = await request("GET", "/v1/inbox?view=summary");
|
|
781
|
+
} else if (name === "relay_share_stats") {
|
|
782
|
+
const query = new URLSearchParams(Object.entries({ from: args.from, to: args.to }).filter(([, v]) => v !== undefined));
|
|
783
|
+
value = await request("GET", `/v1/share-links/${encodeURIComponent(args.relayId)}/stats?${query}`);
|
|
784
|
+
} else if (name === "relay_share_placement") {
|
|
785
|
+
const { relayId, ...body } = args;
|
|
786
|
+
value = await request("POST", `/v1/share-links/${encodeURIComponent(relayId)}/placements`, body);
|
|
787
|
+
} else if (name === "relay_share_snapshot") {
|
|
788
|
+
const { relayId, placementId, ...body } = args;
|
|
789
|
+
value = await request("PUT", `/v1/share-links/${encodeURIComponent(relayId)}/placements/${encodeURIComponent(placementId)}/snapshot`, body);
|
|
775
790
|
} else if (name === "relay_sent_list") {
|
|
776
791
|
const result = await request("GET", "/v1/sent?limit=100");
|
|
777
792
|
const needle = String(args.recipient || "").toLowerCase();
|