@ episterra/ epigo- sdk
EpiGo平台SDK用于Episterra所建站点. 一个网站 服务器 拥有一个工作空间拥有者或管理员曾批准过一次的连接;通过它,SDK调用go-ai工具(在连接工具的授予、批准和审计下打出),并将数据库读作该管理员(打出,只读)。 访客-取景页面使用公用半个,只需要公用肛门键.
设计 : docs/platform-sdk.md 在go-ai仓库。 正在连接一个网站 : docs/platform-sdk-site-connections.md。 。 。 .
服务器
import { createEpigoSite } from "@episterra/epigo-sdk";
const site = await createEpigoSite({
credentials: {
clientId: process.env.EPIGO_CLIENT_ID!,
clientSecret: process.env.EPIGO_CLIENT_SECRET!,
refreshToken: await store.get("epigo_refresh_token"),
// Refresh tokens rotate: persist every new pair, or a restart is cut off.
onTokens: ({ refreshToken }) => store.set("epigo_refresh_token", refreshToken),
},
});
// Actions: typed by the generated catalog, run under the connection's grants.
const r = await site.tools.call("create_lead", { name: "Bigsby", email: "[email protected]" });
if (r.pendingApproval) {
// A workspace admin decides it in the app.
}
// Reads: supabase-js, typed like the app, as the approving admin, read-only.
const { data } = await site.db.from("store_orders").select("id, status, total").eq("buyer_email", email);
// An RPC read must be a GET: the database refuses a site's writes, and supabase-js posts an RPC by default.
const { data: rows } = await site.db.rpc("some_reader", { p_workspace_id }, { get: true });
// Money, the way the app prints it.
site.money.formatCents(88750); // "$887.50"
createEpigoSite 拒绝在浏览器中运行; 平台拒绝他们同样的请求 Origin 头曰. 在服务器上保留客户端和信使的秘密,过滤每一个向服务器上注册的客户端读取的门户网站,并用经过核实的电子邮件在客户端上签名.
访客页面
import { createEpigoPublic, loadConfig } from "@episterra/epigo-sdk";
const pub = createEpigoPublic(await loadConfig());
await pub.store.catalog({ ws_slug: "bigsbys" });
await pub.concierge.turn({ form_id, step_id, visitor_key, messages: [{ role: "user", content: text }] }); // the whole conversation so far
await pub.forms.submit({ form_id, data: answers });
在浏览器中, forms.submit 和警卫人员也发送页面地址(page_url)和网站解析标记的id(analytics: { session_id, visitor_id },来自会话Storage __va_sid 和本地资源 __va_vid),因此EpiGo打开的请求与访问(0.10.1)相关. 拥有自己的身体 page_url 或者说 analytics 按原样发送.
端点和肛门键来自 GET https://api.epigo.ai/functions/v1/sdk-config 在启动时, 键旋转从不需要重排 .
工作人员注册
一个网站的员工在EpiGo主机登录页面上用自己的EpiGo账户签入,然后每个读取和调用都和那个人自己的访问功能一起出站,如EpiGo应用程序. 该网站需要Episterra的应用客户端ID;它没有秘密。 仅浏览器 .
import { createEpigoUser } from "@episterra/epigo-sdk";
const staff = await createEpigoUser({
clientId: "<the site's app client id>",
redirectUri: `${location.origin}/auth/callback`, // registered with the client
});
// Sign-in button:
await staff.signIn({ returnTo: "/staff" });
// On /auth/callback:
const { returnTo } = await staff.handleCallback();
location.replace(returnTo ?? "/staff");
// Anywhere after:
staff.isSignedIn();
staff.claims(); // { sub, email, emailVerified }
await staff.db.from("cal_entries").select("id, title, starts_at");
await staff.functions.invoke("some-function", { workspace_id });
await staff.signOut();
主机登录地址来自 sdk-config (单位:千美元)sign_in(a) 一个没有答案的环境 null 财务报告和审定财务报表 signIn 掷出. 代码与PKCE交换,访问令牌在过期前一分钟被续发,被拒刷新者表示退出.
在不离开页面的情况下签字(0.4.0)
signIn({ popup: true }) 在页面上的一个小窗口中打开主机登录,并在被签名后解决;没有导航。 从点击处理器直接调用(浏览器仅在点击时允许窗口). 被屏蔽的窗口或浏览器 BroadcastChannel,倒回上面的全页登录.
// Sign-in button:
signInButton.onclick = async () => {
await staff.signIn({ popup: true });
render(staff.claims()); // signed in, still on this page
};
// On /auth/callback, the same page serves both: the window finishes the sign-in, tells the page and closes.
const { returnTo } = await staff.handleCallback();
location.replace(returnTo ?? "/staff"); // reached only when the browser kept the window open
窗口在回调页面上完成登录本身,因为EpiGo的页面将其从打开的页面中剪去(也不能被设定). staff.isPopupCallback() 告诉一个做其他工作的回调页面( 预览中继, 说) 这个回调是一个弹出, for handleCallback如果当事人关闭了窗户, signIn 后拒绝 timeoutMs 默认时间为10分钟;第二次点击只需重新打开窗口即可.
客户注册(客户门户)
一个网站的客户在客户池的托管登录上签名,并用电子邮件发给他们,然后读取数据库的门户视图:他们自己的订单,引文,发票和网站工作空间中的事件,通过数据库过滤到客户账户中,他们核实的电子邮件属于客户账户. 客户池每个网站都有自己的应用客户端. 仅浏览器 .
import { createEpigoUser } from "@episterra/epigo-sdk";
const customer = await createEpigoUser({
mode: "customer",
clientId: "<the site's customer app client id>",
redirectUri: `${location.origin}/account/callback`, // registered with the customer client
});
await customer.signIn({ returnTo: "/account" });
// On /account/callback:
const { returnTo } = await customer.handleCallback();
// The views: orders, order_lines, quotes, quote_lines, invoices, events (typed: PortalOrder, PortalInvoice, …).
const { data: orders } = await customer.portal.from("orders").select("*").order("placed_at", { ascending: false });
const { data: invoices } = await customer.portal.from("invoices").select("*");
// An invoice's stripe_payment_link_url opens the hosted page that pays its balance.
// Files staff chose to show on the customer's orders ("Customer can see" on the order's Files tab).
const { data: files } = await customer.portal.from("order_files").select("*").eq("order_id", orders![0].id);
const link = await customer.openPortalFile(files![0].id); // { url, name, mime_type, expires_in }: a link for 5 minutes
window.open(link.url, "_blank", "noopener"); // or { download: true } for a download named after the file
await customer.signOut();
签有订票通行证的订票端页(不是客户池)读作相同 order_files 查看其通行证,并通过张贴打开文件 { pass, file_id, download? } 页:1 portal-file 函数与 肛门键.
钱和来宾计算(0.7.0)
显示数据库的号码;从不在页面上添加付款、发票或数量.
const { data: order } = await customer.portal.from("orders").select("total, paid_total, balance_due").eq("id", orderId).single();
// paid_total: paid, net of refunds, as go-ai counts it (a payment naming the order, its invoices or its quote).
// balance_due: what is still owed, never below 0; null when go-ai states no balance (a cancelled or refunded
// order, or one settled outside EpiGo's ledger). Show "Paid in full" for 0, nothing for null.
const { data: lines } = await customer.portal.from("order_lines").select("id, label, qty, guests, parent_line_id").eq("order_id", orderId);
// guests: the head count the line's calculator priced it by, or null. Never a line's qty.
响起 portal.invoices 给出不同的答案:进口订票的付款没有发票。 工作人员页读取 private_event_money(workspace_id) (每个私人活动订单的价值和支付) staff.db.rpc 而不是在浏览器中自拍.
portal 发送 Accept-Profile: portal,数据库中唯一允许客户读取符号的配置文件; db 丢入客户模式。 客户 functions.invoke 调用与肛门键, 因为函数级只接受工作人员符号。 登录地址来自 sdk-config (单位:千美元)customer_sign_in) (中文(简体) ).
没有EpiGo登录的工作人员:工作证(0.9.0)
小时工作人员经常没有EpiGo登录. createEpigoWorkPass 在企业自己的网站的员工网页上签名,并用链接EpiGo电子邮件与他们的Work EMAIL连接,通过与EpiGo自己的员工网页相同的门(EpiGo)/staff/<slug>:他们轮班,开班接班,换班,请假和请假,工作时间和薪水,以及他们与同事和管理人员的谈话. 只浏览器; 它没有秘密 .
go-ai列出每个工作空间的工作人员网页(STAFF_PAGES_BY_API 内 supabase/functions/_shared/work-portal-link.ts),所以每个链接,通知并推出EpiGo发送到工作空间的工作人员打开网站的页面时,只有去爱时才会在那里列出. 在此之前,他们打开了EpiGo的页面. 添加一个网站的员工页面是公关,就像添加其登录客户端一样,一旦网站的页面给人签入,它就会进入,所以没有链接到达一个无法进入的页面(Bigsby's Cellar, https://www.bigsbysfolly.com/staff/me,是下一个).
import { createEpigoWorkPass, EpigoWorkPassError } from "@episterra/epigo-sdk";
import { workWarningText } from "@episterra/epigo-sdk/rules";
const crew = await createEpigoWorkPass({ workspace: "bigsbys-folly", serviceWorkerUrl: "/sw.js" });
// On every load of the staff page: keeps a link from #access=..., clears it from the address bar, opens it.
const signedIn = await crew.signIn();
if (signedIn.state !== "signed_in") {
// "signed_out" (no link here yet: message null), "expired", "invalid", "member" (they have a login now), "retry".
// Show "Email me a link" with signedIn.message.
const asked = await crew.requestLink(email); // reads the same whether or not EpiGo knows the address
show(asked.message);
} else {
const { me, chatId } = signedIn; // chatId: the conversation a message's notice opened
const schedule = await crew.schedule(); // work, open_slots, offers, time_off, requests, policy, hours, announcements
const time = await crew.time(); // entries, periods (closed pay weeks), totals
schedule.open_slots.forEach((s) => s.warnings.map(workWarningText)); // what stands in the way, in EpiGo's words
try {
await crew.request({ kind: "pickup", taskId: slot.task_id, slotId: slot.slot_id! });
await crew.request({ kind: "time_off", firstDay: "2026-11-10", lastDay: "2026-11-12", note: "Family visit" });
const people = await crew.swapCandidates(shift.task_id, shift.slot_id!); // offer only can_answer ones
await crew.answer(offer.id, true); // a swap offered to them
} catch (e) {
if (e instanceof EpigoWorkPassError) show(e.message); // EpiGo's sentence; state "signed_out" means ask for a new link
}
const { chats, unread } = await crew.chat.list();
const thread = await crew.chat.thread(chats[0]!.id); // { chat, messages, has_more }; older: { before: messages[0].at }
await crew.chat.send(thread.chat!.id, "On my way");
await crew.chat.read(thread.chat!.id);
}
- 传出. 一个链接工作了7天,必要时经常使用。 打开它回答一个小时的通过;客户端在通过结束5分钟前再次打开链接,每当一个读取或动作发现它消失,所以一个被撤销的链接,一个给定的登录或一个被更改的工作邮件在小时内结束会话. 一个停止工作的链接在设备上被遗忘了.
- 拒绝. 工作空间自己的要求政策和规则决定了每一项行动. 拒绝掷出
EpigoWorkPassError与state(单位:千美元)refused, (中文(简体) ),invalid, (中文(简体) ),signed_out, (中文(简体) ),retry)和所要显示的句子. - 通报.
crew.push.enable(),从点击直接拨打,询问浏览器,通过网站的服务人员用密钥订阅sdk-config出版(config.push),并保持不利于个人;crew.push.disable()财务报告和审定财务报表crew.signOut()结束它。 JSON,工人们都表现出来{ title, body, url, tag }打开url点击( 信件带)#chat=<conversation>) (中文(简体) ). 在一款iPhone上,只有当网站被添加到家用屏幕上时,推力才能工作. - 时间钟(0.10.0),仅指工作空间开启的时间钟. 手机的时钟是工作空间设定,默认情况下关闭;而工作时钟则关闭人员进出。 见下文.
时钟( 0. 0.0)
me.clock.phone 说工作空间是否允许人们从电话中计时: off (默认), location 或者说 network显示时钟 workClockOn(me) 说是. 每一出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出入出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出出.
import { workClockOn, workClockWantsLocation } from "@episterra/epigo-sdk";
if (workClockOn(me)) {
const clock = await crew.clock.state(); // open, on_break, shift_now, shift_next, can_clock_in, policy, timezone
clock.shift_now?.rule_warnings?.map(workWarningText); // shown, never a refusal
async function punch(p: "clock_in" | "clock_out" | "break_start" | "break_end") {
// Ask for the position from the punch's click, and only where the location rule wants it.
const location = workClockWantsLocation(p, clock.policy) ? await crew.clock.position() : null;
try {
const { entry, clock: now } = await crew.clock.punch(p, {
location, // clock in and clock out only
attestation: p === "clock_out" && clock.policy.break_attestation ? { breaksTaken, note } : null,
breakKind: p === "break_start" ? kind : undefined, // "rest" (paid) or "meal" (unpaid, what EpiGo takes when none is said)
});
show(now); // the clock as it now stands
} catch (e) {
if (e instanceof EpigoWorkPassError) show(e.message); // the clock's own sentence, word for word
}
}
}
- 关闭意味着没有时钟. 每一拳都打不开,一字不差地说, "在工作场所的时钟上时钟", 什么都没有写。 页面显示没有时钟, 没有读取时钟状态 .
- 拒绝 是时钟本身的句子:没有为时钟输入打开的班次,已经时钟输入,没有在工作场所的Wi-Fi上,规则要求的位置,中断签出缺失. 随所来而示现.
- 立场.
crew.clock.position()询问设备一次( 浏览器第一次提示) :{ lat, lng, accuracyM },或null当浏览器没有时,人说不,或者没有固定在时间(10秒). 从来投出. 下location: "record"未标出位置的一拳"require"没有时间就被拒绝了. - 从个人的浏览器. 出自工作人员页面本身的出击,从未出自网站的服务器:根据网络规则,出击的出击地点由门检查.
细则(0.6.0)
@episterra/epigo-sdk/rules 是 go-ai 自己的规则代码, 相同的文件, 应用程序及其边缘功能运行, 在每次发布时复制 。 一个页面导入规则而不是写自己的规则,所以它同意服务器的意见,对规则的修改随着下一个版本的到来. 它没有从SDK进口其它东西,也没有捆绑任何supabase-js,所以它在任何浏览器中运行. 设计 : docs/platform-builder-kit.md (第1编).
import { portalMenuRules } from "@episterra/epigo-sdk/rules";
// The booking portal's menu answer (`menu` on booking-portal-session) carries `rules`: the groups and options behind
// its sections, with only the fields the rules read.
const r = portalMenuRules(menu);
r.limits(section.group_id); // { min: 2, max: 2, pickOne: false }: "choose 2", without parsing the hint
r.prune(picks); // a changed choice takes what it opened with it
r.complete(picks); // "Select" alone brings its Bar package along
r.check(picks)?.message; // "Proteins: choose 2.", as the server says it
r.startDetail(optionId); // { qty: 2 }: where a new pick starts (drink tickets, 2 a guest)
| 规则 | 出口 |
|---|---|
| 选项组: 选择限制、 显示时间、 选择和打开的内容、 第一个问题 | groupPickLimits, (中文(简体) ), pickLimitsHint, (中文(简体) ), activeGroupIds, (中文(简体) ), showWhenChildren, (中文(简体) ), completeShowWhenChain, (中文(简体) ), checkOptionRules |
| 选取:数量、界限、起始数量、大小 | calcPickRules, (中文(简体) ), defaultPickDetail |
| 文档行:函数及其选项,$0标题,来宾,标题 | groupFunctionLines, (中文(简体) ), headsItsOptions, (中文(简体) ), functionGuests, (中文(简体) ), functionCaption |
| 订货阶段:八个字,其标签,行为订货 | BOOKING_STAGE_WORDS, (中文(简体) ), BOOKING_STAGE_LABELS, (中文(简体) ), stageWordOf, (中文(简体) ), stageLabel, (中文(简体) ), isBookingsRow |
| 更名:"十州"和"来宾"和"队伍"读作(0.8 | CHANGE_STATES, (中文(简体) ), CUSTOMER_STATE_WORDS, (中文(简体) ), customerStateWords, (中文(简体) ), TEAM_STATE_WORDS |
| 发文同意:服务器所保存的句子,逐个字,一个页面显示的字(0.8.3) | textsConsentWording, (中文(简体) ), textsConsentWordingChangesOnly, (中文(简体) ), textsConsentScope, (中文(简体) ), phoneWords, (中文(简体) ), toE164 |
一个工作人员页面,内容是 finance_option_groups 财务报告和审定财务报表 finance_options 本身将其读取的行中的选项函数称为 .
关于预订的文字覆盖了团队的信息(0.8.3)
textsConsentWording(venue, phone) 现在给句子"我同意从{venue}在{phone}收到关于这次预订的短信,包括来自其团队的消息和预订的更改. 消息频率不一. 消息和数据率可能适用。 回复停止选择退出。" 同意的来宾(sms_consent 将这句话改为: wording)是节目组在预订的谈话中发送的每条短信,从会场本身的编号,并有链接到"传送门"(从不包含其访问符),以及有关更改的文本. 在输入数字之前,句子写作"在我的手机号码". 门户显示新句子,或 wording 页:1 texts 读取答案,并可能将其文本框放在任何它喜欢的地方,比如在预订的信息之外.
texts.covers_messages,在texts财务报告和审定财务报表sms_consent回答,说"是"是否覆盖了团队的信息.textsConsentWordingChangesOnly(venue, phone)是0.8.3之前的句子。 go-ai仍保留在其下给出的"是",因为只有关于更改的文本,所以0.8.2上的一页一直工作到更新;这样的来宾只有在他们同意了新句子后才能通过文本获得团队的信息.textsConsentScope(shown, venue, phone)答题"booking"对于新句子,"changes"为旧的和null对于任何其他字, 拒绝作为stale。 。 。 .
建设者指南 (0.8.0)
skills/epigo-builder/SKILL.md 在这个包中,是AI网站构建者(Lovable, Claude)在以EpiGo为基础建立之前所读取的指南:每个页面使用的客户端,请求的每个步骤的呼声,持有,预订,引用,更改或支付,每一个与其状态的预订-端口动作,一个页面的号码和规则都绝不能自己解决. 它在每次发布时都是由go-ai生成的,而go-ai的测试在停止匹配平台时会失败,因此一个站点会从下个版本的向导而不是从聊天消息中学习到一个变化. 每个版本都添加了"为建设者改变了什么"的注释.
- 爱人 : episterra 团队将它安装为工作空间技能,命名为
epigo-builder在EpiGo上创建的可爱的工作空间中,每个项目都得到它,并随每个发布版本更新它. - 克劳德: 复制
node_modules/@episterra/epigo-sdk/skills/epigo-builder/输入网站仓库.claude/skills/。 。 。 . - 任何连接到去爱的 MCP 服务器的代理 (单位:千美元)
https://api.epigo.ai/functions/v1/mcp-server电话:get_sdk_guide。它返回安装步骤和发布 go-ai 运行的指南,在每个连接上.
所需资源
22号节点或服务器上更新(supabase-js需要运行时间的本地WebSocket). 德诺和邦也工作. createEpigoUser 财务报告和审定财务报表 createEpigoWorkPass 在浏览器中运行.
版本数
软件包来自一个平台承诺(epigo.platformCommit 内 package.json (b) 释放。 Pin a s版本并刻意升级;在发行间允许突破更改,而Episterra团队在发行时会更新受影响的站点.