@vibes.diy/prompts 14.3.178 → 14.3.184
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/llms/backend.js +2 -2
- package/llms/backend.js.map +1 -1
- package/llms/backend.md +166 -2
- package/llms/connections.js +6 -1
- package/llms/connections.js.map +1 -1
- package/llms/connections.md +16 -4
- package/llms/stripe-subscriptions.js +1 -1
- package/llms/stripe-subscriptions.js.map +1 -1
- package/llms/stripe-subscriptions.md +280 -154
- package/llms/stripe.js +1 -1
- package/llms/stripe.js.map +1 -1
- package/llms/stripe.md +250 -130
- package/llms/use-vibe.md +5 -4
- package/llms/web-audio.js +1 -1
- package/llms/web-audio.js.map +1 -1
- package/llms/web-audio.md +509 -1
- package/package.json +5 -5
package/llms/backend.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const backendConfig = {
|
|
2
2
|
name: "backend",
|
|
3
3
|
label: "backend-js",
|
|
4
|
-
description: "server-side backend.js file: answer HTTP/webhook requests at the app's /_api URL, react to committed data changes, and run scheduled jobs — its writes go through the app's own access rules; reads a web page, feed or API the person names and hands it to the page through /_api; emails, notifies and reminds people through ctx.notify by handle — a weekly digest, a summary for the owner, a Monday email to the manager — and invites a recipient who is not in the app yet;
|
|
5
|
-
cues: ["email", "notify", "remind", "digest", "zapier", "catch hook", "webhook", "slack", "sheets", "crm"],
|
|
4
|
+
description: "server-side backend.js file: answer HTTP/webhook requests at the app's /_api URL, react to committed data changes, and run scheduled jobs — its writes go through the app's own access rules; reads a web page, feed or API the person names and hands it to the page through /_api; emails, notifies and reminds people through ctx.notify by handle — a weekly digest, a summary for the owner, a Monday email to the manager — and invites a recipient who is not in the app yet; sends a record to a tool the person names (Slack, Google Sheets, a CRM, Zapier) through a Zapier Catch Hook the owner connects; and takes records in from Zapier — a Google Forms response, a new sheet row, an email — at its /_api URL, checked against a shared secret",
|
|
5
|
+
cues: ["email", "notify", "remind", "digest", "zapier", "catch hook", "webhook", "slack", "sheets", "crm", "google forms"],
|
|
6
6
|
};
|
|
7
7
|
//# sourceMappingURL=backend.js.map
|
package/llms/backend.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../../jsr/llms/backend.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../../jsr/llms/backend.ts"],"names":[],"mappings":"AAaA,MAAM,CAAC,MAAM,aAAa,GAAc;IACtC,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,YAAY;IACnB,WAAW,EACT,guBAAguB;IAKluB,IAAI,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,cAAc,CAAC;CAC3H,CAAC"}
|
package/llms/backend.md
CHANGED
|
@@ -21,7 +21,9 @@ create or mention `backend.js`.
|
|
|
21
21
|
## When to use which handler — signal words
|
|
22
22
|
|
|
23
23
|
- **`fetch`** — "webhook", "OAuth", "callback", "API endpoint", "receive",
|
|
24
|
-
"form POST", "Stripe/GitHub/Sonos sends us…"
|
|
24
|
+
"form POST", "Stripe/GitHub/Sonos sends us…", "from Zapier", "when a form
|
|
25
|
+
is submitted in Google Forms", "every new row in my sheet". Something
|
|
26
|
+
**external → us**.
|
|
25
27
|
- **`onChange`** — "when X is created", "after a write", "notify", "send an
|
|
26
28
|
email", "aggregate", "moderate", "sync to…". An **internal write → a side
|
|
27
29
|
effect**: `ctx.notify` for every person the app emails, notifies or reminds,
|
|
@@ -158,8 +160,12 @@ await ctx.notify({ toHandle: "alice", body: "Your turn", dedupeKey: "game-7-turn
|
|
|
158
160
|
ctx.log("info", "tick finished", { written: 3 }); // diagnostics — read back with: vibes-diy app logs owner/slug
|
|
159
161
|
const gh = await ctx.github.fetch("/repos/acme/site/issues", { method: "POST", body: { title: "Filed from the vibe" } }); // connected GitHub repo
|
|
160
162
|
const routes = await ctx.github.routes(); // [{ repo, accountLogin, binderHandle, installationId }] — who connected which repo; no GitHub call
|
|
163
|
+
const session = await ctx.stripe.fetch("/v1/checkout/sessions", { method: "POST", body: form, mode: "test" }); // the owner's connected Stripe account
|
|
164
|
+
const stripe = await ctx.stripe.status(); // { test: { available, state }, live: { available, state } }
|
|
161
165
|
```
|
|
162
166
|
|
|
167
|
+
- An app that takes money loads the `stripe` skill (one-time charges) or the `stripe-subscriptions` skill (recurring): the Connect Stripe setup item, `ctx.stripe.fetch` with the order's `mode` on every call, hosted Checkout and a complete example.
|
|
168
|
+
|
|
163
169
|
- An app that works with somebody's GitHub repo loads the `github` skill: the connect link, `config.github`, `ctx.github.routes()`, deliveries and a complete example.
|
|
164
170
|
- `ctx.github.fetch(path, init)` calls the GitHub REST API **as the GitHub App
|
|
165
171
|
installation the owner connected to this vibe**. It is the only GitHub surface
|
|
@@ -400,7 +406,9 @@ const routes = await ctx.github.routes(); // [{ repo, accountLogin, binderHandle
|
|
|
400
406
|
anything else **throw** — a handler that returns normally has acknowledged
|
|
401
407
|
the event, so the platform will not call it again for that write, while a
|
|
402
408
|
thrown `onChange` is re-delivered with backoff (a handful of tries over a few
|
|
403
|
-
minutes) and a run that never succeeds lands in the app's logs.
|
|
409
|
+
minutes) and a run that never succeeds lands in the app's logs. Records
|
|
410
|
+
travelling the other way, from a tool into the app, arrive at `fetch`:
|
|
411
|
+
see *Records in from Zapier* below.
|
|
404
412
|
|
|
405
413
|
```js
|
|
406
414
|
export async function onChange(event, ctx) {
|
|
@@ -843,6 +851,162 @@ signed in.
|
|
|
843
851
|
Writes a handler makes with `ctx.db.put` still pass through `access.js` as the
|
|
844
852
|
caller, so the app's rules hold for API writes too.
|
|
845
853
|
|
|
854
|
+
### Records in from Zapier — a Zap that POSTs to `/_api`
|
|
855
|
+
|
|
856
|
+
A Zap brings records into the app by POSTing to its `/_api` route.
|
|
857
|
+
Reach for it when the ask says the records come *from* another tool — "from
|
|
858
|
+
Zapier", "when a form is submitted in Google Forms", "every new row in my
|
|
859
|
+
sheet", "each new email in my inbox". The owner makes a Zap whose trigger is
|
|
860
|
+
that tool and whose action is **Webhooks by Zapier → POST** to
|
|
861
|
+
`https://vibes.diy/vibe/<owner>/<slug>/_api/zapier/lead`, sending the fields as
|
|
862
|
+
JSON and mapping the tool's own id for the record (the form response id, the
|
|
863
|
+
row id, the message id) to `id`. The route answers the world once the app is
|
|
864
|
+
published.
|
|
865
|
+
|
|
866
|
+
A Zap carries no vibes.diy sign-in, so the route runs as nobody, and the
|
|
867
|
+
recipe has three parts:
|
|
868
|
+
|
|
869
|
+
- **A shared secret proves the sender.** The owner picks a long random value,
|
|
870
|
+
saves it as a secret (`vibes-diy secrets set ZAPIER_INBOUND_SECRET`), and
|
|
871
|
+
pastes the same value into the Zap as an `x-zapier-secret` header. The
|
|
872
|
+
handler compares the two in constant time and answers `401` when they
|
|
873
|
+
differ. The secret rides a header of the app's own naming: `Authorization`
|
|
874
|
+
and `Cookie` are removed before the handler sees the request, and
|
|
875
|
+
`x-vibes-*` belongs to the platform.
|
|
876
|
+
- **The same record lands on the same document.** Zapier can deliver one
|
|
877
|
+
record twice, so its key comes from the record itself — the mapped `id`, or
|
|
878
|
+
a hash of the body — and the record the app shows is written at
|
|
879
|
+
`lead:<key>` with `ifRev: null`, so a second delivery meets a `conflict`
|
|
880
|
+
and changes nothing.
|
|
881
|
+
- **The route writes a sealed arrival, and the tick turns it into the
|
|
882
|
+
record.** The route's write is anonymous, so `access.js` admits it with
|
|
883
|
+
`allowAnonymous: true` — and a type an anonymous route may write, any
|
|
884
|
+
visitor may write directly too. So the route writes an `arrival` carrying a
|
|
885
|
+
seal (an HMAC of its fields under the secret) on a channel no reader is
|
|
886
|
+
granted, and `scheduled`, which reads every channel, makes the seal again,
|
|
887
|
+
writes each matching arrival as the record the app shows (a type only
|
|
888
|
+
`scheduled` writes), and clears the arrival. An arrival with no matching
|
|
889
|
+
seal is cleared without becoming a record. The tick pages through every
|
|
890
|
+
arrival on the `next` cursor, up to ten pages a tick, so a pile of unsealed
|
|
891
|
+
arrivals never stands in front of a real one, and it handles each arrival
|
|
892
|
+
on its own — fields checked as strings, any failure logged — so one odd
|
|
893
|
+
document is cleared and the rest go through. `access.js` keeps every
|
|
894
|
+
arrival at an `arrival:` id, so none can occupy a lead's id.
|
|
895
|
+
|
|
896
|
+
Gate both handlers on the secret, so the app works fully before any Zap
|
|
897
|
+
exists, and say in your reply what the owner sets up: the secret, the Zap's
|
|
898
|
+
POST address and header, and anonymous writes for the app (publishing from
|
|
899
|
+
the editor turns them on when `access.js` declares `allowAnonymous`; after a
|
|
900
|
+
CLI push, `vibes-diy app anon-write <owner>/<slug> on`).
|
|
901
|
+
|
|
902
|
+
### `backend.js`
|
|
903
|
+
```js
|
|
904
|
+
export const config = { scheduled: { interval: "1m" } };
|
|
905
|
+
|
|
906
|
+
const enc = new TextEncoder();
|
|
907
|
+
|
|
908
|
+
function json(data, status) {
|
|
909
|
+
return new Response(JSON.stringify(data), { status: status ?? 200, headers: { "content-type": "application/json" } });
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
function hex(buf) {
|
|
913
|
+
return [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// The seal: an HMAC of the record's fields under the shared secret.
|
|
917
|
+
async function sealOf(secret, arrival) {
|
|
918
|
+
const key = await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
919
|
+
return hex(await crypto.subtle.sign("HMAC", key, enc.encode(JSON.stringify([arrival.key, arrival.name, arrival.email]))));
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Constant time: both sides become fixed-length digests, compared byte by byte with no early exit.
|
|
923
|
+
async function sameText(a, b) {
|
|
924
|
+
const [x, y] = await Promise.all([a, b].map(async (s) => new Uint8Array(await crypto.subtle.digest("SHA-256", enc.encode(s)))));
|
|
925
|
+
let diff = 0;
|
|
926
|
+
for (let i = 0; i < x.length; i++) diff |= x[i] ^ y[i];
|
|
927
|
+
return diff === 0;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
export async function fetch(request, ctx) {
|
|
931
|
+
const url = new URL(request.url);
|
|
932
|
+
if (url.pathname !== "/zapier/lead" || request.method !== "POST") return json({ ok: false, reason: "not found" }, 404);
|
|
933
|
+
const secret = ctx.secrets.ZAPIER_INBOUND_SECRET;
|
|
934
|
+
if (!secret) return json({ ok: false, reason: "this app is still being set up" }, 503);
|
|
935
|
+
const given = request.headers.get("x-zapier-secret") ?? "";
|
|
936
|
+
if (!(await sameText(given, secret))) return json({ ok: false, reason: "unrecognized sender" }, 401);
|
|
937
|
+
const raw = await request.text();
|
|
938
|
+
let body;
|
|
939
|
+
try {
|
|
940
|
+
body = JSON.parse(raw);
|
|
941
|
+
} catch {
|
|
942
|
+
return json({ ok: false, reason: "send the fields as JSON" }, 400);
|
|
943
|
+
}
|
|
944
|
+
if (typeof body?.name !== "string" || typeof body?.email !== "string") {
|
|
945
|
+
return json({ ok: false, reason: "name and email are needed" }, 400);
|
|
946
|
+
}
|
|
947
|
+
// The key comes from the record itself, so a second delivery of it lands on the same lead.
|
|
948
|
+
const source = typeof body.id === "string" && body.id !== "" ? body.id : raw;
|
|
949
|
+
const key = hex(await crypto.subtle.digest("SHA-256", enc.encode(source)));
|
|
950
|
+
const arrival = { type: "arrival", key, name: body.name.slice(0, 200), email: body.email.slice(0, 200) };
|
|
951
|
+
arrival.seal = await sealOf(secret, arrival);
|
|
952
|
+
await ctx.db.put(arrival, { db: "leads", id: "arrival:" + arrival.seal });
|
|
953
|
+
return json({ ok: true, key });
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
export async function scheduled(event, ctx) {
|
|
957
|
+
const secret = ctx.secrets.ZAPIER_INBOUND_SECRET;
|
|
958
|
+
let added = 0;
|
|
959
|
+
let cleared = 0;
|
|
960
|
+
let after;
|
|
961
|
+
let pages = 0;
|
|
962
|
+
// Page through every arrival (up to 10 pages a tick), so a pile of unsealed ones never hides a real one.
|
|
963
|
+
do {
|
|
964
|
+
const page = secret ? await ctx.db.query({ db: "leads", field: "type", key: "arrival", limit: 200, after }) : [];
|
|
965
|
+
for (const arrival of page) {
|
|
966
|
+
// Each arrival is handled on its own: whatever one holds, the tick clears it and moves on.
|
|
967
|
+
try {
|
|
968
|
+
const wellFormed = ["key", "name", "email", "seal"].every((f) => typeof arrival[f] === "string");
|
|
969
|
+
if (wellFormed && (await sameText(arrival.seal, await sealOf(secret, arrival)))) {
|
|
970
|
+
const lead = { type: "lead", name: arrival.name, email: arrival.email, addedAt: event.scheduledTime };
|
|
971
|
+
await ctx.db.put(lead, { db: "leads", id: "lead:" + arrival.key, ifRev: null });
|
|
972
|
+
added += 1;
|
|
973
|
+
} else {
|
|
974
|
+
cleared += 1;
|
|
975
|
+
}
|
|
976
|
+
} catch (err) {
|
|
977
|
+
// A conflict means this lead is already here; anything else is logged and the arrival cleared.
|
|
978
|
+
if (err?.code !== "conflict") ctx.log("warn", "arrival set aside", { id: arrival._id, reason: String(err?.message ?? err) });
|
|
979
|
+
}
|
|
980
|
+
await ctx.db.delete(arrival._id, { db: "leads" }).catch((err) => ctx.log("warn", "arrival kept", { id: arrival._id, reason: String(err?.message ?? err) }));
|
|
981
|
+
}
|
|
982
|
+
after = page.next;
|
|
983
|
+
pages += 1;
|
|
984
|
+
} while (after && pages < 10);
|
|
985
|
+
ctx.log("info", "zapier arrivals", { added, cleared });
|
|
986
|
+
}
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
### `access.js`
|
|
990
|
+
```js
|
|
991
|
+
export function leads(doc, oldDoc, user, ctx) {
|
|
992
|
+
if (oldDoc && oldDoc.type !== doc.type) throw { forbidden: "a record keeps its type" }; // a delete arrives as the stored record, type and all
|
|
993
|
+
if (doc.type === "arrival") {
|
|
994
|
+
// An arrival lives at `arrival:<seal>`, so no arrival can sit at a lead's id.
|
|
995
|
+
if (typeof doc._id !== "string" || !doc._id.startsWith("arrival:")) throw { forbidden: "an arrival lives at arrival:<seal>" };
|
|
996
|
+
if (doc._deleted) ctx.requireBackend("scheduled"); // the tick clears arrivals
|
|
997
|
+
return { channels: ["arrivals"], allowAnonymous: true }; // no reader is granted this channel
|
|
998
|
+
}
|
|
999
|
+
if (doc.type === "lead") {
|
|
1000
|
+
ctx.requireBackend("scheduled"); // a lead is written only after its seal matched
|
|
1001
|
+
return { channels: ["leads"], grant: { users: { [user.userHandle]: ["leads"] } } };
|
|
1002
|
+
}
|
|
1003
|
+
throw { forbidden: "unknown type" };
|
|
1004
|
+
}
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
The page lists `lead` records with `useLiveQuery("type", { key: "lead" })`,
|
|
1008
|
+
and the owner sees each one within a minute of the Zap firing.
|
|
1009
|
+
|
|
846
1010
|
### Bulk transformations run as a route
|
|
847
1011
|
|
|
848
1012
|
A change that touches many stored records is written once, as a named `/_api` route, and run
|
package/llms/connections.js
CHANGED
|
@@ -1,20 +1,25 @@
|
|
|
1
1
|
export const connectionsConfig = {
|
|
2
2
|
name: "connections",
|
|
3
3
|
label: "Connections",
|
|
4
|
-
description: "Connect a visitor's own GitHub profile, Threads or
|
|
4
|
+
description: "Connect a visitor's own GitHub profile, Threads, Instagram or LinkedIn account with one click, read it from backend.js, and post to their Threads, Instagram or LinkedIn — config.connections, useConnection, @vibes.diy/social",
|
|
5
5
|
cues: [
|
|
6
6
|
"connect my",
|
|
7
7
|
"sign in with github",
|
|
8
8
|
"sign in with instagram",
|
|
9
9
|
"sign in with threads",
|
|
10
|
+
"sign in with linkedin",
|
|
10
11
|
"my instagram",
|
|
11
12
|
"my threads",
|
|
12
13
|
"my github",
|
|
14
|
+
"my linkedin",
|
|
15
|
+
"post to linkedin",
|
|
16
|
+
"share on linkedin",
|
|
13
17
|
"my posts",
|
|
14
18
|
"my followers",
|
|
15
19
|
"social media login",
|
|
16
20
|
"instagram",
|
|
17
21
|
"threads",
|
|
22
|
+
"linkedin",
|
|
18
23
|
"github account",
|
|
19
24
|
],
|
|
20
25
|
importModule: "use-vibes",
|
package/llms/connections.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connections.js","sourceRoot":"","sources":["../../jsr/llms/connections.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,iBAAiB,GAAc;IAC1C,IAAI,EAAE,aAAa;IACnB,KAAK,EAAE,aAAa;IACpB,WAAW,EACT,
|
|
1
|
+
{"version":3,"file":"connections.js","sourceRoot":"","sources":["../../jsr/llms/connections.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,iBAAiB,GAAc;IAC1C,IAAI,EAAE,aAAa;IACnB,KAAK,EAAE,aAAa;IACpB,WAAW,EACT,iOAAiO;IACnO,IAAI,EAAE;QACJ,YAAY;QACZ,qBAAqB;QACrB,wBAAwB;QACxB,sBAAsB;QACtB,uBAAuB;QACvB,cAAc;QACd,YAAY;QACZ,WAAW;QACX,aAAa;QACb,kBAAkB;QAClB,mBAAmB;QACnB,UAAU;QACV,cAAc;QACd,oBAAoB;QACpB,WAAW;QACX,SAAS;QACT,UAAU;QACV,gBAAgB;KACjB;IACD,YAAY,EAAE,WAAW;IACzB,UAAU,EAAE,eAAe;CAC5B,CAAC"}
|
package/llms/connections.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Connections — a visitor's own account, spent by your app
|
|
2
2
|
|
|
3
3
|
A **connection** lets a visitor link their own third-party account (GitHub,
|
|
4
|
-
Threads, Instagram) to your app in one click, and lets your `backend.js` read
|
|
4
|
+
Threads, Instagram, LinkedIn) to your app in one click, and lets your `backend.js` read
|
|
5
5
|
from that account — and, when they approve it, post to it — as them. The platform shows the consent screen, runs the
|
|
6
6
|
sign-in, holds the credential, refreshes it, and warns the person before it
|
|
7
7
|
expires. Your app contains **no** client id, secret, redirect route, token
|
|
@@ -17,7 +17,7 @@ to replace.
|
|
|
17
17
|
|
|
18
18
|
## When to use it — signal words
|
|
19
19
|
|
|
20
|
-
"connect my Instagram", "sign in with GitHub", "my Threads posts", "show my
|
|
20
|
+
"connect my Instagram", "sign in with GitHub", "my Threads posts", "post this to my LinkedIn", "show my
|
|
21
21
|
profile from …", "which of my videos got the most likes", "my followers on …".
|
|
22
22
|
A request that names one of the providers below and wants **their** data is a
|
|
23
23
|
connection. A request for public data nobody has to log in for (a public feed,
|
|
@@ -62,6 +62,8 @@ GitHub profile next to your entries."), never as a note to yourself.
|
|
|
62
62
|
| `threads` | `threads_content_publish` | **post to their Threads feed** (`threads(ctx).publish()`) |
|
|
63
63
|
| `instagram` | `instagram_business_basic` | read the account's profile and its own media with like/comment counts |
|
|
64
64
|
| `instagram` | `instagram_business_content_publish` | **post a photo or video to their Instagram** (`instagram(ctx).publish()`) |
|
|
65
|
+
| `linkedin` | `profile` | read the member's name and profile photo (`linkedin(ctx).me()`) |
|
|
66
|
+
| `linkedin` | `w_member_social` | **post to their LinkedIn feed** (`linkedin(ctx).post()`); declare `profile` beside it |
|
|
65
67
|
|
|
66
68
|
**A scope that posts is a separate scope, and asking for it is the whole
|
|
67
69
|
authorization.** Declare it only when the app really posts, and say so in `why`
|
|
@@ -158,8 +160,18 @@ provider call as a typed request with a typed response, so there is no
|
|
|
158
160
|
- `instagram(ctx).publish({ imageUrl, caption? })` → `{ id }`. Needs
|
|
159
161
|
`instagram_business_content_publish`. Instagram has no text-only post, so
|
|
160
162
|
`imageUrl` is required.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
+
- `linkedin(ctx).me()` → `{ id, name, givenName, familyName, picture, personUrn }`.
|
|
164
|
+
Needs `profile`.
|
|
165
|
+
- `linkedin(ctx).post({ text, visibility?, link? })` → `{ id }`. Needs
|
|
166
|
+
`w_member_social` and `profile` (a post is authored as the member, and
|
|
167
|
+
`profile` is where their id comes from). `text` is plain prose: the library
|
|
168
|
+
escapes LinkedIn's formatting characters and keeps `#hashtags` live.
|
|
169
|
+
`visibility` is `"PUBLIC"` (default) or `"CONNECTIONS"`. `link` is
|
|
170
|
+
`{ url, title?, description? }` and becomes the post's card exactly as given,
|
|
171
|
+
since LinkedIn shows what the request carries. LinkedIn posts carry text and
|
|
172
|
+
a link card.
|
|
173
|
+
|
|
174
|
+
**Video posting is not available on any provider yet** — the provider hands
|
|
163
175
|
back a container while the video is still processing, and the platform cannot
|
|
164
176
|
yet read that container's status, so a video post would intermittently fail
|
|
165
177
|
rather than land. Build on text and images; do not work around it with a raw
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const stripeSubscriptionsConfig = {
|
|
2
2
|
name: "stripe-subscriptions",
|
|
3
3
|
label: "Stripe subscriptions",
|
|
4
|
-
description: "A paid membership or subscription through Stripe Checkout in subscription mode, ONLY when the person asks for recurring money — monthly, yearly, subscribe, membership fee, paid tier, paywall —
|
|
4
|
+
description: "A paid membership or subscription through Stripe Checkout in subscription mode, ONLY when the person asks for recurring money — monthly, yearly, subscribe, membership fee, paid tier, paywall — into the owner's own Stripe account through ctx.stripe, connected once with Connect Stripe: the Connect Stripe and Customer Portal setup items, test mode first with the membership's mode passed on every call and live once the owner takes this app live, every Stripe call in backend.js with no Stripe.js in the page, one pending membership then one Checkout Session opened in a new tab, entitlement computed from Stripe's status plus one GRACE_DAYS knob for a past_due card, members-only content as a channel the membership record grants and the backend withdraws, cancellation and card changes in Stripe's Customer Portal, and a scheduled sweep that re-reads every live subscription. A one-time charge (dues once, a ticket, a deposit) is the stripe skill; an app with members-only content that charges nothing does not need this — offer it as the [pay] option instead",
|
|
5
5
|
cues: [
|
|
6
6
|
"subscription",
|
|
7
7
|
"subscribe",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stripe-subscriptions.js","sourceRoot":"","sources":["../../jsr/llms/stripe-subscriptions.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,yBAAyB,GAAc;IAClD,IAAI,EAAE,sBAAsB;IAC5B,KAAK,EAAE,sBAAsB;IAC7B,WAAW,EACT,
|
|
1
|
+
{"version":3,"file":"stripe-subscriptions.js","sourceRoot":"","sources":["../../jsr/llms/stripe-subscriptions.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,yBAAyB,GAAc;IAClD,IAAI,EAAE,sBAAsB;IAC5B,KAAK,EAAE,sBAAsB;IAC7B,WAAW,EACT,siCAAsiC;IACxiC,IAAI,EAAE;QACJ,cAAc;QACd,WAAW;QACX,SAAS;QACT,WAAW;QACX,SAAS;QACT,QAAQ;QACR,gBAAgB;QAChB,iBAAiB;QACjB,WAAW;QACX,SAAS;QACT,WAAW;QACX,cAAc;QACd,iBAAiB;KAClB;CACF,CAAC"}
|